[Feat] Support Pipeline Resolution Based on HF Config & Make Pipeline Registry Eager (#3760)
Signed-off-by: Alex Brooks <albrooks@redhat.com>
This commit is contained in:
1 parent
f6894f4308
commit
465f1464d2
44 files changed
+1038
-1126
No files matched your search
@@ -1,146 +1,68 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Tests for the central pipeline registry (2.5/N)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
"""Tests for out of tree registration to OMNI_PIPELINES."""
|
||||
|
||||
import pytest
|
||||
from transformers import PretrainedConfig
|
||||
|
||||
from vllm_omni.config.pipeline_registry import _OMNI_PIPELINES
|
||||
from vllm_omni.config.stage_config import (
|
||||
_PIPELINE_REGISTRY,
|
||||
PipelineConfig,
|
||||
StageExecutionType,
|
||||
StagePipelineConfig,
|
||||
register_pipeline,
|
||||
)
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES, register_pipeline
|
||||
from vllm_omni.config.stage_config import PipelineConfig, pipeline_cfg_resolver
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class TestCentralRegistryDeclarations:
|
||||
"""Every in-tree pipeline must be declared exactly once in the central registry."""
|
||||
@pytest.fixture
|
||||
def custom_resolver():
|
||||
"""Build a reusable custom resolver for PipelineConfigs."""
|
||||
|
||||
def test_omni_entries_visible_in_registry(self):
|
||||
for key in _OMNI_PIPELINES:
|
||||
assert key in _PIPELINE_REGISTRY
|
||||
class CustomConfigType(PretrainedConfig):
|
||||
pass
|
||||
|
||||
def test_expected_omni_pipelines_present(self):
|
||||
# Guard against accidental removal during future refactors.
|
||||
assert "qwen2_5_omni" in _OMNI_PIPELINES
|
||||
assert "qwen2_5_omni_thinker_only" in _OMNI_PIPELINES
|
||||
assert "qwen3_omni_moe" in _OMNI_PIPELINES
|
||||
assert "aura_omni" in _OMNI_PIPELINES
|
||||
assert "qwen3_tts" in _OMNI_PIPELINES
|
||||
@pipeline_cfg_resolver(config_type=CustomConfigType)
|
||||
def custom_resolver(
|
||||
hf_config: CustomConfigType,
|
||||
) -> PipelineConfig:
|
||||
return PipelineConfig(model_type="resolved_type")
|
||||
|
||||
return custom_resolver
|
||||
|
||||
|
||||
class TestLazyLoading:
|
||||
"""Pipelines are imported only on first access."""
|
||||
|
||||
def test_contains_without_import(self):
|
||||
# ``in`` hits the lazy map, not the loaded cache.
|
||||
assert "qwen3_omni_moe" in _PIPELINE_REGISTRY
|
||||
|
||||
def test_getitem_loads_correct_pipeline(self):
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
assert pipeline.model_type == "qwen3_omni_moe"
|
||||
assert pipeline.model_arch == "Qwen3OmniMoeForConditionalGeneration"
|
||||
|
||||
def test_getitem_loads_aura_omni_pipeline(self):
|
||||
pipeline = _PIPELINE_REGISTRY["aura_omni"]
|
||||
assert pipeline.model_type == "aura_omni"
|
||||
assert [stage.model_stage for stage in pipeline.stages] == [
|
||||
"asr",
|
||||
"aura",
|
||||
"qwen3_tts",
|
||||
"code2wav",
|
||||
]
|
||||
assert pipeline.stages[0].model_arch == "Qwen3ASRForConditionalGeneration"
|
||||
assert pipeline.stages[1].model_arch == "AuraQwen3VLForConditionalGeneration"
|
||||
assert pipeline.stages[2].model_arch == "Qwen3TTSTalkerForConditionalGeneration"
|
||||
assert pipeline.stages[3].model_arch == "Qwen3TTSCode2Wav"
|
||||
|
||||
def test_unknown_model_type_returns_none_via_get(self):
|
||||
assert _PIPELINE_REGISTRY.get("not_a_real_pipeline") is None
|
||||
|
||||
def test_unknown_model_type_raises_keyerror_via_getitem(self):
|
||||
with pytest.raises(KeyError):
|
||||
_PIPELINE_REGISTRY["not_a_real_pipeline"]
|
||||
|
||||
def test_iteration_yields_registered_pipelines(self):
|
||||
keys = set(_PIPELINE_REGISTRY)
|
||||
assert "qwen2_5_omni" in keys
|
||||
assert "qwen3_omni_moe" in keys
|
||||
|
||||
def test_iteration_resilience(self, monkeypatch, caplog):
|
||||
# Synthetic failure to verify iteration robustness against load errors.
|
||||
target_logger = logging.getLogger("vllm_omni.config.stage_config")
|
||||
target_logger.addHandler(caplog.handler)
|
||||
prev_level = target_logger.level
|
||||
target_logger.setLevel(logging.ERROR)
|
||||
try:
|
||||
monkeypatch.setattr(
|
||||
_PIPELINE_REGISTRY,
|
||||
"_lazy_map",
|
||||
{
|
||||
"healthy": ("vllm_omni.model_executor.models.qwen2_5_omni.pipeline", "QWEN2_5_OMNI_PIPELINE"),
|
||||
"broken": ("vllm_omni.non_existent_module", "SomeConfig"),
|
||||
"attr_fail": ("vllm_omni.config.stage_config", "NON_EXISTENT_VAR"),
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(_PIPELINE_REGISTRY, "_loaded", {})
|
||||
|
||||
# Assert that list(_PIPELINE_REGISTRY.values()) and dict(_PIPELINE_REGISTRY.items())
|
||||
# both yield only healthy entries.
|
||||
healthy_instance = _PIPELINE_REGISTRY["healthy"]
|
||||
assert list(_PIPELINE_REGISTRY.values()) == [healthy_instance]
|
||||
assert dict(_PIPELINE_REGISTRY.items()) == {"healthy": healthy_instance}
|
||||
assert "Failed to import pipeline module" in caplog.text
|
||||
assert "Pipeline variable 'NON_EXISTENT_VAR' not found" in caplog.text
|
||||
|
||||
finally:
|
||||
target_logger.removeHandler(caplog.handler)
|
||||
target_logger.setLevel(prev_level)
|
||||
def test_register_pipeline_config(clean_pipeline_registry):
|
||||
"""Ensure that we can register a custom pipeline config to OMNI_PIPELINES."""
|
||||
new_model_type = "new_model_type"
|
||||
pipe_cfg = PipelineConfig(model_type=new_model_type)
|
||||
assert new_model_type not in OMNI_PIPELINES
|
||||
register_pipeline(pipe_cfg)
|
||||
assert new_model_type in OMNI_PIPELINES
|
||||
assert OMNI_PIPELINES[new_model_type] is pipe_cfg
|
||||
|
||||
|
||||
class TestDynamicRegistration:
|
||||
"""``register_pipeline()`` still works for plugins and tests."""
|
||||
def test_register_pipeline_config_with_model_type(clean_pipeline_registry):
|
||||
"""Ensure that we can register a custom pipeline config with an explicit model_type to OMNI_PIPELINES."""
|
||||
new_model_type = "new_model_type"
|
||||
unused_model_type = "foo"
|
||||
pipe_cfg = PipelineConfig(model_type=unused_model_type)
|
||||
assert new_model_type not in OMNI_PIPELINES
|
||||
assert unused_model_type not in OMNI_PIPELINES
|
||||
|
||||
def test_register_adds_to_registry(self):
|
||||
custom = PipelineConfig(
|
||||
model_type="_test_dynamic_registration",
|
||||
model_arch="DynamicTestModel",
|
||||
stages=(
|
||||
StagePipelineConfig(
|
||||
stage_id=0,
|
||||
model_stage="test",
|
||||
execution_type=StageExecutionType.LLM_AR,
|
||||
input_sources=(),
|
||||
final_output=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
register_pipeline(custom)
|
||||
try:
|
||||
assert "_test_dynamic_registration" in _PIPELINE_REGISTRY
|
||||
assert _PIPELINE_REGISTRY["_test_dynamic_registration"] is custom
|
||||
finally:
|
||||
# Don't leak the test registration into other tests.
|
||||
if "_test_dynamic_registration" in _PIPELINE_REGISTRY:
|
||||
del _PIPELINE_REGISTRY["_test_dynamic_registration"]
|
||||
# Registering with an explicitly provided model_type uses
|
||||
# the passed value instead of the pipeline_cfg.model_type
|
||||
register_pipeline(pipe_cfg, new_model_type)
|
||||
assert new_model_type in OMNI_PIPELINES
|
||||
assert unused_model_type not in OMNI_PIPELINES
|
||||
assert OMNI_PIPELINES[new_model_type] is pipe_cfg
|
||||
|
||||
def test_dynamic_registration_overrides_lazy_entry(self):
|
||||
# Build a substitute for qwen3_omni_moe that we can distinguish.
|
||||
original = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
override = PipelineConfig(
|
||||
model_type="qwen3_omni_moe",
|
||||
model_arch="OverriddenArch",
|
||||
stages=original.stages,
|
||||
)
|
||||
register_pipeline(override)
|
||||
try:
|
||||
assert _PIPELINE_REGISTRY["qwen3_omni_moe"].model_arch == "OverriddenArch"
|
||||
finally:
|
||||
# Remove the dynamic override so later tests see the original.
|
||||
if "qwen3_omni_moe" in _PIPELINE_REGISTRY._loaded:
|
||||
del _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
|
||||
def test_register_resolver(custom_resolver, clean_pipeline_registry):
|
||||
"""Ensure that we can register a custom resolver to OMNI_PIPELINES."""
|
||||
new_model_type = "new_model_type"
|
||||
assert new_model_type not in OMNI_PIPELINES
|
||||
register_pipeline(custom_resolver, new_model_type)
|
||||
assert new_model_type in OMNI_PIPELINES
|
||||
assert OMNI_PIPELINES[new_model_type] is custom_resolver
|
||||
|
||||
|
||||
def test_register_resolver_requires_model_type(custom_resolver, clean_pipeline_registry):
|
||||
"""Ensure that registering a custom resolver to OMNI_PIPELINES requires an explicit model_type."""
|
||||
with pytest.raises(ValueError):
|
||||
register_pipeline(custom_resolver)
|
||||
@@ -17,6 +17,7 @@ from tests.model_executor.helpers import bootstrap_vllm_layer_custom_op_modules
|
||||
bootstrap_vllm_layer_custom_op_modules()
|
||||
|
||||
pytest_plugins = (
|
||||
"tests.helpers.fixtures.config",
|
||||
"tests.helpers.fixtures.env",
|
||||
"tests.helpers.fixtures.log",
|
||||
"tests.helpers.fixtures.run_args",
|
||||
|
||||
@@ -14,7 +14,7 @@ from tests.helpers.stage_config import get_deploy_config_path, modify_stage_conf
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
|
||||
models = ["Qwen/Qwen3-Omni-30B-A3B-Instruct"]
|
||||
|
||||
thinker_only_models = ["Qwen/Qwen3-Omni-30B-A3B-Captioner"]
|
||||
|
||||
# Single CI deploy YAML; rocm/xpu deltas are picked automatically via the
|
||||
# platforms: section. Only CUDA needs an extra enforce_eager tweak.
|
||||
@@ -40,6 +40,10 @@ else:
|
||||
|
||||
# Create parameter combinations for model and stage config
|
||||
test_params = [(model, stage_config) for model in models for stage_config in stage_configs]
|
||||
# we can use the same config for a model that only has thinker (i.e., does not
|
||||
# enable audio output) because the resolver should figure out that it doesn't
|
||||
# need the full pipeline based on the HF config.
|
||||
thinker_test_params = [(model, stage_config) for model in thinker_only_models for stage_config in stage_configs]
|
||||
|
||||
|
||||
def get_question(prompt_type="video"):
|
||||
@@ -61,3 +65,15 @@ def test_video_to_audio(omni_runner, omni_runner_handler) -> None:
|
||||
|
||||
# Test single completion
|
||||
omni_runner_handler.send_omni_request(request_config)
|
||||
|
||||
|
||||
@pytest.mark.advanced_model
|
||||
@pytest.mark.omni
|
||||
@hardware_test(res={"cuda": "H100", "rocm": "MI325"}, num_cards=1)
|
||||
@pytest.mark.parametrize("omni_runner", thinker_test_params, indirect=True)
|
||||
def test_thinker_only_model_request(omni_runner, omni_runner_handler) -> None:
|
||||
"""Test that we can load and run a request through a model that only has the thinker stage."""
|
||||
request_config = {"prompts": "what color is the sky?", "modalities": ["text"]}
|
||||
|
||||
# Test single completion
|
||||
omni_runner_handler.send_omni_request(request_config)
|
||||
@@ -9,13 +9,13 @@ from vllm.inputs import PromptType
|
||||
|
||||
from tests.helpers.mark import hardware_test
|
||||
from tests.helpers.stage_config import get_deploy_config_path
|
||||
from vllm_omni.config.stage_config import _PIPELINE_REGISTRY
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
||||
from vllm_omni.entrypoints.async_omni import AsyncOmni
|
||||
|
||||
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
|
||||
|
||||
SEED = 42
|
||||
pipeline = _PIPELINE_REGISTRY["qwen2_5_omni_thinker_only"]
|
||||
pipeline = OMNI_PIPELINES["qwen2_5_omni_thinker_only"]
|
||||
|
||||
# Single-stage thinker-only deploy, materialized from tests.helpers.stage_config._CI_OVERLAYS.
|
||||
stage_config = get_deploy_config_path("ci/qwen2_5_omni_thinker_only.yaml")
|
||||
|
||||
@@ -24,7 +24,7 @@ def test_dreamzero_vla_resolves_to_dreamzero_config(monkeypatch):
|
||||
|
||||
def test_dreamzero_config_sets_model_class_and_policy_config(monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.config.stage_config.StageConfigFactory._auto_detect_model_type",
|
||||
"vllm_omni.config.config_factory.StageConfigFactory._auto_detect_model_type",
|
||||
classmethod(lambda _cls, _model, trust_remote_code=True: ("vla", None)),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
"""Fixtures config creation & out of tree registry management."""
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def clean_pipeline_registry():
|
||||
"""Ensure the OMNI_PIPELINES are in a clean state for a test that mutates it."""
|
||||
snapshot = dict(OMNI_PIPELINES)
|
||||
yield
|
||||
OMNI_PIPELINES.clear()
|
||||
OMNI_PIPELINES.update(snapshot)
|
||||
@@ -1,76 +0,0 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Unit tests for GLM-Image model __init__.py lazy import pattern.
|
||||
|
||||
The ``__init__.py`` uses ``__getattr__`` for lazy loading to avoid importing
|
||||
``transformers.models.glm_image`` at module init, which may not be available
|
||||
in all environments.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
|
||||
class TestLazyImport:
|
||||
"""Test the __getattr__ lazy import pattern in glm_image __init__.py."""
|
||||
|
||||
def test_getattr_exists_and_is_callable(self):
|
||||
"""Verify __getattr__ exists and is callable."""
|
||||
import vllm_omni.model_executor.models.glm_image as glm_image_pkg
|
||||
|
||||
assert hasattr(glm_image_pkg, "__getattr__")
|
||||
assert callable(glm_image_pkg.__getattr__)
|
||||
|
||||
def test_getattr_returns_class_for_known_attribute(self):
|
||||
"""Verify __getattr__ returns GlmImageForConditionalGeneration for known attribute."""
|
||||
import vllm_omni.model_executor.models.glm_image as glm_image_pkg
|
||||
|
||||
# Call __getattr__ directly to test the lazy import logic
|
||||
result = glm_image_pkg.__getattr__("GlmImageForConditionalGeneration")
|
||||
|
||||
# Verify we get a class (the actual GlmImageForConditionalGeneration)
|
||||
assert result is not None
|
||||
assert isinstance(result, type)
|
||||
assert result.__name__ == "GlmImageForConditionalGeneration"
|
||||
|
||||
def test_getattr_raises_for_unknown_attribute(self):
|
||||
"""Verify __getattr__ raises AttributeError for unknown attributes."""
|
||||
import vllm_omni.model_executor.models.glm_image as glm_image_pkg
|
||||
|
||||
# Test unknown attribute via __getattr__ directly
|
||||
with pytest.raises(AttributeError, match="has no attribute"):
|
||||
glm_image_pkg.__getattr__("UnknownClass")
|
||||
|
||||
def test___all___exports_correct_symbols(self):
|
||||
"""Verify __all__ contains the expected exported symbols."""
|
||||
import vllm_omni.model_executor.models.glm_image as glm_image_pkg
|
||||
|
||||
assert hasattr(glm_image_pkg, "__all__")
|
||||
assert "GlmImageForConditionalGeneration" in glm_image_pkg.__all__
|
||||
|
||||
|
||||
class TestLazyImportDoesNotImportTransformersAtInit:
|
||||
"""Verify that importing the package does not eagerly load transformers."""
|
||||
|
||||
def test_glm_image_module_has_getattr(self):
|
||||
"""Test that the module has __getattr__ for lazy loading."""
|
||||
import vllm_omni.model_executor.models.glm_image as glm_image_pkg
|
||||
|
||||
# The module should have __getattr__
|
||||
assert hasattr(glm_image_pkg, "__getattr__")
|
||||
assert callable(glm_image_pkg.__getattr__)
|
||||
|
||||
# And __all__
|
||||
assert hasattr(glm_image_pkg, "__all__")
|
||||
|
||||
def test_module_has_proper_structure(self):
|
||||
"""Test that the module has proper Python module structure."""
|
||||
import vllm_omni.model_executor.models.glm_image as glm_image_pkg
|
||||
|
||||
# Should be a module
|
||||
assert hasattr(glm_image_pkg, "__name__")
|
||||
assert glm_image_pkg.__name__ == "vllm_omni.model_executor.models.glm_image"
|
||||
|
||||
# Should have __file__ (even if it's a package)
|
||||
assert hasattr(glm_image_pkg, "__file__")
|
||||
@@ -19,9 +19,8 @@ from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from vllm_omni.config.pipeline_registry import _OMNI_PIPELINES
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
||||
from vllm_omni.config.stage_config import (
|
||||
_PIPELINE_REGISTRY,
|
||||
PipelineConfig,
|
||||
StageExecutionType,
|
||||
)
|
||||
@@ -34,13 +33,13 @@ _PIPELINE_KEY = "minicpmo_4_5"
|
||||
|
||||
class TestRegistryDeclaration:
|
||||
def test_declared_in_omni_pipelines(self) -> None:
|
||||
assert _PIPELINE_KEY in _OMNI_PIPELINES
|
||||
assert _PIPELINE_KEY in OMNI_PIPELINES
|
||||
|
||||
def test_visible_in_central_registry(self) -> None:
|
||||
assert _PIPELINE_KEY in _PIPELINE_REGISTRY
|
||||
assert _PIPELINE_KEY in OMNI_PIPELINES
|
||||
|
||||
def test_lazy_load_returns_pipeline_config(self) -> None:
|
||||
pipeline = _PIPELINE_REGISTRY[_PIPELINE_KEY]
|
||||
pipeline = OMNI_PIPELINES[_PIPELINE_KEY]
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
assert pipeline.model_type == _PIPELINE_KEY
|
||||
assert pipeline.model_arch == "MiniCPMO45OmniForConditionalGeneration"
|
||||
@@ -49,7 +48,7 @@ class TestRegistryDeclaration:
|
||||
class TestPipelineTopology:
|
||||
@pytest.fixture(scope="class")
|
||||
def pipeline(self) -> PipelineConfig:
|
||||
return _PIPELINE_REGISTRY[_PIPELINE_KEY]
|
||||
return OMNI_PIPELINES[_PIPELINE_KEY]
|
||||
|
||||
def test_two_stages(self, pipeline: PipelineConfig) -> None:
|
||||
assert len(pipeline.stages) == 2
|
||||
@@ -99,7 +98,7 @@ class TestArchAliases:
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def pipeline(self) -> PipelineConfig:
|
||||
return _PIPELINE_REGISTRY[_PIPELINE_KEY]
|
||||
return OMNI_PIPELINES[_PIPELINE_KEY]
|
||||
|
||||
def test_shared_minicpmo_alias_present(self, pipeline: PipelineConfig) -> None:
|
||||
# MiniCPM-o 4.5 ships ``architectures=["MiniCPMO"]`` in its HF config.
|
||||
@@ -123,7 +122,7 @@ class TestHfConfigPredicate:
|
||||
|
||||
@pytest.fixture(scope="class")
|
||||
def predicate(self):
|
||||
pipeline = _PIPELINE_REGISTRY[_PIPELINE_KEY]
|
||||
pipeline = OMNI_PIPELINES[_PIPELINE_KEY]
|
||||
assert pipeline.hf_config_predicate is not None, (
|
||||
"MiniCPM-o 4.5 pipeline must declare hf_config_predicate to "
|
||||
"avoid misrouting MiniCPM-o 2.6 checkpoints into the 4.5 path."
|
||||
|
||||
+252
-73
@@ -12,14 +12,15 @@ from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from transformers import PretrainedConfig, Qwen3OmniMoeConfig
|
||||
|
||||
from tests.helpers.stage_config import get_deploy_config_path
|
||||
from vllm_omni.config.config_factory import StageConfigFactory
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES, register_pipeline
|
||||
from vllm_omni.config.stage_config import (
|
||||
_PIPELINE_REGISTRY,
|
||||
DeployConfig,
|
||||
PipelineConfig,
|
||||
StageConfig,
|
||||
StageConfigFactory,
|
||||
StageDeployConfig,
|
||||
StageExecutionType,
|
||||
StagePipelineConfig,
|
||||
@@ -30,14 +31,16 @@ from vllm_omni.config.stage_config import (
|
||||
build_stage_runtime_overrides,
|
||||
load_deploy_config,
|
||||
merge_pipeline_deploy,
|
||||
register_pipeline,
|
||||
pipeline_cfg_resolver,
|
||||
strip_parent_engine_args,
|
||||
)
|
||||
from vllm_omni.engine.arg_utils import SHARED_FIELDS, EngineArgs, internal_blacklist_keys
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||||
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG = Qwen3OmniMoeConfig(enable_audio_output=True)
|
||||
Q3_OMNI_THINKER_HF_CONFIG = Qwen3OmniMoeConfig(enable_audio_output=False)
|
||||
|
||||
|
||||
class TestStageType:
|
||||
@@ -511,26 +514,39 @@ class TestStageResolutionHelpers:
|
||||
|
||||
|
||||
class TestPipelineDiscovery:
|
||||
"""Tests for the central pipeline registry (``pipeline_registry._OMNI_PIPELINES``)."""
|
||||
"""Tests for the central pipeline registry (``OMNI_PIPELINES``)."""
|
||||
|
||||
def test_registry_has_known_models(self):
|
||||
"""Built-in pipelines are lazy-loaded from the central declaration
|
||||
on first access; no eager import or discovery walk needed."""
|
||||
# ``in`` triggers the lazy-map lookup without forcing a load.
|
||||
assert "qwen2_5_omni" in _PIPELINE_REGISTRY
|
||||
assert "qwen3_omni_moe" in _PIPELINE_REGISTRY
|
||||
assert "qwen3_tts" in _PIPELINE_REGISTRY
|
||||
"""Check that specific models are in OMNI_PIPELINES."""
|
||||
assert "qwen2_5_omni" in OMNI_PIPELINES
|
||||
assert "qwen3_omni_moe" in OMNI_PIPELINES
|
||||
assert "qwen3_tts" in OMNI_PIPELINES
|
||||
|
||||
def test_registry_loads_pipeline_on_getitem(self):
|
||||
"""Looking up a registered model_type returns the matching PipelineConfig."""
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
def test_registry_resolver_qwen3_omni_all_stages(self):
|
||||
"""Test that providing the HF config for qwen3 omni with audio enabled uses all stages."""
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
assert pipeline.model_type == "qwen3_omni_moe"
|
||||
assert len(pipeline.stages) == 3 # thinker + talker + code2wav
|
||||
|
||||
def test_registry_resolver_qwen3_omni_thinker_only(self):
|
||||
"""Test that providing the HF config for qwen3 omni without audio is thinker only."""
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_THINKER_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
assert pipeline.model_type == "qwen3_omni_moe_thinker_only"
|
||||
assert len(pipeline.stages) == 1 # thinker only
|
||||
|
||||
def test_registry_returns_none_for_unknown(self):
|
||||
"""Unknown model_types aren't found; ``get()`` returns None."""
|
||||
assert "definitely_not_a_real_model" not in _PIPELINE_REGISTRY
|
||||
assert _PIPELINE_REGISTRY.get("definitely_not_a_real_model") is None
|
||||
"""Unknown model_types aren't found and resolve to `None`."""
|
||||
assert "definitely_not_a_real_model" not in OMNI_PIPELINES
|
||||
assert OMNI_PIPELINES.get("definitely_not_a_real_model") is None
|
||||
assert StageConfigFactory.resolve_pipeline_config("definitely_not_a_real_model") is None
|
||||
|
||||
def test_pipeline_config_supports_hf_architectures(self):
|
||||
"""PipelineConfig accepts hf_architectures for HF-arch fallback
|
||||
@@ -579,6 +595,67 @@ class TestPipelineConfigNew:
|
||||
assert any("no stages" in e.lower() for e in p.validate())
|
||||
|
||||
|
||||
class TestPipelineRegistration:
|
||||
def test_pipeline_registration(self, clean_pipeline_registry):
|
||||
"""Ensure that we can register and create a custom pipeline config."""
|
||||
new_model_type = "new_model_type"
|
||||
pipe_cfg = PipelineConfig(model_type=new_model_type)
|
||||
|
||||
# Register the new PipelineConfig
|
||||
assert new_model_type not in OMNI_PIPELINES
|
||||
register_pipeline(pipe_cfg)
|
||||
assert new_model_type in OMNI_PIPELINES
|
||||
assert OMNI_PIPELINES[new_model_type] is pipe_cfg
|
||||
|
||||
class FakeConfig(PretrainedConfig):
|
||||
model_type = new_model_type
|
||||
|
||||
# Create the model
|
||||
with (
|
||||
patch("vllm_omni.config.config_factory.get_config", return_value=FakeConfig()),
|
||||
patch.object(StageConfigFactory, "_create_from_registry") as mock_create,
|
||||
):
|
||||
StageConfigFactory.create_from_model("fake/model")
|
||||
mock_create.assert_called_once()
|
||||
# Ensure that the passed pipeline_config is the right type
|
||||
pipeline_cfg = mock_create.call_args[0][1]
|
||||
assert isinstance(pipeline_cfg, PipelineConfig)
|
||||
assert pipe_cfg.model_type == new_model_type
|
||||
|
||||
def test_resolver_registration(self, clean_pipeline_registry):
|
||||
"""Ensure that we can register and create a custom resolver."""
|
||||
new_model_type = "new_model_type"
|
||||
resolved_type = "resolved_type"
|
||||
|
||||
class FakeConfig(PretrainedConfig):
|
||||
model_type = new_model_type
|
||||
|
||||
@pipeline_cfg_resolver(config_type=FakeConfig)
|
||||
def custom_resolver(
|
||||
hf_config: FakeConfig,
|
||||
) -> PipelineConfig:
|
||||
return PipelineConfig(model_type=resolved_type)
|
||||
|
||||
# Register the new PipelineConfig
|
||||
assert new_model_type not in OMNI_PIPELINES
|
||||
|
||||
register_pipeline(custom_resolver, model_type=new_model_type)
|
||||
assert new_model_type in OMNI_PIPELINES
|
||||
assert OMNI_PIPELINES[new_model_type] is custom_resolver
|
||||
|
||||
# Create the model
|
||||
with (
|
||||
patch("vllm_omni.config.config_factory.get_config", return_value=FakeConfig()),
|
||||
patch.object(StageConfigFactory, "_create_from_registry") as mock_create,
|
||||
):
|
||||
StageConfigFactory.create_from_model("fake/model")
|
||||
mock_create.assert_called_once()
|
||||
# Ensure that the passed pipeline_config is the right type
|
||||
pipeline_cfg = mock_create.call_args[0][1]
|
||||
assert isinstance(pipeline_cfg, PipelineConfig)
|
||||
assert pipeline_cfg.model_type == resolved_type
|
||||
|
||||
|
||||
class TestResolveScheduler:
|
||||
def test_all_execution_types_handled(self):
|
||||
for et in StageExecutionType:
|
||||
@@ -603,18 +680,6 @@ class TestResolveScheduler:
|
||||
assert _resolve_scheduler(StageExecutionType.DIFFUSION) is None
|
||||
|
||||
|
||||
class TestPipelineRegistry:
|
||||
def test_register_and_lookup(self):
|
||||
p = PipelineConfig(
|
||||
model_type="__test_only__",
|
||||
model_arch="A",
|
||||
stages=(StagePipelineConfig(stage_id=0, model_stage="a"),),
|
||||
)
|
||||
register_pipeline(p)
|
||||
assert _PIPELINE_REGISTRY["__test_only__"] is p
|
||||
del _PIPELINE_REGISTRY["__test_only__"]
|
||||
|
||||
|
||||
class TestDeployConfigLoading:
|
||||
def test_custom_voice_dir_is_pipeline_wide_engine_arg(self, tmp_path):
|
||||
deploy_path = tmp_path / "qwen3_tts_custom_voice.yaml"
|
||||
@@ -633,8 +698,9 @@ stages:
|
||||
)
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_tts"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("qwen3_tts")
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
|
||||
assert deploy.custom_voice_dir == str(custom_voice_dir)
|
||||
assert {s.yaml_engine_args.get("custom_voice_dir") for s in stages} == {str(custom_voice_dir)}
|
||||
@@ -664,11 +730,16 @@ stages:
|
||||
assert "compilation_config" not in deploy.stages[0].engine_extras
|
||||
|
||||
def test_merge_pipeline_deploy(self):
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
@@ -680,7 +751,12 @@ stages:
|
||||
assert s0.yaml_extras["default_sampling_params"]["detokenize"] is True
|
||||
|
||||
def test_merge_pipeline_deploy_preserves_num_replicas(self, tmp_path):
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
|
||||
base = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not base.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
@@ -894,14 +970,24 @@ stages:
|
||||
|
||||
class TestQwen3OmniPipeline:
|
||||
def test_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("qwen3_omni_moe")
|
||||
assert p is not None
|
||||
p = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(p, PipelineConfig)
|
||||
assert p.model_arch == "Qwen3OmniMoeForConditionalGeneration"
|
||||
assert len(p.stages) == 3
|
||||
assert p.validate() == []
|
||||
|
||||
def test_thinker(self):
|
||||
s = _PIPELINE_REGISTRY["qwen3_omni_moe"].get_stage(0)
|
||||
p = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(0)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
@@ -909,14 +995,28 @@ class TestQwen3OmniPipeline:
|
||||
assert s.sampling_constraints["detokenize"] is True
|
||||
|
||||
def test_talker(self):
|
||||
s = _PIPELINE_REGISTRY["qwen3_omni_moe"].get_stage(1)
|
||||
p = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(1)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.input_sources == (0,)
|
||||
assert s.sampling_constraints["stop_token_ids"] == [2150]
|
||||
assert s.custom_process_input_func is not None
|
||||
assert s.custom_process_next_stage_input_func is not None
|
||||
|
||||
def test_code2wav(self):
|
||||
s = _PIPELINE_REGISTRY["qwen3_omni_moe"].get_stage(2)
|
||||
p = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(2)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.final_output_type == "audio"
|
||||
assert s.custom_process_input_func is not None
|
||||
@@ -924,14 +1024,18 @@ class TestQwen3OmniPipeline:
|
||||
|
||||
class TestQwen2_5OmniPipeline:
|
||||
def test_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("qwen2_5_omni")
|
||||
assert p is not None
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen2_5_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
assert p.model_arch == "Qwen2_5OmniForConditionalGeneration"
|
||||
assert len(p.stages) == 3
|
||||
assert p.validate() == []
|
||||
|
||||
def test_thinker(self):
|
||||
s = _PIPELINE_REGISTRY["qwen2_5_omni"].get_stage(0)
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen2_5_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(0)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
@@ -939,13 +1043,21 @@ class TestQwen2_5OmniPipeline:
|
||||
assert s.requires_multimodal_data is True
|
||||
|
||||
def test_talker(self):
|
||||
s = _PIPELINE_REGISTRY["qwen2_5_omni"].get_stage(1)
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen2_5_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(1)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.input_sources == (0,)
|
||||
assert s.sampling_constraints["stop_token_ids"] == [8294]
|
||||
assert s.custom_process_input_func is not None
|
||||
|
||||
def test_code2wav(self):
|
||||
s = _PIPELINE_REGISTRY["qwen2_5_omni"].get_stage(2)
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen2_5_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(2)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.final_output_type == "audio"
|
||||
assert s.engine_output_type == "audio"
|
||||
@@ -953,14 +1065,19 @@ class TestQwen2_5OmniPipeline:
|
||||
|
||||
class TestQwen3TTSPipeline:
|
||||
def test_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("qwen3_tts")
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen3_tts")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
assert p is not None
|
||||
assert p.model_arch == "Qwen3TTSTalkerForConditionalGeneration"
|
||||
assert len(p.stages) == 2
|
||||
assert p.validate() == []
|
||||
|
||||
def test_talker_stage(self):
|
||||
s = _PIPELINE_REGISTRY["qwen3_tts"].get_stage(0)
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen3_tts")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(0)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "qwen3_tts"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
@@ -970,7 +1087,11 @@ class TestQwen3TTSPipeline:
|
||||
assert s.model_arch is None
|
||||
|
||||
def test_code2wav_stage_has_per_stage_model_arch(self):
|
||||
s = _PIPELINE_REGISTRY["qwen3_tts"].get_stage(1)
|
||||
p = StageConfigFactory.resolve_pipeline_config("qwen3_tts")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(1)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.final_output_type == "audio"
|
||||
assert s.engine_output_type == "audio"
|
||||
@@ -986,7 +1107,7 @@ class TestQwen3TTSPipeline:
|
||||
pytest.skip("qwen3_tts deploy yaml not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_tts"]
|
||||
pipeline = OMNI_PIPELINES["qwen3_tts"]
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
# Stage 0 inherits pipeline-level model_arch
|
||||
@@ -1025,14 +1146,18 @@ class TestQwen3TTSPipeline:
|
||||
|
||||
class TestMingFlashOmniPipeline:
|
||||
def test_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("ming_flash_omni")
|
||||
assert p is not None
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
||||
assert len(p.stages) == 2
|
||||
assert p.validate() == []
|
||||
|
||||
def test_thinker_stage(self):
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni"].get_stage(0)
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(0)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.owns_tokenizer is True
|
||||
@@ -1042,7 +1167,11 @@ class TestMingFlashOmniPipeline:
|
||||
assert s.sampling_constraints["detokenize"] is True
|
||||
|
||||
def test_talker_stage(self):
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni"].get_stage(1)
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(1)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "ming_tts"
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.input_sources == (0,)
|
||||
@@ -1060,20 +1189,28 @@ class TestMingFlashOmniPipeline:
|
||||
Lazy string references only fail at first inference otherwise — this
|
||||
catches typos in the pipeline declaration at import / registration time.
|
||||
"""
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni"].get_stage(1)
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(1)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
module_path, _, attr = s.custom_process_input_func.rpartition(".")
|
||||
module = importlib.import_module(module_path)
|
||||
assert callable(getattr(module, attr))
|
||||
|
||||
def test_tts_pipeline_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("ming_flash_omni_tts")
|
||||
assert p is not None
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_tts")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
assert p.model_arch == "MingFlashOmniTalkerForConditionalGeneration"
|
||||
assert len(p.stages) == 1
|
||||
assert p.validate() == []
|
||||
|
||||
def test_tts_stage(self):
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni_tts"].get_stage(0)
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_tts")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(0)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "ming_tts"
|
||||
assert s.execution_type == StageExecutionType.LLM_GENERATION
|
||||
assert s.input_sources == ()
|
||||
@@ -1096,7 +1233,8 @@ class TestMingFlashOmniPipeline:
|
||||
# We won't test stage 0/1 colocation contract here,
|
||||
# as there could exist more variant of custom device setup
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["ming_flash_omni"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("ming_flash_omni")
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
assert len(stages) == 2
|
||||
assert stages[0].yaml_engine_args["model_arch"] == "MingFlashOmniForConditionalGeneration"
|
||||
@@ -1112,20 +1250,24 @@ class TestMingFlashOmniPipeline:
|
||||
assert len(deploy.stages) == 1
|
||||
assert deploy.pipeline == "ming_flash_omni_tts"
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["ming_flash_omni_tts"]
|
||||
pipeline = OMNI_PIPELINES["ming_flash_omni_tts"]
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
assert len(stages) == 1
|
||||
assert stages[0].yaml_engine_args["model_arch"] == "MingFlashOmniTalkerForConditionalGeneration"
|
||||
|
||||
def test_thinker_only_pipeline_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("ming_flash_omni_thinker_only")
|
||||
assert p is not None
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_thinker_only")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
||||
assert len(p.stages) == 1
|
||||
assert p.validate() == []
|
||||
|
||||
def test_thinker_only_stage(self):
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni_thinker_only"].get_stage(0)
|
||||
p = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_thinker_only")
|
||||
assert isinstance(p, PipelineConfig)
|
||||
|
||||
s = p.get_stage(0)
|
||||
assert isinstance(s, StagePipelineConfig)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.input_sources == ()
|
||||
@@ -1146,20 +1288,21 @@ class TestMingFlashOmniPipeline:
|
||||
assert len(deploy.stages) == 1
|
||||
assert deploy.pipeline == "ming_flash_omni_thinker_only"
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["ming_flash_omni_thinker_only"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_thinker_only")
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
assert len(stages) == 1
|
||||
assert stages[0].yaml_engine_args["model_arch"] == "MingFlashOmniForConditionalGeneration"
|
||||
|
||||
def test_image_pipeline_registered(self):
|
||||
p = _PIPELINE_REGISTRY.get("ming_flash_omni_image")
|
||||
p = OMNI_PIPELINES.get("ming_flash_omni_image")
|
||||
assert p is not None
|
||||
assert p.model_arch == "MingFlashOmniForConditionalGeneration"
|
||||
assert len(p.stages) == 2
|
||||
assert p.validate() == []
|
||||
|
||||
def test_image_thinker_stage(self):
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni_image"].get_stage(0)
|
||||
s = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_image").get_stage(0)
|
||||
assert s.model_stage == "thinker"
|
||||
assert s.execution_type == StageExecutionType.LLM_AR
|
||||
assert s.input_sources == ()
|
||||
@@ -1173,7 +1316,7 @@ class TestMingFlashOmniPipeline:
|
||||
assert s.prompt_expand_func is not None
|
||||
|
||||
def test_image_dit_stage(self):
|
||||
s = _PIPELINE_REGISTRY["ming_flash_omni_image"].get_stage(1)
|
||||
s = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_image").get_stage(1)
|
||||
assert s.model_stage == "dit"
|
||||
assert s.execution_type == StageExecutionType.DIFFUSION
|
||||
assert s.input_sources == (0,)
|
||||
@@ -1185,8 +1328,11 @@ class TestMingFlashOmniPipeline:
|
||||
|
||||
def test_image_processor_wiring_resolves(self):
|
||||
"""The prompt_expand_func and custom_process_input_func strings must point to real callables."""
|
||||
thinker = _PIPELINE_REGISTRY["ming_flash_omni_image"].get_stage(0)
|
||||
dit = _PIPELINE_REGISTRY["ming_flash_omni_image"].get_stage(1)
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_image")
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
|
||||
thinker = pipeline.get_stage(0)
|
||||
dit = pipeline.get_stage(1)
|
||||
for ref in (thinker.prompt_expand_func, dit.custom_process_input_func):
|
||||
module_path, _, attr = ref.rpartition(".")
|
||||
module = importlib.import_module(module_path)
|
||||
@@ -1205,7 +1351,8 @@ class TestMingFlashOmniPipeline:
|
||||
assert deploy.connectors is not None
|
||||
assert "shared_memory_connector" in deploy.connectors
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["ming_flash_omni_image"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("ming_flash_omni_image")
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
assert len(stages) == 2
|
||||
# Stage 0 thinker: AR worker that emits latents.
|
||||
@@ -1356,12 +1503,16 @@ class TestCLIOverrideFlow:
|
||||
"""Test --stage-overrides JSON merge into StageConfig."""
|
||||
|
||||
def test_stage_overrides_merge(self):
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
# Simulate --stage-overrides '{"0": {"gpu_memory_utilization": 0.5}}'
|
||||
@@ -1370,12 +1521,16 @@ class TestCLIOverrideFlow:
|
||||
assert stages[0].runtime_overrides["gpu_memory_utilization"] == 0.5
|
||||
|
||||
def test_global_override_applies_to_all(self):
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
overrides = {"enforce_eager": True}
|
||||
@@ -1392,8 +1547,11 @@ class TestAuraOmniDeploy:
|
||||
assert deploy.pipeline == "aura_omni"
|
||||
|
||||
def test_aura_omni_deploy_resolves_four_native_stages(self):
|
||||
pipeline_cfg = StageConfigFactory.resolve_pipeline_config("aura_omni")
|
||||
|
||||
stages = StageConfigFactory._create_from_registry(
|
||||
"qwen3_tts",
|
||||
pipeline_cfg,
|
||||
cli_overrides={},
|
||||
deploy_config_path=str(Path(__file__).parent.parent / "vllm_omni" / "deploy" / "aura_omni.yaml"),
|
||||
)
|
||||
@@ -1417,8 +1575,14 @@ class TestSentinelDefaultPrecedence:
|
||||
to YAML / dataclass defaults (#3035)."""
|
||||
|
||||
def _stages(self, cli_overrides):
|
||||
model_type = "qwen3_omni_moe"
|
||||
pipeline_cfg = StageConfigFactory.resolve_pipeline_config(
|
||||
model_type,
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
return StageConfigFactory._create_from_registry(
|
||||
"qwen3_omni_moe",
|
||||
pipeline_cfg,
|
||||
cli_overrides=cli_overrides,
|
||||
)
|
||||
|
||||
@@ -1483,7 +1647,8 @@ class TestSentinelDefaultPrecedence:
|
||||
"""A single ``qwen3_tts`` pipeline picks per-chunk vs end-to-end
|
||||
processors based on ``deploy.async_chunk``, without needing a
|
||||
separate variant pipeline registration."""
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_tts"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("qwen3_tts")
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
|
||||
# async_chunk=True → stage 0's per-chunk processor wires up, stage 1
|
||||
# has no sync input processor.
|
||||
@@ -1552,9 +1717,8 @@ class TestSentinelDefaultPrecedence:
|
||||
``thinker2talker_token_only`` placeholder (sync_process_input_func).
|
||||
Merge under either async_chunk mode must not re-introduce a
|
||||
stage-0 full-payload hook."""
|
||||
from vllm_omni.config.stage_config import DeployConfig, merge_pipeline_deploy
|
||||
|
||||
pipeline = _PIPELINE_REGISTRY["ming_flash_omni"]
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config("ming_flash_omni")
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
|
||||
stage0, stage1 = pipeline.stages
|
||||
assert stage0.custom_process_next_stage_input_func is None, (
|
||||
@@ -1587,15 +1751,30 @@ class TestSamplingConstraintsPrecedence:
|
||||
"""Test that pipeline sampling_constraints override deploy defaults."""
|
||||
|
||||
def test_constraints_win(self):
|
||||
pipeline = _PIPELINE_REGISTRY["qwen3_omni_moe"]
|
||||
deploy_path = Path(__file__).parent.parent / "vllm_omni" / "deploy" / "qwen3_omni_moe.yaml"
|
||||
if not deploy_path.exists():
|
||||
pytest.skip("Deploy config not found")
|
||||
|
||||
deploy = load_deploy_config(deploy_path)
|
||||
pipeline = StageConfigFactory.resolve_pipeline_config(
|
||||
"qwen3_omni_moe",
|
||||
Q3_OMNI_ALL_STAGES_HF_CONFIG,
|
||||
)
|
||||
assert isinstance(pipeline, PipelineConfig)
|
||||
stages = merge_pipeline_deploy(pipeline, deploy)
|
||||
|
||||
# Pipeline says detokenize=True for thinker, deploy can't override
|
||||
assert stages[0].yaml_extras["default_sampling_params"]["detokenize"] is True
|
||||
# Pipeline says stop_token_ids=[2150] for talker
|
||||
assert stages[1].yaml_extras["default_sampling_params"]["stop_token_ids"] == [2150]
|
||||
|
||||
|
||||
class TestPipelineConfigResolvers:
|
||||
@pytest.mark.parametrize("resolver", [obj for obj in OMNI_PIPELINES.values() if callable(obj)])
|
||||
def test_all_resolvers_reject_bad_types(self, resolver):
|
||||
"""Ensure that all resolvers registered reject incorrect config types."""
|
||||
|
||||
class NotTheRightHfConfig(PretrainedConfig):
|
||||
pass
|
||||
|
||||
assert resolver(NotTheRightHfConfig()) is None
|
||||
@@ -10,7 +10,7 @@ from collections.abc import Mapping
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from vllm_omni.config.stage_config import StageConfigFactory
|
||||
from vllm_omni.config.config_factory import StageConfigFactory
|
||||
from vllm_omni.diffusion.data import (
|
||||
DiffusionParallelConfig,
|
||||
OmniDiffusionConfig,
|
||||
|
||||
@@ -19,7 +19,7 @@ import torch
|
||||
|
||||
class TestHiggsAudioV3Config:
|
||||
def test_default_config_loads(self):
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestHiggsAudioV3Config:
|
||||
assert config.tie_modality_embeddings is True
|
||||
|
||||
def test_custom_audio_encoder_config(self):
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
@@ -51,7 +51,7 @@ class TestHiggsAudioV3Config:
|
||||
assert config.tie_modality_embeddings is False
|
||||
|
||||
def test_invalid_num_codebooks_rejected(self):
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ class TestHiggsAudioV3Config:
|
||||
HiggsAudioV3Config(audio_encoder_config={"num_codebooks": 0, "vocab_size": 1026})
|
||||
|
||||
def test_negative_num_codebooks_rejected(self):
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ class TestHiggsAudioV3Config:
|
||||
HiggsAudioV3Config(audio_encoder_config={"num_codebooks": -1, "vocab_size": 1026})
|
||||
|
||||
def test_special_token_ids_initially_none(self):
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
@@ -77,7 +77,7 @@ class TestHiggsAudioV3Config:
|
||||
assert config.audio_continuation_id is None
|
||||
|
||||
def test_hidden_size_from_text_config(self):
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
@@ -797,12 +797,12 @@ class TestAudioFeedback:
|
||||
class TestCodecStrictness:
|
||||
def test_bundled_missing_quantizer_key_raises(self):
|
||||
"""Bundled codec load must fail when a quantizer codebook key is missing."""
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.higgs_audio_v3_code2wav import (
|
||||
HiggsAudioV3Code2Wav,
|
||||
)
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
config = HiggsAudioV3Config()
|
||||
c2w = HiggsAudioV3Code2Wav(config=config)
|
||||
@@ -821,12 +821,12 @@ class TestCodecStrictness:
|
||||
|
||||
def test_bundled_missing_fc2_raises(self):
|
||||
"""Bundled codec load must fail when fc2 keys are missing."""
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.higgs_audio_v3_code2wav import (
|
||||
HiggsAudioV3Code2Wav,
|
||||
)
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
config = HiggsAudioV3Config()
|
||||
c2w = HiggsAudioV3Code2Wav(config=config)
|
||||
@@ -844,12 +844,12 @@ class TestCodecStrictness:
|
||||
def test_bundled_unknown_decoder_key_rejected(self, monkeypatch):
|
||||
"""Unknown decoder-side key should be rejected as unconsumed."""
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3 import higgs_audio_v3_code2wav as c2w_mod
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.higgs_audio_v3_code2wav import (
|
||||
HiggsAudioV3Code2Wav,
|
||||
)
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
class FakeDAC(torch.nn.Module):
|
||||
def __init__(self):
|
||||
@@ -871,12 +871,12 @@ class TestCodecStrictness:
|
||||
def test_bundled_encoder_side_keys_accepted(self, monkeypatch):
|
||||
"""Known encoder-side keys should not trigger unconsumed-key rejection."""
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3 import higgs_audio_v3_code2wav as c2w_mod
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.higgs_audio_v3_code2wav import (
|
||||
HiggsAudioV3Code2Wav,
|
||||
)
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
class FakeDAC(torch.nn.Module):
|
||||
def __init__(self):
|
||||
@@ -1199,9 +1199,9 @@ class TestRegistry:
|
||||
assert "HiggsAudioV3Code2WavForConditionalGeneration" in _OMNI_MODELS
|
||||
|
||||
def test_pipeline_registered(self):
|
||||
from vllm_omni.config.pipeline_registry import _OMNI_PIPELINES
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
||||
|
||||
assert "higgs_multimodal_qwen3" in _OMNI_PIPELINES
|
||||
assert "higgs_multimodal_qwen3" in OMNI_PIPELINES
|
||||
|
||||
def test_deploy_yaml_exists(self):
|
||||
import os
|
||||
|
||||
@@ -8,11 +8,11 @@ import pytest
|
||||
import yaml
|
||||
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
||||
|
||||
from vllm_omni.config.config_factory import StageConfigFactory
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
||||
from vllm_omni.config.stage_config import (
|
||||
_PIPELINE_REGISTRY,
|
||||
DeployConfig,
|
||||
PipelineConfig,
|
||||
StageConfigFactory,
|
||||
StageDeployConfig,
|
||||
StagePipelineConfig,
|
||||
)
|
||||
@@ -44,9 +44,9 @@ _TEST_DEPLOY = DeployConfig(
|
||||
@pytest.fixture()
|
||||
def mock_stages(monkeypatch):
|
||||
"""Register a fake pipeline and mock deploy YAML loading."""
|
||||
monkeypatch.setitem(_PIPELINE_REGISTRY._loaded, _TEST_MODEL, _TEST_PIPELINE)
|
||||
monkeypatch.setitem(OMNI_PIPELINES, _TEST_MODEL, _TEST_PIPELINE)
|
||||
monkeypatch.setattr(
|
||||
"vllm_omni.config.stage_config.load_deploy_config",
|
||||
"vllm_omni.config.config_factory.load_deploy_config",
|
||||
lambda _path: _TEST_DEPLOY,
|
||||
)
|
||||
return __file__
|
||||
@@ -462,6 +462,7 @@ def test_explicit_cli_arg_reaches_runtime_overrides(mock_stages):
|
||||
explicit_kwargs = ns.get_explicit_kwargs_dict()
|
||||
stages = StageConfigFactory._create_from_registry(
|
||||
_TEST_MODEL,
|
||||
_TEST_PIPELINE,
|
||||
explicit_kwargs,
|
||||
deploy_config_path=mock_stages,
|
||||
)
|
||||
@@ -478,6 +479,7 @@ def test_omitted_default_not_in_runtime_overrides(mock_stages):
|
||||
explicit_kwargs = ns.get_explicit_kwargs_dict()
|
||||
stages = StageConfigFactory._create_from_registry(
|
||||
_TEST_MODEL,
|
||||
_TEST_PIPELINE,
|
||||
explicit_kwargs,
|
||||
deploy_config_path=mock_stages,
|
||||
)
|
||||
@@ -499,6 +501,7 @@ def test_config_file_args_reach_runtime_overrides(mock_stages):
|
||||
explicit_kwargs = ns.get_explicit_kwargs_dict()
|
||||
stages = StageConfigFactory._create_from_registry(
|
||||
_TEST_MODEL,
|
||||
_TEST_PIPELINE,
|
||||
explicit_kwargs,
|
||||
deploy_config_path=mock_stages,
|
||||
)
|
||||
@@ -516,6 +519,7 @@ def test_per_stage_override_routes_correctly(mock_stages):
|
||||
explicit_kwargs = ns.get_explicit_kwargs_dict()
|
||||
stages = StageConfigFactory._create_from_registry(
|
||||
_TEST_MODEL,
|
||||
_TEST_PIPELINE,
|
||||
explicit_kwargs,
|
||||
deploy_config_path=mock_stages,
|
||||
)
|
||||
@@ -537,6 +541,7 @@ def test_explicit_args_omitted_from_yaml(mock_stages):
|
||||
explicit_kwargs = ns.get_explicit_kwargs_dict()
|
||||
stages = StageConfigFactory._create_from_registry(
|
||||
_TEST_MODEL,
|
||||
_TEST_PIPELINE,
|
||||
explicit_kwargs,
|
||||
deploy_config_path=mock_stages,
|
||||
)
|
||||
|
||||
@@ -8,14 +8,12 @@ from vllm_omni.config.stage_config import (
|
||||
DeployConfig,
|
||||
PipelineConfig,
|
||||
StageConfig,
|
||||
StageConfigFactory,
|
||||
StageDeployConfig,
|
||||
StageExecutionType,
|
||||
StagePipelineConfig,
|
||||
StageType,
|
||||
load_deploy_config,
|
||||
merge_pipeline_deploy,
|
||||
register_pipeline,
|
||||
)
|
||||
from vllm_omni.config.yaml_util import (
|
||||
create_config,
|
||||
@@ -28,7 +26,6 @@ __all__ = [
|
||||
"OmniModelConfig",
|
||||
"LoRAConfig",
|
||||
"StageConfig",
|
||||
"StageConfigFactory",
|
||||
"StageType",
|
||||
"StageExecutionType",
|
||||
"StagePipelineConfig",
|
||||
@@ -37,7 +34,6 @@ __all__ = [
|
||||
"DeployConfig",
|
||||
"load_deploy_config",
|
||||
"merge_pipeline_deploy",
|
||||
"register_pipeline",
|
||||
"create_config",
|
||||
"load_yaml_config",
|
||||
"merge_configs",
|
||||
|
||||
@@ -0,0 +1,338 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Config factories for vllm-omni, e.g., StageConfigFactory."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from transformers import PreTrainedConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm.transformers_utils.config import get_config
|
||||
from vllm.transformers_utils.repo_utils import get_hf_file_to_dict
|
||||
|
||||
from vllm_omni.config.pipeline_registry import OMNI_PIPELINES
|
||||
from vllm_omni.config.stage_config import (
|
||||
_DEPLOY_DIR,
|
||||
DeployConfig,
|
||||
PipelineConfig,
|
||||
StageConfig,
|
||||
StageType,
|
||||
build_stage_runtime_overrides,
|
||||
load_deploy_config,
|
||||
merge_pipeline_deploy,
|
||||
)
|
||||
from vllm_omni.config.yaml_util import create_config
|
||||
from vllm_omni.diffusion.utils.hf_utils import _looks_like_dreamzero
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
|
||||
class StageConfigFactory:
|
||||
"""Factory that loads pipeline YAML and merges CLI overrides.
|
||||
|
||||
Handles both single-stage and multi-stage models.
|
||||
|
||||
Pipelines are declared in ``vllm_omni/config/pipeline_registry.py`` and
|
||||
where keys in OMNI_PIPELINES map to either a PipelineConfig, or a callable
|
||||
which accepts a Transformers config as an arg & resolves to a PipelineConfig.
|
||||
|
||||
NOTE: Models with generic HF ``model_type`` collisions (e.g. MiMo Audio
|
||||
reports ``qwen2``) should declare ``hf_architectures=(...)`` on their
|
||||
``PipelineConfig`` so the factory can disambiguate via ``hf_config.architectures``.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def create_from_model(
|
||||
cls,
|
||||
model: str,
|
||||
cli_overrides: dict[str, Any] | None = None,
|
||||
deploy_config_path: str | None = None,
|
||||
**deprecated_kwargs: Any,
|
||||
) -> list[StageConfig] | None:
|
||||
"""Load pipeline + deploy config, merge with CLI overrides.
|
||||
|
||||
Checks OMNI_PIPELINES first, since supported models should be explicitly
|
||||
registered. If a model is not registered in OMNI_PIPELINES, tries to fall
|
||||
back to using the Transformers config & finding pipelines that have overlapping
|
||||
supported architectures.
|
||||
"""
|
||||
if cli_overrides is None:
|
||||
cli_overrides = {}
|
||||
|
||||
trust_remote_code = cli_overrides.get("trust_remote_code", True)
|
||||
if trust_remote_code is None:
|
||||
trust_remote_code = False
|
||||
|
||||
# --- New path: check pipeline registry by model_type first ---
|
||||
model_type, hf_config = cls._auto_detect_model_type(model, trust_remote_code=trust_remote_code)
|
||||
if model_type == "vla":
|
||||
if _looks_like_dreamzero(model):
|
||||
model_type = "dreamzero"
|
||||
|
||||
if model_type and model_type in OMNI_PIPELINES:
|
||||
pipeline_cfg = cls.resolve_pipeline_config(model_type, hf_config)
|
||||
if pipeline_cfg is not None:
|
||||
return cls._create_from_registry(
|
||||
model_type,
|
||||
pipeline_cfg,
|
||||
cli_overrides,
|
||||
deploy_config_path,
|
||||
)
|
||||
|
||||
# --- HF architecture fallback: some models report a generic
|
||||
# model_type that collides with another model. Match by the
|
||||
# hf_architectures declared on each registered PipelineConfig.
|
||||
if hf_config is not None:
|
||||
logger.warning("Inferred model type %s is not registered to an Omni pipeline", model_type)
|
||||
hf_archs = set(getattr(hf_config, "architectures", []) or [])
|
||||
if hf_archs:
|
||||
for registered in OMNI_PIPELINES.values():
|
||||
pipeline_cfg = registered if isinstance(registered, PipelineConfig) else registered(hf_config)
|
||||
# Resolvers that get configs of the incorrect type should return None
|
||||
if pipeline_cfg is None:
|
||||
continue
|
||||
predicate = pipeline_cfg.hf_config_predicate
|
||||
if predicate is not None:
|
||||
try:
|
||||
if not predicate(hf_config):
|
||||
logger.debug(
|
||||
"Pipeline %r matched on architectures %s but its "
|
||||
"hf_config_predicate rejected the loaded config; "
|
||||
"continuing fallback search.",
|
||||
pipeline_cfg.model_type,
|
||||
sorted(hf_archs.intersection(pipeline_cfg.hf_architectures)),
|
||||
)
|
||||
continue
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Pipeline %r hf_config_predicate raised; skipping.",
|
||||
pipeline_cfg.model_type,
|
||||
)
|
||||
continue
|
||||
|
||||
if isinstance(pipeline_cfg, PipelineConfig) and hf_archs.intersection(
|
||||
pipeline_cfg.hf_architectures
|
||||
):
|
||||
return cls._create_from_registry(
|
||||
pipeline_cfg.model_type,
|
||||
pipeline_cfg,
|
||||
cli_overrides,
|
||||
deploy_config_path,
|
||||
)
|
||||
# Not in the pipeline registry — let the caller fall back to the
|
||||
# legacy ``stage_configs/*.yaml`` path (resolve_model_config_path).
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _create_from_registry(
|
||||
cls,
|
||||
model_type: str,
|
||||
pipeline_cfg: PipelineConfig,
|
||||
cli_overrides: dict[str, Any],
|
||||
deploy_config_path: str | None = None,
|
||||
**deprecated_kwargs: Any,
|
||||
) -> list[StageConfig]:
|
||||
"""Create StageConfigs from pipeline registry + deploy YAML.
|
||||
|
||||
Precedence: caller-typed (non-None) value > deploy YAML >
|
||||
StageDeployConfig dataclass default.
|
||||
"""
|
||||
# Resolve deploy config path
|
||||
if deploy_config_path is None:
|
||||
deploy_path = _DEPLOY_DIR / f"{model_type}.yaml"
|
||||
else:
|
||||
deploy_path = Path(deploy_config_path)
|
||||
|
||||
if not deploy_path.exists():
|
||||
logger.warning(
|
||||
"Deploy config not found: %s — using pipeline defaults only",
|
||||
deploy_path,
|
||||
)
|
||||
deploy_cfg = DeployConfig()
|
||||
else:
|
||||
deploy_cfg = load_deploy_config(deploy_path)
|
||||
# Fallback to using the deploy config pipeline class if it's a mismatch
|
||||
if deploy_cfg.pipeline and deploy_cfg.pipeline != model_type:
|
||||
resolved = cls.resolve_pipeline_config(deploy_cfg.pipeline)
|
||||
if resolved is None:
|
||||
raise KeyError(
|
||||
f"Pipeline {deploy_cfg.pipeline!r} from {deploy_path.name!r} "
|
||||
f"not found in OMNI_PIPELINES. Available: "
|
||||
f"{sorted(OMNI_PIPELINES.keys())}"
|
||||
)
|
||||
pipeline_cfg = resolved
|
||||
|
||||
cli_async_chunk = cli_overrides.get("async_chunk")
|
||||
if cli_async_chunk is not None:
|
||||
deploy_cfg.async_chunk = bool(cli_async_chunk)
|
||||
|
||||
stages = merge_pipeline_deploy(pipeline_cfg, deploy_cfg, cli_overrides)
|
||||
|
||||
explicit_overrides = {k: v for k, v in cli_overrides.items() if v is not None}
|
||||
|
||||
for stage in stages:
|
||||
stage.runtime_overrides = cls._merge_cli_overrides(stage, explicit_overrides)
|
||||
|
||||
return stages
|
||||
|
||||
@classmethod
|
||||
def create_default_diffusion(cls, kwargs: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Single-stage diffusion - no YAML needed.
|
||||
|
||||
Creates a default diffusion stage configuration for single-stage
|
||||
diffusion models. Returns a legacy OmegaConf-compatible dict for
|
||||
backward compatibility with OmniStage.
|
||||
|
||||
Args:
|
||||
kwargs: Engine arguments from CLI/API.
|
||||
|
||||
Returns:
|
||||
List containing a single config dict for the diffusion stage.
|
||||
"""
|
||||
# Calculate devices based on parallel config
|
||||
devices = "0"
|
||||
if "parallel_config" in kwargs:
|
||||
num_devices = kwargs["parallel_config"].world_size
|
||||
for i in range(1, num_devices):
|
||||
devices += f",{i}"
|
||||
|
||||
engine_args: dict[str, Any] = {}
|
||||
for key, value in kwargs.items():
|
||||
if key in ("parallel_config",):
|
||||
continue
|
||||
engine_args[key] = value
|
||||
|
||||
# Serialize parallel_config as dict for OmegaConf. Test helpers
|
||||
# sometimes pass SimpleNamespace rather than a dataclass instance.
|
||||
if "parallel_config" in kwargs:
|
||||
parallel_config = kwargs["parallel_config"]
|
||||
if dataclasses.is_dataclass(parallel_config) and not isinstance(parallel_config, type):
|
||||
engine_args["parallel_config"] = asdict(parallel_config)
|
||||
elif hasattr(parallel_config, "__dict__"):
|
||||
engine_args["parallel_config"] = dict(vars(parallel_config))
|
||||
else:
|
||||
engine_args["parallel_config"] = parallel_config
|
||||
|
||||
engine_args.setdefault("cache_backend", "none")
|
||||
engine_args["model_stage"] = "diffusion"
|
||||
|
||||
# Convert dtype to string for OmegaConf
|
||||
if "dtype" in engine_args:
|
||||
engine_args["dtype"] = str(engine_args["dtype"])
|
||||
|
||||
engine_args.setdefault("max_num_seqs", 1)
|
||||
|
||||
config_dict: dict[str, Any] = {
|
||||
"stage_id": 0,
|
||||
"stage_type": StageType.DIFFUSION.value,
|
||||
"runtime": {
|
||||
"process": True,
|
||||
"devices": devices,
|
||||
},
|
||||
"engine_args": create_config(engine_args),
|
||||
"final_output": True,
|
||||
"final_output_type": "image",
|
||||
}
|
||||
|
||||
return [config_dict]
|
||||
|
||||
@classmethod
|
||||
def _auto_detect_model_type(cls, model: str, trust_remote_code: bool = True) -> tuple[str | None, Any]:
|
||||
"""Auto-detect model_type from model directory.
|
||||
|
||||
Args:
|
||||
model: Model name or path.
|
||||
trust_remote_code: Whether to trust remote code for HF config loading.
|
||||
|
||||
Returns:
|
||||
Tuple of (model_type, hf_config). Both may be None on failure.
|
||||
"""
|
||||
hf_config = None
|
||||
|
||||
try:
|
||||
hf_config = get_config(model, trust_remote_code=trust_remote_code)
|
||||
return hf_config.model_type, hf_config
|
||||
except Exception as e:
|
||||
logger.debug(f"`get_config` failed for {e}; Falling back to raw config.json path")
|
||||
|
||||
# Fallback: read config.json directly for custom model types that
|
||||
# are not registered with transformers (e.g. qwen3_tts).
|
||||
try:
|
||||
config_dict = get_hf_file_to_dict("config.json", model, revision=None)
|
||||
if config_dict:
|
||||
if "model_type" in config_dict:
|
||||
return config_dict["model_type"], None
|
||||
# VoxCPM2-style configs use singular ``architecture`` rather
|
||||
# than HF's standard ``model_type`` / ``architectures``. Accept
|
||||
# it as a fallback so the pipeline registry can still match.
|
||||
if "architecture" in config_dict and isinstance(config_dict["architecture"], str):
|
||||
return config_dict["architecture"], None
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to auto-detect model type for {model}: {e}")
|
||||
|
||||
# Fallback for diffusers-style models: check model_index.json.
|
||||
# Some models (e.g. GLM-Image) have no root config.json but ship a
|
||||
# model_index.json with _class_name that maps to a pipeline key via
|
||||
# PipelineConfig.diffusers_class_name.
|
||||
try:
|
||||
model_index = get_hf_file_to_dict("model_index.json", model, revision=None)
|
||||
if model_index and "_class_name" in model_index:
|
||||
class_name = model_index["_class_name"]
|
||||
for obj in OMNI_PIPELINES.values():
|
||||
# If we have a resolver, call it with the optional hf_config
|
||||
# to get the default pipeline config for this key
|
||||
pipeline_cfg = obj(hf_config) if callable(obj) else obj
|
||||
if pipeline_cfg is not None and pipeline_cfg.diffusers_class_name == class_name:
|
||||
logger.info(
|
||||
"Detected pipeline %r from model_index.json (_class_name=%r)",
|
||||
pipeline_cfg.model_type,
|
||||
class_name,
|
||||
)
|
||||
return pipeline_cfg.model_type, None
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to detect model type for diffusers-style models: {e}")
|
||||
|
||||
# Final fallback: some models (e.g. CosyVoice3) ship an empty
|
||||
# config.json and rely on naming conventions. Match the model path
|
||||
# basename against registered pipeline keys — longest match wins
|
||||
# so "cosyvoice3" (length 10) beats "cosyvoice" (length 9).
|
||||
model_lower = model.lower().replace("-", "").replace("_", "")
|
||||
best: str | None = None
|
||||
best_len = 0
|
||||
for registered_key in OMNI_PIPELINES.keys():
|
||||
candidate = registered_key.lower().replace("-", "").replace("_", "")
|
||||
if candidate and candidate in model_lower and len(candidate) > best_len:
|
||||
best = registered_key
|
||||
best_len = len(candidate)
|
||||
if best is not None:
|
||||
return best, None
|
||||
|
||||
return None, None
|
||||
|
||||
@classmethod
|
||||
def _merge_cli_overrides(
|
||||
cls,
|
||||
stage: StageConfig,
|
||||
cli_overrides: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Merge global and per-stage (``stage_N_*``) CLI overrides.
|
||||
|
||||
Orchestrator-owned keys are filtered by ``build_stage_runtime_overrides``
|
||||
using ``OrchestratorArgs`` as the single source of truth; unknown
|
||||
server/uvicorn keys are dropped downstream by
|
||||
``filter_dataclass_kwargs(OmniEngineArgs, ...)``.
|
||||
"""
|
||||
return build_stage_runtime_overrides(stage.stage_id, cli_overrides)
|
||||
|
||||
@staticmethod
|
||||
def resolve_pipeline_config(model_type: str, hf_config: PreTrainedConfig | None = None) -> PipelineConfig | None:
|
||||
"""Given a model type, resolve to the pipeline to be used. If the pipeline
|
||||
maps to a callable we resolve based on the HF config."""
|
||||
if model_type not in OMNI_PIPELINES:
|
||||
return None
|
||||
obj = OMNI_PIPELINES[model_type]
|
||||
return obj(hf_config) if callable(obj) else obj
|
||||
@@ -1,176 +1,142 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Central declarative registry of all vllm-omni pipelines.
|
||||
"""Pipeline registry and factory for vllm-omni.
|
||||
|
||||
Mirrors the pattern in ``vllm/model_executor/models/registry.py``: each entry
|
||||
is ``model_type -> (module_path, variable_name)``, and the module is imported
|
||||
lazily on first lookup (see ``_LazyPipelineRegistry`` in
|
||||
``vllm_omni/config/stage_config.py``). Keeping every pipeline declared in one
|
||||
file makes it easy to spot a missing registration, which was the original
|
||||
motivation in https://github.com/vllm-project/vllm-omni/issues/2887 (item 4).
|
||||
``OMNI_PIPELINES`` maps each ``model_type`` to either a ``PipelineConfig``
|
||||
instance or a resolver callable that accepts an optional HF config and returns
|
||||
a ``PipelineConfig``.
|
||||
|
||||
Per-model ``pipeline.py`` modules still define the ``PipelineConfig`` instance;
|
||||
they just no longer need to self-register via ``register_pipeline(...)``.
|
||||
|
||||
Adding a new pipeline:
|
||||
To add a new pipeline:
|
||||
1. Define the ``PipelineConfig`` instance as a module-level variable in
|
||||
``vllm_omni/.../pipeline.py``.
|
||||
2. Add one line to ``_OMNI_PIPELINES`` below.
|
||||
2. If the model needs to support several configurations, e.g., because some
|
||||
stages are optional, implement a resolver that consumes the HF config
|
||||
and returns a ``PipelineConfig``.
|
||||
3. Update the registry to map the key to the new config object (in the case
|
||||
of new keys) or to the resolver func.
|
||||
|
||||
Plain single-stage diffusion models continue to use the
|
||||
``_create_default_diffusion_stage_cfg`` fallback in ``async_omni_engine.py``.
|
||||
The empty ``_DIFFUSION_PIPELINES`` placeholder previously here (#2915) was
|
||||
removed once #2987 (which would have populated it) was deferred.
|
||||
Out of tree pipeline configs or resolvers can also be registered with register_pipeline.
|
||||
|
||||
``register_pipeline(config)`` in ``stage_config`` is still supported for
|
||||
out-of-tree plugins and tests that create pipelines at runtime; those override
|
||||
the entries declared here.
|
||||
NOTE: Single-stage diffusion models continue to use the
|
||||
``_create_default_diffusion_stage_cfg`` fallback in
|
||||
``async_omni_engine.py``; for now we do not add them to registry.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import TypeAlias
|
||||
|
||||
from transformers import PreTrainedConfig
|
||||
from vllm.logger import init_logger
|
||||
|
||||
from vllm_omni.config.stage_config import (
|
||||
PipelineConfig,
|
||||
)
|
||||
from vllm_omni.model_executor.models.aura_omni.pipeline import AURA_OMNI_PIPELINE
|
||||
from vllm_omni.model_executor.models.bagel.pipeline import (
|
||||
BAGEL_PIPELINE,
|
||||
BAGEL_SINGLE_STAGE_PIPELINE,
|
||||
BAGEL_THINK_PIPELINE,
|
||||
)
|
||||
from vllm_omni.model_executor.models.cosyvoice3.pipeline import COSYVOICE3_PIPELINE
|
||||
from vllm_omni.model_executor.models.covo_audio.pipeline import COVO_AUDIO_PIPELINE
|
||||
from vllm_omni.model_executor.models.dreamzero.pipeline import DREAMZERO_PIPELINE
|
||||
from vllm_omni.model_executor.models.dynin_omni.pipeline import DYNIN_OMNI_PIPELINE
|
||||
from vllm_omni.model_executor.models.fish_speech.pipeline import FISH_SPEECH_PIPELINE
|
||||
from vllm_omni.model_executor.models.glm_image.pipeline import GLM_IMAGE_PIPELINE
|
||||
from vllm_omni.model_executor.models.glm_tts.pipeline import GLM_TTS_PIPELINE
|
||||
from vllm_omni.model_executor.models.gr00t.pipeline import GR00T_N1D7_PIPELINE
|
||||
from vllm_omni.model_executor.models.higgs_audio_v2.pipeline import HIGGS_AUDIO_V2_PIPELINE
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.pipeline import HIGGS_AUDIO_V3_PIPELINE
|
||||
from vllm_omni.model_executor.models.hunyuan_image3.pipeline import (
|
||||
HUNYUAN_IMAGE3_AR_PIPELINE,
|
||||
HUNYUAN_IMAGE3_DIT_PIPELINE,
|
||||
HUNYUAN_IMAGE3_PIPELINE,
|
||||
)
|
||||
from vllm_omni.model_executor.models.indextts2.pipeline import INDEXTTS2_PIPELINE
|
||||
from vllm_omni.model_executor.models.lance.pipeline import LANCE_PIPELINE
|
||||
from vllm_omni.model_executor.models.mimo_audio.pipeline import MIMO_AUDIO_PIPELINE
|
||||
from vllm_omni.model_executor.models.ming_flash_omni.pipeline import (
|
||||
MING_FLASH_OMNI_IMAGE_PIPELINE,
|
||||
MING_FLASH_OMNI_PIPELINE,
|
||||
MING_FLASH_OMNI_THINKER_ONLY_PIPELINE,
|
||||
MING_FLASH_OMNI_TTS_PIPELINE,
|
||||
)
|
||||
from vllm_omni.model_executor.models.ming_tts.pipeline import MING_TTS_PIPELINE
|
||||
from vllm_omni.model_executor.models.minicpmo_4_5.pipeline import MINICPMO_4_5_PIPELINE
|
||||
from vllm_omni.model_executor.models.moss_tts.pipeline import MOSS_TTS_PIPELINE, MOSS_TTS_REALTIME_PIPELINE
|
||||
from vllm_omni.model_executor.models.moss_tts_nano.pipeline import MOSS_TTS_NANO_PIPELINE
|
||||
from vllm_omni.model_executor.models.qwen2_5_omni.pipeline import (
|
||||
QWEN2_5_OMNI_PIPELINE,
|
||||
QWEN2_5_OMNI_THINKER_ONLY_PIPELINE,
|
||||
)
|
||||
from vllm_omni.model_executor.models.qwen3_omni.pipeline import resolve_qwen3_omni_pipeline
|
||||
from vllm_omni.model_executor.models.qwen3_tts.pipeline import QWEN3_TTS_PIPELINE
|
||||
from vllm_omni.model_executor.models.voxcpm2.pipeline import VOXCPM2_PIPELINE
|
||||
from vllm_omni.model_executor.models.voxtral_tts.pipeline import VOXTRAL_TTS_PIPELINE
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
PipelineResolverFunc: TypeAlias = Callable[[PreTrainedConfig | None], PipelineConfig | None]
|
||||
|
||||
# --- Multi-stage omni pipelines (LLM-centric; audio / video I/O) ---
|
||||
_OMNI_PIPELINES: dict[str, tuple[str, str]] = {
|
||||
# model_type -> (module_path, variable_name)
|
||||
"qwen2_5_omni": (
|
||||
"vllm_omni.model_executor.models.qwen2_5_omni.pipeline",
|
||||
"QWEN2_5_OMNI_PIPELINE",
|
||||
),
|
||||
"qwen2_5_omni_thinker_only": (
|
||||
"vllm_omni.model_executor.models.qwen2_5_omni.pipeline",
|
||||
"QWEN2_5_OMNI_THINKER_ONLY_PIPELINE",
|
||||
),
|
||||
"qwen3_omni_moe": (
|
||||
"vllm_omni.model_executor.models.qwen3_omni.pipeline",
|
||||
"QWEN3_OMNI_PIPELINE",
|
||||
),
|
||||
"aura_omni": (
|
||||
"vllm_omni.model_executor.models.aura_omni.pipeline",
|
||||
"AURA_OMNI_PIPELINE",
|
||||
),
|
||||
"qwen3_tts": (
|
||||
"vllm_omni.model_executor.models.qwen3_tts.pipeline",
|
||||
"QWEN3_TTS_PIPELINE",
|
||||
),
|
||||
"covo_audio": (
|
||||
"vllm_omni.model_executor.models.covo_audio.pipeline",
|
||||
"COVO_AUDIO_PIPELINE",
|
||||
),
|
||||
"bagel": (
|
||||
"vllm_omni.model_executor.models.bagel.pipeline",
|
||||
"BAGEL_PIPELINE",
|
||||
),
|
||||
"bagel_think": (
|
||||
"vllm_omni.model_executor.models.bagel.pipeline",
|
||||
"BAGEL_THINK_PIPELINE",
|
||||
),
|
||||
"bagel_single_stage": (
|
||||
"vllm_omni.model_executor.models.bagel.pipeline",
|
||||
"BAGEL_SINGLE_STAGE_PIPELINE",
|
||||
),
|
||||
# Lance (ByteDance) — BAGEL-lineage unified AR+diffusion, single-stage DiT.
|
||||
"lance": (
|
||||
"vllm_omni.model_executor.models.lance.pipeline",
|
||||
"LANCE_PIPELINE",
|
||||
),
|
||||
"dreamzero": (
|
||||
"vllm_omni.model_executor.models.dreamzero.pipeline",
|
||||
"DREAMZERO_PIPELINE",
|
||||
),
|
||||
"glm_image": (
|
||||
"vllm_omni.model_executor.models.glm_image.pipeline",
|
||||
"GLM_IMAGE_PIPELINE",
|
||||
),
|
||||
"Gr00tN1d7": (
|
||||
"vllm_omni.model_executor.models.gr00t.pipeline",
|
||||
"GR00T_N1D7_PIPELINE",
|
||||
),
|
||||
"hunyuan_image_3_moe": (
|
||||
"vllm_omni.model_executor.models.hunyuan_image3.pipeline",
|
||||
"HUNYUAN_IMAGE3_PIPELINE",
|
||||
),
|
||||
"hunyuan_image3_ar": (
|
||||
"vllm_omni.model_executor.models.hunyuan_image3.pipeline",
|
||||
"HUNYUAN_IMAGE3_AR_PIPELINE",
|
||||
),
|
||||
"hunyuan_image3_dit": (
|
||||
"vllm_omni.model_executor.models.hunyuan_image3.pipeline",
|
||||
"HUNYUAN_IMAGE3_DIT_PIPELINE",
|
||||
),
|
||||
"voxcpm2": (
|
||||
"vllm_omni.model_executor.models.voxcpm2.pipeline",
|
||||
"VOXCPM2_PIPELINE",
|
||||
),
|
||||
"cosyvoice3": (
|
||||
"vllm_omni.model_executor.models.cosyvoice3.pipeline",
|
||||
"COSYVOICE3_PIPELINE",
|
||||
),
|
||||
"mimo_audio": (
|
||||
"vllm_omni.model_executor.models.mimo_audio.pipeline",
|
||||
"MIMO_AUDIO_PIPELINE",
|
||||
),
|
||||
"ming_tts": (
|
||||
"vllm_omni.model_executor.models.ming_tts.pipeline",
|
||||
"MING_TTS_PIPELINE",
|
||||
),
|
||||
"voxtral_tts": (
|
||||
"vllm_omni.model_executor.models.voxtral_tts.pipeline",
|
||||
"VOXTRAL_TTS_PIPELINE",
|
||||
),
|
||||
"glm_tts": (
|
||||
"vllm_omni.model_executor.models.glm_tts.pipeline",
|
||||
"GLM_TTS_PIPELINE",
|
||||
),
|
||||
"fish_qwen3_omni": (
|
||||
"vllm_omni.model_executor.models.fish_speech.pipeline",
|
||||
"FISH_SPEECH_PIPELINE",
|
||||
),
|
||||
"ming_flash_omni": (
|
||||
"vllm_omni.model_executor.models.ming_flash_omni.pipeline",
|
||||
"MING_FLASH_OMNI_PIPELINE",
|
||||
),
|
||||
"ming_flash_omni_tts": (
|
||||
"vllm_omni.model_executor.models.ming_flash_omni.pipeline",
|
||||
"MING_FLASH_OMNI_TTS_PIPELINE",
|
||||
),
|
||||
"ming_flash_omni_thinker_only": (
|
||||
"vllm_omni.model_executor.models.ming_flash_omni.pipeline",
|
||||
"MING_FLASH_OMNI_THINKER_ONLY_PIPELINE",
|
||||
),
|
||||
"ming_flash_omni_image": (
|
||||
"vllm_omni.model_executor.models.ming_flash_omni.pipeline",
|
||||
"MING_FLASH_OMNI_IMAGE_PIPELINE",
|
||||
),
|
||||
"moss_tts_nano": (
|
||||
"vllm_omni.model_executor.models.moss_tts_nano.pipeline",
|
||||
"MOSS_TTS_NANO_PIPELINE",
|
||||
),
|
||||
"moss_tts_delay": (
|
||||
"vllm_omni.model_executor.models.moss_tts.pipeline",
|
||||
"MOSS_TTS_PIPELINE",
|
||||
),
|
||||
"moss_tts_realtime": (
|
||||
"vllm_omni.model_executor.models.moss_tts.pipeline",
|
||||
"MOSS_TTS_REALTIME_PIPELINE",
|
||||
),
|
||||
"minicpmo_4_5": (
|
||||
"vllm_omni.model_executor.models.minicpmo_4_5.pipeline",
|
||||
"MINICPMO_4_5_PIPELINE",
|
||||
),
|
||||
"higgs_audio_v2": (
|
||||
"vllm_omni.model_executor.models.higgs_audio_v2.pipeline",
|
||||
"HIGGS_AUDIO_V2_PIPELINE",
|
||||
),
|
||||
"higgs_multimodal_qwen3": (
|
||||
"vllm_omni.model_executor.models.higgs_audio_v3.pipeline",
|
||||
"HIGGS_AUDIO_V3_PIPELINE",
|
||||
),
|
||||
"dynin_omni": (
|
||||
"vllm_omni.model_executor.models.dynin_omni.pipeline",
|
||||
"DYNIN_OMNI_PIPELINE",
|
||||
),
|
||||
"indextts2": (
|
||||
"vllm_omni.model_executor.models.indextts2.pipeline",
|
||||
"INDEXTTS2_PIPELINE",
|
||||
),
|
||||
OMNI_PIPELINES: dict[str, PipelineConfig | PipelineResolverFunc] = {
|
||||
"aura_omni": AURA_OMNI_PIPELINE,
|
||||
"qwen2_5_omni": QWEN2_5_OMNI_PIPELINE,
|
||||
"qwen2_5_omni_thinker_only": QWEN2_5_OMNI_THINKER_ONLY_PIPELINE,
|
||||
"qwen3_omni_moe": resolve_qwen3_omni_pipeline,
|
||||
"qwen3_tts": QWEN3_TTS_PIPELINE,
|
||||
"covo_audio": COVO_AUDIO_PIPELINE,
|
||||
"bagel": BAGEL_PIPELINE,
|
||||
"bagel_think": BAGEL_THINK_PIPELINE,
|
||||
"bagel_single_stage": BAGEL_SINGLE_STAGE_PIPELINE,
|
||||
"lance": LANCE_PIPELINE,
|
||||
"dreamzero": DREAMZERO_PIPELINE,
|
||||
"Gr00tN1d7": GR00T_N1D7_PIPELINE,
|
||||
"glm_image": GLM_IMAGE_PIPELINE,
|
||||
"hunyuan_image_3_moe": HUNYUAN_IMAGE3_PIPELINE,
|
||||
"hunyuan_image3_ar": HUNYUAN_IMAGE3_AR_PIPELINE,
|
||||
"hunyuan_image3_dit": HUNYUAN_IMAGE3_DIT_PIPELINE,
|
||||
"voxcpm2": VOXCPM2_PIPELINE,
|
||||
"cosyvoice3": COSYVOICE3_PIPELINE,
|
||||
"mimo_audio": MIMO_AUDIO_PIPELINE,
|
||||
"ming_tts": MING_TTS_PIPELINE,
|
||||
"voxtral_tts": VOXTRAL_TTS_PIPELINE,
|
||||
"glm_tts": GLM_TTS_PIPELINE,
|
||||
"fish_qwen3_omni": FISH_SPEECH_PIPELINE,
|
||||
"ming_flash_omni": MING_FLASH_OMNI_PIPELINE,
|
||||
"ming_flash_omni_tts": MING_FLASH_OMNI_TTS_PIPELINE,
|
||||
"ming_flash_omni_thinker_only": MING_FLASH_OMNI_THINKER_ONLY_PIPELINE,
|
||||
"ming_flash_omni_image": MING_FLASH_OMNI_IMAGE_PIPELINE,
|
||||
"moss_tts_nano": MOSS_TTS_NANO_PIPELINE,
|
||||
"moss_tts_delay": MOSS_TTS_PIPELINE,
|
||||
"moss_tts_realtime": MOSS_TTS_REALTIME_PIPELINE,
|
||||
"minicpmo_4_5": MINICPMO_4_5_PIPELINE,
|
||||
"higgs_audio_v2": HIGGS_AUDIO_V2_PIPELINE,
|
||||
"higgs_multimodal_qwen3": HIGGS_AUDIO_V3_PIPELINE,
|
||||
"dynin_omni": DYNIN_OMNI_PIPELINE,
|
||||
"indextts2": INDEXTTS2_PIPELINE,
|
||||
}
|
||||
|
||||
|
||||
def register_pipeline(pipeline: PipelineConfig | PipelineResolverFunc, model_type: str | None = None):
|
||||
"""Register an out of tree pipeline or PipelineResolverFunc to a model_type key.
|
||||
If a PipelineConfig is provided, model_type is optional, and pipeline.model_type
|
||||
will be used by default. If a callable is provided, model_type must be provided,
|
||||
since resolvers can return multiple different PipelineConfigs depending on the
|
||||
consumed config.
|
||||
"""
|
||||
errors: list[str] = []
|
||||
if isinstance(pipeline, PipelineConfig):
|
||||
errors = pipeline.validate()
|
||||
model_type = model_type if model_type is not None else pipeline.model_type
|
||||
else:
|
||||
if model_type is None:
|
||||
raise ValueError("Model type must be explicitly provided when registering a pipeline resolver")
|
||||
|
||||
if model_type in OMNI_PIPELINES:
|
||||
errors.append(f"Model type {model_type} is already registered; the old mapping will be clobbered")
|
||||
if errors:
|
||||
logger.warning("Registration for pipeline of type %s produced the following issues: %s", model_type, errors)
|
||||
OMNI_PIPELINES[model_type] = pipeline
|
||||
@@ -5,6 +5,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import functools
|
||||
import re
|
||||
import warnings
|
||||
from collections.abc import Callable
|
||||
@@ -13,6 +14,7 @@ from enum import Enum
|
||||
from pathlib import Path
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from transformers import PretrainedConfig
|
||||
from vllm.logger import init_logger
|
||||
from vllm.v1.core.sched.scheduler import Scheduler as VLLMScheduler
|
||||
|
||||
@@ -22,10 +24,26 @@ from vllm_omni.core.sched.omni_generation_scheduler import OmniGenerationSchedul
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
_DEPLOY_DIR = Path(__file__).resolve().parent.parent / "deploy"
|
||||
|
||||
_STAGE_OVERRIDE_PATTERN = re.compile(r"^stage_(\d+)_(.+)$")
|
||||
|
||||
|
||||
def pipeline_cfg_resolver(config_type: type[PretrainedConfig]):
|
||||
"""Wraps a resolver such that we return None if a hf_config of the wrong type is provided."""
|
||||
|
||||
def resolver_builder(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(hf_config: PretrainedConfig | None):
|
||||
if hf_config is None or not isinstance(hf_config, config_type):
|
||||
return None
|
||||
return func(hf_config)
|
||||
|
||||
return wrapper
|
||||
|
||||
return resolver_builder
|
||||
|
||||
|
||||
def build_stage_runtime_overrides(
|
||||
stage_id: int,
|
||||
cli_overrides: dict[str, Any],
|
||||
@@ -273,153 +291,6 @@ class PipelineConfig:
|
||||
return errors
|
||||
|
||||
|
||||
class _LazyPipelineRegistry:
|
||||
"""Dict-like registry that lazy-loads pipelines from the central declaration.
|
||||
|
||||
In-tree pipelines are declared once in
|
||||
``vllm_omni/config/pipeline_registry.py`` as
|
||||
``model_type -> (module_path, variable_name)`` entries; the module is
|
||||
imported only when the pipeline is first looked up. This mirrors the
|
||||
pattern in ``vllm/model_executor/models/registry.py`` and addresses
|
||||
https://github.com/vllm-project/vllm-omni/issues/2887 (item 4): having
|
||||
every registration in one file makes a missing entry easy to spot.
|
||||
|
||||
Out-of-tree / dynamic registrations via ``register_pipeline()`` are stored
|
||||
directly in ``_loaded`` and take precedence over the lazy-map entry with
|
||||
the same ``model_type``.
|
||||
|
||||
The class exposes the subset of ``dict`` operations the rest of this
|
||||
module relies on (``__contains__``, ``__getitem__``, ``__setitem__``,
|
||||
``get``, ``keys``, ``values``, ``items``, ``__iter__``), so existing call
|
||||
sites don't need to change.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._loaded: dict[str, PipelineConfig] = {}
|
||||
# Populated lazily to avoid a circular import at module init time.
|
||||
self._lazy_map: dict[str, tuple[str, str]] | None = None
|
||||
|
||||
def _get_lazy_map(self) -> dict[str, tuple[str, str]]:
|
||||
if self._lazy_map is None:
|
||||
from vllm_omni.config.pipeline_registry import _OMNI_PIPELINES
|
||||
|
||||
self._lazy_map = _OMNI_PIPELINES
|
||||
return self._lazy_map
|
||||
|
||||
def _load_lazy(self, model_type: str) -> PipelineConfig | None:
|
||||
entry = self._get_lazy_map().get(model_type)
|
||||
if entry is None:
|
||||
return None
|
||||
module_path, var_name = entry
|
||||
import importlib
|
||||
|
||||
try:
|
||||
module = importlib.import_module(module_path)
|
||||
pipeline = getattr(module, var_name, None)
|
||||
if pipeline is None:
|
||||
logger.error(
|
||||
"Pipeline variable %r not found in module %r (registered for %r)",
|
||||
var_name,
|
||||
module_path,
|
||||
model_type,
|
||||
)
|
||||
return None
|
||||
errors = pipeline.validate()
|
||||
if errors:
|
||||
logger.warning("Pipeline %s has issues: %s", pipeline.model_type, errors)
|
||||
self._loaded[model_type] = pipeline
|
||||
return pipeline
|
||||
except Exception:
|
||||
logger.exception("Failed to import pipeline module %r for %r", module_path, model_type)
|
||||
return None
|
||||
|
||||
def __contains__(self, model_type: str) -> bool:
|
||||
if model_type in self._loaded:
|
||||
return True
|
||||
return model_type in self._get_lazy_map()
|
||||
|
||||
def __getitem__(self, model_type: str) -> PipelineConfig:
|
||||
if model_type in self._loaded:
|
||||
return self._loaded[model_type]
|
||||
pipeline = self._load_lazy(model_type)
|
||||
if pipeline is None:
|
||||
raise KeyError(model_type)
|
||||
return pipeline
|
||||
|
||||
def get(self, model_type: str, default: PipelineConfig | None = None) -> PipelineConfig | None:
|
||||
if model_type in self._loaded:
|
||||
return self._loaded[model_type]
|
||||
pipeline = self._load_lazy(model_type)
|
||||
return pipeline if pipeline is not None else default
|
||||
|
||||
def __setitem__(self, model_type: str, pipeline: PipelineConfig) -> None:
|
||||
self._loaded[model_type] = pipeline
|
||||
|
||||
def __delitem__(self, model_type: str) -> None:
|
||||
"""Remove a dynamically-registered pipeline.
|
||||
|
||||
Only the dynamic-cache side of the registry can be mutated; the
|
||||
central declarative registry is immutable at runtime. Calling ``del``
|
||||
on a model_type that only exists in the central registry raises
|
||||
``KeyError``.
|
||||
"""
|
||||
if model_type in self._loaded:
|
||||
del self._loaded[model_type]
|
||||
return
|
||||
if model_type in self._get_lazy_map():
|
||||
raise KeyError(
|
||||
f"{model_type!r} is declared in the central pipeline_registry and "
|
||||
"cannot be removed at runtime. Edit "
|
||||
"vllm_omni/config/pipeline_registry.py to delete a built-in entry."
|
||||
)
|
||||
raise KeyError(model_type)
|
||||
|
||||
def keys(self) -> set[str]:
|
||||
return set(self._get_lazy_map().keys()) | set(self._loaded.keys())
|
||||
|
||||
def _safe_get(self, key: str) -> PipelineConfig | None:
|
||||
try:
|
||||
return self[key]
|
||||
except Exception:
|
||||
logger.warning("Skipping pipeline %r because it failed to load.", key)
|
||||
return None
|
||||
|
||||
def values(self):
|
||||
# Iterating forces a lazy import for each pipeline; failures are logged and skipped.
|
||||
for key in self.keys():
|
||||
if (p := self._safe_get(key)) is not None:
|
||||
yield p
|
||||
|
||||
def items(self):
|
||||
for key in self.keys():
|
||||
if (p := self._safe_get(key)) is not None:
|
||||
yield key, p
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.keys())
|
||||
|
||||
|
||||
_PIPELINE_REGISTRY = _LazyPipelineRegistry()
|
||||
|
||||
|
||||
def register_pipeline(pipeline: PipelineConfig) -> None:
|
||||
"""Register a pipeline config dynamically.
|
||||
|
||||
In-tree pipelines are declared in ``pipeline_registry._OMNI_PIPELINES``
|
||||
and loaded lazily; calling ``register_pipeline`` is only needed for
|
||||
out-of-tree plugins or tests that build a ``PipelineConfig`` at runtime.
|
||||
A dynamic registration overrides the central-registry entry with the same
|
||||
``model_type``.
|
||||
"""
|
||||
errors = pipeline.validate()
|
||||
if errors:
|
||||
logger.warning("Pipeline %s has issues: %s", pipeline.model_type, errors)
|
||||
_PIPELINE_REGISTRY[pipeline.model_type] = pipeline
|
||||
|
||||
|
||||
_DEPLOY_DIR = Path(__file__).resolve().parent.parent / "deploy"
|
||||
|
||||
|
||||
@dataclass
|
||||
class StageDeployConfig:
|
||||
"""Per-stage deployment knobs.
|
||||
@@ -905,6 +776,10 @@ def merge_pipeline_deploy(
|
||||
deploy = _apply_platform_overrides(deploy)
|
||||
deploy_by_id = {s.stage_id: s for s in deploy.stages}
|
||||
|
||||
# async_chunk is irrelevant for single-stage pipelines, so we always disable it
|
||||
if len(pipeline.stages) <= 1:
|
||||
deploy.async_chunk = False
|
||||
|
||||
# A pipeline supports async_chunk if any stage has either an explicit
|
||||
# async-chunk-only processor slot OR a custom next-stage processor (some
|
||||
# pipelines like qwen3_omni wire async-chunk processing directly through
|
||||
@@ -1052,305 +927,3 @@ class StageConfig:
|
||||
config_dict.update(self.yaml_extras)
|
||||
|
||||
return create_config(config_dict)
|
||||
|
||||
|
||||
class StageConfigFactory:
|
||||
"""Factory that loads pipeline YAML and merges CLI overrides.
|
||||
|
||||
Handles both single-stage and multi-stage models.
|
||||
|
||||
Pipelines are declared in ``vllm_omni/config/pipeline_registry.py`` and
|
||||
loaded lazily via ``_PIPELINE_REGISTRY``; no hardcoded model-type →
|
||||
directory mapping is maintained here. Models with generic HF
|
||||
``model_type`` collisions (e.g. MiMo Audio reports ``qwen2``) should
|
||||
declare ``hf_architectures=(...)`` on their ``PipelineConfig`` so the
|
||||
factory can disambiguate via ``hf_config.architectures``.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def create_from_model(
|
||||
cls,
|
||||
model: str,
|
||||
cli_overrides: dict[str, Any] | None = None,
|
||||
deploy_config_path: str | None = None,
|
||||
) -> list[StageConfig] | None:
|
||||
"""Load pipeline + deploy config, merge with CLI overrides.
|
||||
|
||||
Checks _PIPELINE_REGISTRY first (new path), falls back to legacy YAML.
|
||||
"""
|
||||
if cli_overrides is None:
|
||||
cli_overrides = {}
|
||||
|
||||
trust_remote_code = cli_overrides.get("trust_remote_code", True)
|
||||
if trust_remote_code is None:
|
||||
trust_remote_code = False
|
||||
|
||||
# --- New path: check pipeline registry by model_type first ---
|
||||
model_type, hf_config = cls._auto_detect_model_type(model, trust_remote_code=trust_remote_code)
|
||||
if model_type == "vla":
|
||||
from vllm_omni.diffusion.utils.hf_utils import _looks_like_dreamzero
|
||||
|
||||
if _looks_like_dreamzero(model):
|
||||
model_type = "dreamzero"
|
||||
if model_type and model_type in _PIPELINE_REGISTRY:
|
||||
return cls._create_from_registry(model_type, cli_overrides, deploy_config_path)
|
||||
|
||||
# --- HF architecture fallback: some models report a generic
|
||||
# model_type that collides with another model. Match by the
|
||||
# hf_architectures declared on each registered PipelineConfig.
|
||||
# When a pipeline declares ``hf_config_predicate`` (e.g. to
|
||||
# disambiguate generation-revision collisions like MiniCPM-o 2.6 vs
|
||||
# 4.5 which share ``architectures=["MiniCPMO"]``), the predicate
|
||||
# must also accept the loaded ``hf_config``.
|
||||
if hf_config is not None:
|
||||
hf_archs = set(getattr(hf_config, "architectures", []) or [])
|
||||
if hf_archs:
|
||||
for registered in _PIPELINE_REGISTRY.values():
|
||||
if not hf_archs.intersection(registered.hf_architectures):
|
||||
continue
|
||||
predicate = registered.hf_config_predicate
|
||||
if predicate is not None:
|
||||
try:
|
||||
if not predicate(hf_config):
|
||||
logger.debug(
|
||||
"Pipeline %r matched on architectures %s but its "
|
||||
"hf_config_predicate rejected the loaded config; "
|
||||
"continuing fallback search.",
|
||||
registered.model_type,
|
||||
sorted(hf_archs.intersection(registered.hf_architectures)),
|
||||
)
|
||||
continue
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Pipeline %r hf_config_predicate raised; skipping.",
|
||||
registered.model_type,
|
||||
)
|
||||
continue
|
||||
return cls._create_from_registry(registered.model_type, cli_overrides, deploy_config_path)
|
||||
|
||||
# An explicit deploy config can select a pipeline when the model's HF
|
||||
# metadata is too generic for registry auto-detection.
|
||||
if deploy_config_path is not None:
|
||||
deploy_path = Path(deploy_config_path)
|
||||
if deploy_path.exists():
|
||||
deploy_config = load_deploy_config(deploy_path)
|
||||
if deploy_config.pipeline in _PIPELINE_REGISTRY:
|
||||
return cls._create_from_registry(
|
||||
deploy_config.pipeline,
|
||||
cli_overrides,
|
||||
deploy_config_path,
|
||||
)
|
||||
|
||||
# Not in the pipeline registry — let the caller fall back to the
|
||||
# legacy ``stage_configs/*.yaml`` path (resolve_model_config_path).
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def _create_from_registry(
|
||||
cls,
|
||||
model_type: str,
|
||||
cli_overrides: dict[str, Any],
|
||||
deploy_config_path: str | None = None,
|
||||
) -> list[StageConfig]:
|
||||
"""Create StageConfigs from pipeline registry + deploy YAML.
|
||||
|
||||
Precedence: caller-typed (non-None) value > deploy YAML >
|
||||
StageDeployConfig dataclass default.
|
||||
"""
|
||||
# Resolve deploy config path
|
||||
if deploy_config_path is None:
|
||||
deploy_path = _DEPLOY_DIR / f"{model_type}.yaml"
|
||||
else:
|
||||
deploy_path = Path(deploy_config_path)
|
||||
# Bare-name lookup: if the value isn't a real path, try the
|
||||
# built-in deploy directory. This lets ``deploy_config="moss_tts"``
|
||||
# resolve to ``vllm_omni/deploy/moss_tts.yaml`` when several model
|
||||
# variants share an HF ``model_type`` and need different YAMLs.
|
||||
if not deploy_path.exists() and deploy_path.parent == Path("."):
|
||||
bare_name = deploy_path.name
|
||||
if not bare_name.endswith(".yaml"):
|
||||
bare_name = f"{bare_name}.yaml"
|
||||
candidate = _DEPLOY_DIR / bare_name
|
||||
if candidate.exists():
|
||||
deploy_path = candidate
|
||||
|
||||
if not deploy_path.exists():
|
||||
logger.warning(
|
||||
"Deploy config not found: %s — using pipeline defaults only",
|
||||
deploy_path,
|
||||
)
|
||||
deploy_cfg = DeployConfig()
|
||||
else:
|
||||
deploy_cfg = load_deploy_config(deploy_path)
|
||||
|
||||
cli_async_chunk = cli_overrides.get("async_chunk")
|
||||
if cli_async_chunk is not None:
|
||||
deploy_cfg.async_chunk = bool(cli_async_chunk)
|
||||
|
||||
pipeline_key = deploy_cfg.pipeline or model_type
|
||||
if pipeline_key not in _PIPELINE_REGISTRY:
|
||||
raise KeyError(
|
||||
f"Pipeline {pipeline_key!r} not in registry "
|
||||
f"(resolved from {deploy_path.name!r}). Available: "
|
||||
f"{sorted(_PIPELINE_REGISTRY.keys())}"
|
||||
)
|
||||
pipeline_cfg = _PIPELINE_REGISTRY[pipeline_key]
|
||||
|
||||
stages = merge_pipeline_deploy(pipeline_cfg, deploy_cfg, cli_overrides)
|
||||
|
||||
explicit_overrides = {k: v for k, v in cli_overrides.items() if v is not None}
|
||||
|
||||
for stage in stages:
|
||||
stage.runtime_overrides = cls._merge_cli_overrides(stage, explicit_overrides)
|
||||
|
||||
return stages
|
||||
|
||||
@classmethod
|
||||
def create_default_diffusion(cls, kwargs: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"""Single-stage diffusion - no YAML needed.
|
||||
|
||||
Creates a default diffusion stage configuration for single-stage
|
||||
diffusion models. Returns a legacy OmegaConf-compatible dict for
|
||||
backward compatibility with OmniStage.
|
||||
|
||||
Args:
|
||||
kwargs: Engine arguments from CLI/API.
|
||||
|
||||
Returns:
|
||||
List containing a single config dict for the diffusion stage.
|
||||
"""
|
||||
# Calculate devices based on parallel config
|
||||
devices = "0"
|
||||
if "parallel_config" in kwargs:
|
||||
num_devices = kwargs["parallel_config"].world_size
|
||||
for i in range(1, num_devices):
|
||||
devices += f",{i}"
|
||||
|
||||
engine_args: dict[str, Any] = {}
|
||||
for key, value in kwargs.items():
|
||||
if key in ("parallel_config",):
|
||||
continue
|
||||
engine_args[key] = value
|
||||
|
||||
# Serialize parallel_config as dict for OmegaConf. Test helpers
|
||||
# sometimes pass SimpleNamespace rather than a dataclass instance.
|
||||
if "parallel_config" in kwargs:
|
||||
parallel_config = kwargs["parallel_config"]
|
||||
if dataclasses.is_dataclass(parallel_config) and not isinstance(parallel_config, type):
|
||||
engine_args["parallel_config"] = asdict(parallel_config)
|
||||
elif hasattr(parallel_config, "__dict__"):
|
||||
engine_args["parallel_config"] = dict(vars(parallel_config))
|
||||
else:
|
||||
engine_args["parallel_config"] = parallel_config
|
||||
|
||||
engine_args.setdefault("cache_backend", "none")
|
||||
engine_args["model_stage"] = "diffusion"
|
||||
|
||||
# Convert dtype to string for OmegaConf
|
||||
if "dtype" in engine_args:
|
||||
engine_args["dtype"] = str(engine_args["dtype"])
|
||||
|
||||
engine_args.setdefault("max_num_seqs", 1)
|
||||
|
||||
config_dict: dict[str, Any] = {
|
||||
"stage_id": 0,
|
||||
"stage_type": StageType.DIFFUSION.value,
|
||||
"runtime": {
|
||||
"process": True,
|
||||
"devices": devices,
|
||||
},
|
||||
"engine_args": create_config(engine_args),
|
||||
"final_output": True,
|
||||
"final_output_type": "image",
|
||||
}
|
||||
|
||||
return [config_dict]
|
||||
|
||||
@classmethod
|
||||
def _auto_detect_model_type(cls, model: str, trust_remote_code: bool = True) -> tuple[str | None, Any]:
|
||||
"""Auto-detect model_type from model directory.
|
||||
|
||||
Args:
|
||||
model: Model name or path.
|
||||
trust_remote_code: Whether to trust remote code for HF config loading.
|
||||
|
||||
Returns:
|
||||
Tuple of (model_type, hf_config). Both may be None on failure.
|
||||
"""
|
||||
try:
|
||||
from vllm.transformers_utils.config import get_config
|
||||
|
||||
hf_config = get_config(model, trust_remote_code=trust_remote_code)
|
||||
return hf_config.model_type, hf_config
|
||||
except Exception as e:
|
||||
logger.debug(f"`get_config` failed for {e}; Falling back to raw config.json path")
|
||||
|
||||
# Fallback: read config.json directly for custom model types that
|
||||
# are not registered with transformers (e.g. qwen3_tts).
|
||||
try:
|
||||
from vllm.transformers_utils.config import get_hf_file_to_dict
|
||||
|
||||
config_dict = get_hf_file_to_dict("config.json", model, revision=None)
|
||||
if config_dict:
|
||||
if "model_type" in config_dict:
|
||||
return config_dict["model_type"], None
|
||||
# VoxCPM2-style configs use singular ``architecture`` rather
|
||||
# than HF's standard ``model_type`` / ``architectures``. Accept
|
||||
# it as a fallback so the pipeline registry can still match.
|
||||
if "architecture" in config_dict and isinstance(config_dict["architecture"], str):
|
||||
return config_dict["architecture"], None
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to auto-detect model type for {model}: {e}")
|
||||
|
||||
# Fallback for diffusers-style models: check model_index.json.
|
||||
# Some models (e.g. GLM-Image) have no root config.json but ship a
|
||||
# model_index.json with _class_name that maps to a pipeline key via
|
||||
# PipelineConfig.diffusers_class_name.
|
||||
try:
|
||||
from vllm.transformers_utils.config import get_hf_file_to_dict
|
||||
|
||||
model_index = get_hf_file_to_dict("model_index.json", model, revision=None)
|
||||
if model_index and "_class_name" in model_index:
|
||||
class_name = model_index["_class_name"]
|
||||
for pipeline_cfg in _PIPELINE_REGISTRY.values():
|
||||
if pipeline_cfg.diffusers_class_name == class_name:
|
||||
logger.info(
|
||||
"Detected pipeline %r from model_index.json (_class_name=%r)",
|
||||
pipeline_cfg.model_type,
|
||||
class_name,
|
||||
)
|
||||
return pipeline_cfg.model_type, None
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to detect model type for diffusers-style models: {e}")
|
||||
|
||||
# Final fallback: some models (e.g. CosyVoice3) ship an empty
|
||||
# config.json and rely on naming conventions. Match the model path
|
||||
# basename against registered pipeline keys — longest match wins
|
||||
# so "cosyvoice3" (length 10) beats "cosyvoice" (length 9).
|
||||
model_lower = model.lower().replace("-", "").replace("_", "")
|
||||
best: str | None = None
|
||||
best_len = 0
|
||||
for registered_key in _PIPELINE_REGISTRY.keys():
|
||||
candidate = registered_key.lower().replace("-", "").replace("_", "")
|
||||
if candidate and candidate in model_lower and len(candidate) > best_len:
|
||||
best = registered_key
|
||||
best_len = len(candidate)
|
||||
if best is not None:
|
||||
return best, None
|
||||
|
||||
return None, None
|
||||
|
||||
@classmethod
|
||||
def _merge_cli_overrides(
|
||||
cls,
|
||||
stage: StageConfig,
|
||||
cli_overrides: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Merge global and per-stage (``stage_N_*``) CLI overrides.
|
||||
|
||||
Orchestrator-owned keys are filtered by ``build_stage_runtime_overrides``
|
||||
using ``OrchestratorArgs`` as the single source of truth; unknown
|
||||
server/uvicorn keys are dropped downstream by
|
||||
``filter_dataclass_kwargs(OmniEngineArgs, ...)``.
|
||||
"""
|
||||
return build_stage_runtime_overrides(stage.stage_id, cli_overrides)
|
||||
@@ -11,7 +11,7 @@ from vllm.sampling_params import RequestOutputKind, SamplingParams
|
||||
from vllm.transformers_utils.config import get_config, get_hf_file_to_dict
|
||||
from vllm.transformers_utils.repo_utils import file_or_path_exists
|
||||
|
||||
from vllm_omni.config.stage_config import StageConfigFactory
|
||||
from vllm_omni.config.config_factory import StageConfigFactory
|
||||
from vllm_omni.config.yaml_util import create_config, load_yaml_config, merge_configs
|
||||
from vllm_omni.diffusion.utils.hf_utils import _looks_like_dreamzero
|
||||
from vllm_omni.entrypoints.stage_utils import _to_dict
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,3 +1,8 @@
|
||||
from .bagel import OmniBagelForConditionalGeneration
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
__all__ = ["OmniBagelForConditionalGeneration"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,6 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from vllm_omni.model_executor.models.dreamzero.pipeline import DREAMZERO_PIPELINE
|
||||
|
||||
__all__ = ["DREAMZERO_PIPELINE"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,59 +1,8 @@
|
||||
from __future__ import annotations
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from .dynin_omni import DyninOmniForConditionalGeneration
|
||||
from .dynin_omni_common import (
|
||||
get_dynin_magvit_attr,
|
||||
get_dynin_modeling_attr,
|
||||
get_dynin_sampling_attr,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .dynin_omni_token2audio import DyninOmniToken2Audio
|
||||
from .dynin_omni_token2image import DyninOmniToken2Image
|
||||
from .dynin_omni_token2text import DyninOmniToken2Text
|
||||
|
||||
|
||||
_STAGE_EXPORTS = {
|
||||
"DyninOmniToken2Audio": (".dynin_omni_token2audio", "DyninOmniToken2Audio"),
|
||||
"DyninOmniToken2Image": (".dynin_omni_token2image", "DyninOmniToken2Image"),
|
||||
"DyninOmniToken2Text": (".dynin_omni_token2text", "DyninOmniToken2Text"),
|
||||
}
|
||||
|
||||
_MODELING_EXPORTS = {"DyninOmniConfig", "DyninOmniModelLM", "VideoTokenMerger"}
|
||||
_MAGVIT_EXPORTS = {"VQGANEncoder", "VQGANDecoder", "LFQuantizer", "MAGVITv2"}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
if name in _STAGE_EXPORTS:
|
||||
module_name, attr_name = _STAGE_EXPORTS[name]
|
||||
module = __import__(module_name, globals(), locals(), [attr_name], 1)
|
||||
return getattr(module, attr_name)
|
||||
|
||||
if name in _MODELING_EXPORTS:
|
||||
return get_dynin_modeling_attr(name)
|
||||
|
||||
if name in _MAGVIT_EXPORTS:
|
||||
return get_dynin_magvit_attr(name)
|
||||
|
||||
if name == "get_mask_schedule":
|
||||
return get_dynin_sampling_attr("get_mask_schedule")
|
||||
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DyninOmniForConditionalGeneration",
|
||||
"DyninOmniToken2Audio",
|
||||
"DyninOmniToken2Image",
|
||||
"DyninOmniToken2Text",
|
||||
"DyninOmniConfig",
|
||||
"DyninOmniModelLM",
|
||||
"VideoTokenMerger",
|
||||
"VQGANEncoder",
|
||||
"VQGANDecoder",
|
||||
"LFQuantizer",
|
||||
"MAGVITv2",
|
||||
"get_mask_schedule",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,16 +1,8 @@
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import to avoid importing transformers.models.glm_image at module init.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
The AR model depends on ``transformers.models.glm_image`` which is only
|
||||
available when the model weights are present. Importing it eagerly in
|
||||
``__init__.py`` breaks the pipeline registry lookup for environments that
|
||||
don't have this custom transformers extension installed.
|
||||
"""
|
||||
if name == "GlmImageForConditionalGeneration":
|
||||
from .glm_image_ar import GlmImageForConditionalGeneration
|
||||
|
||||
return GlmImageForConditionalGeneration
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = ["GlmImageForConditionalGeneration"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,18 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""GLM-TTS model support for vLLM-Omni.
|
||||
|
||||
GLM-TTS is a two-stage text-to-speech system:
|
||||
- Stage 0 (LLM): Llama-based AR model generates speech tokens from text
|
||||
- Stage 1 (DiT): Flow matching model converts speech tokens to mel-spectrogram
|
||||
|
||||
Reference: https://github.com/zai-org/GLM-TTS
|
||||
"""
|
||||
|
||||
from vllm_omni.model_executor.models.glm_tts.glm_tts import (
|
||||
GLMTTSForConditionalGeneration,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"GLMTTSForConditionalGeneration",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,2 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,9 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""vllm-omni integration for boson-ai's higgs-audio v2 (two-stage TTS)."""
|
||||
|
||||
from vllm_omni.model_executor.models.higgs_audio_v2.configuration_higgs_audio_v2 import (
|
||||
HiggsAudioV2Config,
|
||||
)
|
||||
|
||||
__all__ = ["HiggsAudioV2Config"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,9 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""vllm-omni integration for boson-ai's higgs-audio v3 (two-stage TTS)."""
|
||||
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
__all__ = ["HiggsAudioV3Config"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -23,11 +23,11 @@ from vllm_omni.model_executor.models.higgs_audio_v2.higgs_audio_decoder import (
|
||||
build_higgs_audio_acoustic_decoder,
|
||||
load_higgs_audio_codec,
|
||||
)
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.platforms import current_omni_platform
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"HiggsAudioV3Code2Wav",
|
||||
|
||||
@@ -36,10 +36,10 @@ from vllm.model_executor.models.qwen3 import Qwen3Model
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.v1.outputs import SamplerOutput
|
||||
|
||||
from vllm_omni.model_executor.models.higgs_audio_v3.configuration_higgs_audio_v3 import (
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
from vllm_omni.transformers_utils.configs.higgs_audio_v3 import (
|
||||
HiggsAudioV3Config,
|
||||
)
|
||||
from vllm_omni.model_executor.models.output_templates import OmniOutput
|
||||
|
||||
__all__ = ["HiggsAudioV3TalkerForConditionalGeneration"]
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from .hunyuan_image3 import HunyuanImage3ForConditionalGeneration
|
||||
|
||||
__all__ = ["HunyuanImage3ForConditionalGeneration"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,2 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,9 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
"""Lance (ByteDance) — vLLM-Omni model_executor package.
|
||||
|
||||
Single-stage topology: a self-contained diffusion stage (``LancePipeline``
|
||||
defined in ``vllm_omni.diffusion.models.lance``) that owns the Qwen2-MoT LLM,
|
||||
the Qwen2.5-VL ViT, the Wan2.2 VAE and the tokenizer — directly analogous to
|
||||
``bagel_single_stage``. The ``PipelineConfig`` lives in :mod:`.pipeline`.
|
||||
"""
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,17 +1,8 @@
|
||||
from transformers import AutoTokenizer
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from vllm_omni.tokenizers.mammoth_moda2_tokenizer import MammothUTokenizer
|
||||
from vllm_omni.transformers_utils.configs.mammoth_moda2 import ( # noqa: F401 registers AutoConfig
|
||||
Mammothmoda2Config,
|
||||
Mammothmoda2Qwen2_5_VLConfig,
|
||||
)
|
||||
|
||||
from .mammoth_moda2 import MammothModa2ARForConditionalGeneration
|
||||
|
||||
# AutoConfig.register is done inside transformers_utils/configs/mammoth_moda2.py
|
||||
AutoTokenizer.register(config_class=Mammothmoda2Config, slow_tokenizer_class=MammothUTokenizer)
|
||||
AutoTokenizer.register(config_class=Mammothmoda2Qwen2_5_VLConfig, slow_tokenizer_class=MammothUTokenizer)
|
||||
|
||||
__all__ = [
|
||||
"MammothModa2ARForConditionalGeneration",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,20 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright 2025 The vLLM-Omni team.
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
from .ming_flash_omni import MingFlashOmniForConditionalGeneration
|
||||
from .ming_flash_omni_talker import MingFlashOmniTalkerForConditionalGeneration
|
||||
from .ming_flash_omni_thinker import (
|
||||
MingFlashOmniThinkerDummyInputsBuilder,
|
||||
MingFlashOmniThinkerForConditionalGeneration,
|
||||
MingFlashOmniThinkerMultiModalProcessor,
|
||||
MingFlashOmniThinkerProcessingInfo,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"MingFlashOmniForConditionalGeneration",
|
||||
"MingFlashOmniTalkerForConditionalGeneration",
|
||||
"MingFlashOmniThinkerForConditionalGeneration",
|
||||
"MingFlashOmniThinkerProcessingInfo",
|
||||
"MingFlashOmniThinkerMultiModalProcessor",
|
||||
"MingFlashOmniThinkerDummyInputsBuilder",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,13 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from .config_ming_tts import MingDenseConfig
|
||||
from .ming_tts import MingTTSForConditionalGeneration
|
||||
from .ming_tts_audio_vae import MingAudioVAEModel
|
||||
from .ming_tts_llm import MingLLMModel
|
||||
|
||||
__all__ = [
|
||||
"MingDenseConfig",
|
||||
"MingTTSForConditionalGeneration",
|
||||
"MingLLMModel",
|
||||
"MingAudioVAEModel",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,9 +1,8 @@
|
||||
from .minicpmo_4_5_omni import MiniCPMO45OmniForConditionalGeneration
|
||||
from .minicpmo_4_5_omni_llm import MiniCPMO45OmniLLMForConditionalGeneration
|
||||
from .minicpmo_4_5_omni_tts import MiniCPMO45OmniTTSForConditionalGeneration
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
__all__ = [
|
||||
"MiniCPMO45OmniForConditionalGeneration",
|
||||
"MiniCPMO45OmniLLMForConditionalGeneration",
|
||||
"MiniCPMO45OmniTTSForConditionalGeneration",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,13 +1,8 @@
|
||||
from vllm_omni.model_executor.models.moss_tts.modeling_moss_tts_codec import (
|
||||
MossTTSCodecDecoder,
|
||||
)
|
||||
from vllm_omni.model_executor.models.moss_tts.modeling_moss_tts_talker import (
|
||||
MossTTSDelayTalkerForGeneration,
|
||||
MossTTSRealtimeTalkerForGeneration,
|
||||
)
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
__all__ = [
|
||||
"MossTTSDelayTalkerForGeneration",
|
||||
"MossTTSRealtimeTalkerForGeneration",
|
||||
"MossTTSCodecDecoder",
|
||||
]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,5 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from .modeling_moss_tts_nano import MossTTSNanoForGeneration
|
||||
|
||||
__all__ = ["MossTTSNanoForGeneration"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -1,3 +1,8 @@
|
||||
from .qwen3_omni import Qwen3OmniMoeForConditionalGeneration
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
|
||||
__all__ = ["Qwen3OmniMoeForConditionalGeneration"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -7,10 +7,13 @@ Stage 1: Talker — text embeddings → RVQ codec codes
|
||||
Stage 2: Code2Wav — RVQ codes → audio waveform
|
||||
"""
|
||||
|
||||
from transformers import Qwen3OmniMoeConfig
|
||||
|
||||
from vllm_omni.config.stage_config import (
|
||||
PipelineConfig,
|
||||
StageExecutionType,
|
||||
StagePipelineConfig,
|
||||
pipeline_cfg_resolver,
|
||||
)
|
||||
|
||||
_PROC = "vllm_omni.model_executor.stage_input_processors.qwen3_omni"
|
||||
@@ -64,3 +67,38 @@ QWEN3_OMNI_PIPELINE = PipelineConfig(
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
QWEN3_OMNI_THINKER_ONLY_PIPELINE = PipelineConfig(
|
||||
model_type="qwen3_omni_moe_thinker_only",
|
||||
model_arch="Qwen3OmniMoeForConditionalGeneration",
|
||||
stages=(
|
||||
StagePipelineConfig(
|
||||
stage_id=0,
|
||||
model_stage="thinker",
|
||||
execution_type=StageExecutionType.LLM_AR,
|
||||
input_sources=(),
|
||||
final_output=True,
|
||||
final_output_type="text",
|
||||
owns_tokenizer=True,
|
||||
requires_multimodal_data=True,
|
||||
hf_config_name="thinker_config",
|
||||
engine_output_type="latent",
|
||||
sampling_constraints={"detokenize": True},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@pipeline_cfg_resolver(config_type=Qwen3OmniMoeConfig)
|
||||
def resolve_qwen3_omni_pipeline(
|
||||
hf_config: Qwen3OmniMoeConfig,
|
||||
) -> PipelineConfig:
|
||||
"""Select the right pipeline variant based on the HF config, since some variants,
|
||||
e.g., Qwen3-Omni-30B-A3B-Captioner, are thinker only.
|
||||
|
||||
By default, we load the full pipeline, as this is the common case.
|
||||
"""
|
||||
# If we have a config and it explicitly disabled audio input, load thinker only
|
||||
if not hf_config.enable_audio_output:
|
||||
return QWEN3_OMNI_THINKER_ONLY_PIPELINE
|
||||
return QWEN3_OMNI_PIPELINE
|
||||
@@ -106,9 +106,6 @@ class Qwen3OmniMoeTalkerForConditionalGeneration(
|
||||
self.hidden_projection = Qwen3OmniMoeTalkerResizeMLP(self.config)
|
||||
self.codec_head = nn.Linear(self.config.text_config.hidden_size, self.config.text_config.vocab_size, bias=False)
|
||||
|
||||
self.rope_deltas = None
|
||||
self.spatial_merge_size = self.config.spatial_merge_size
|
||||
|
||||
self.vocab_size = self.config.code_predictor_config.vocab_size
|
||||
self.num_code_groups = self.config.code_predictor_config.num_code_groups
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from .voxcpm2_talker import VoxCPM2TalkerForConditionalGeneration
|
||||
|
||||
__all__ = ["VoxCPM2TalkerForConditionalGeneration"]
|
||||
# NOTE: Do not import model classes in this file. Importing any
|
||||
# submodule in this package triggers __init__.py execution, and
|
||||
# both the model registry and pipeline registry import submodules
|
||||
# directly — heavy imports here would be loaded as a side effect
|
||||
# even though nothing depends on these re-exports.
|
||||
@@ -10,6 +10,7 @@ from __future__ import annotations
|
||||
import importlib
|
||||
|
||||
_CLASS_TO_MODULE: dict[str, str] = {
|
||||
"HiggsAudioV3Config": "vllm_omni.transformers_utils.configs.higgs_audio_v3",
|
||||
"Mammothmoda2Config": "vllm_omni.transformers_utils.configs.mammoth_moda2",
|
||||
"Mammothmoda2Qwen2_5_VLConfig": "vllm_omni.transformers_utils.configs.mammoth_moda2",
|
||||
"Mammothmoda2Qwen2_5_VLTextConfig": "vllm_omni.transformers_utils.configs.mammoth_moda2",
|
||||
@@ -31,6 +32,7 @@ _CLASS_TO_MODULE: dict[str, str] = {
|
||||
}
|
||||
|
||||
__all__ = [
|
||||
"HiggsAudioV3Config",
|
||||
"Mammothmoda2Config",
|
||||
"Mammothmoda2Qwen2_5_VLConfig",
|
||||
"Mammothmoda2Qwen2_5_VLTextConfig",
|
||||
@@ -70,6 +72,7 @@ def __dir__():
|
||||
from vllm_omni.transformers_utils.configs import cosyvoice3 as _cosyvoice3 # noqa: F401, E402
|
||||
from vllm_omni.transformers_utils.configs import fish_speech as _fish_speech # noqa: F401, E402
|
||||
from vllm_omni.transformers_utils.configs import glm_tts as _glm_tts # noqa: F401, E402
|
||||
from vllm_omni.transformers_utils.configs import higgs_audio_v3 as _higgs_audio_v3 # noqa: F401, E402
|
||||
from vllm_omni.transformers_utils.configs import mammoth_moda2 as _mammoth_moda2 # noqa: F401, E402
|
||||
from vllm_omni.transformers_utils.configs import ming_flash_omni as _ming_flash_omni # noqa: F401, E402
|
||||
from vllm_omni.transformers_utils.configs import omnivoice as _omnivoice # noqa: F401, E402
|
||||
|
||||
File renamed without changes.
@@ -2,13 +2,15 @@
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
from typing import ClassVar, Literal
|
||||
|
||||
from transformers import AutoConfig, PretrainedConfig
|
||||
from transformers import AutoConfig, AutoTokenizer, PretrainedConfig
|
||||
from transformers.models.qwen2_5_vl.configuration_qwen2_5_vl import (
|
||||
Qwen2_5_VLConfig,
|
||||
Qwen2_5_VLTextConfig,
|
||||
Qwen2_5_VLVisionConfig,
|
||||
)
|
||||
|
||||
from vllm_omni.tokenizers.mammoth_moda2_tokenizer import MammothUTokenizer
|
||||
|
||||
__all__ = [
|
||||
"Mammothmoda2Config",
|
||||
"Mammothmoda2Qwen2_5_VLConfig",
|
||||
@@ -281,3 +283,6 @@ AutoConfig.register(Mammothmoda2Config.model_type, Mammothmoda2Config)
|
||||
AutoConfig.register(Mammothmoda2Qwen2_5_VLConfig.model_type, Mammothmoda2Qwen2_5_VLConfig)
|
||||
AutoConfig.register(Mammothmoda2Qwen2_5_VLTextConfig.model_type, Mammothmoda2Qwen2_5_VLTextConfig)
|
||||
AutoConfig.register(Mammothmoda2Qwen2_5_VLVisionConfig.model_type, Mammothmoda2Qwen2_5_VLVisionConfig)
|
||||
# Register tokenizer_type -> the configs & AutoTokenizer
|
||||
AutoTokenizer.register(config_class=Mammothmoda2Config, slow_tokenizer_class=MammothUTokenizer)
|
||||
AutoTokenizer.register(config_class=Mammothmoda2Qwen2_5_VLConfig, slow_tokenizer_class=MammothUTokenizer)
|
||||
Reference in new issue
Block a user