[Bugfix][NPU] Break the pytest DiffusionOutput circular import (#6293)

Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
amy-why-3459 authored and GitHub committed 2026-08-18 10:11:10 +08:00
1 parent d7216ef514
commit 3c6648d8a5
3 files changed
+128 -7

No files matched your search

+95
View File
@@ -0,0 +1,95 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Guard the NPU pytest circular import through DiffusionOutput."""
from __future__ import annotations
import ast
import os
import subprocess
import sys
from pathlib import Path
import pytest
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
REPO_ROOT = Path(__file__).resolve().parents[2]
def _top_level_import_modules(path: Path) -> set[str]:
tree = ast.parse(path.read_text(encoding="utf-8"))
modules: set[str] = set()
for node in tree.body:
if isinstance(node, ast.ImportFrom) and node.module:
modules.add(node.module)
elif isinstance(node, ast.Import):
modules.update(alias.name for alias in node.names)
return modules
def _assert_isolated_import_succeeds(script: str) -> None:
env = os.environ.copy()
env.pop("CUDA_VISIBLE_DEVICES", None)
env.pop("HIP_VISIBLE_DEVICES", None)
result = subprocess.run(
[sys.executable, "-c", script],
cwd=REPO_ROOT,
env=env,
capture_output=True,
text=True,
timeout=120,
check=False,
)
assert result.returncode == 0, result.stderr
def test_config_package_does_not_eagerly_import_pipeline_registry() -> None:
imported = _top_level_import_modules(REPO_ROOT / "vllm_omni" / "config" / "__init__.py")
assert "vllm_omni.config.config_factory" not in imported
assert "vllm_omni.config.pipeline_registry" not in imported
def test_config_package_lazy_exports_still_resolve() -> None:
from vllm_omni.config import StageConfigFactory, register_pipeline
from vllm_omni.config.config_factory import StageConfigFactory as DirectStageConfigFactory
from vllm_omni.config.pipeline_registry import register_pipeline as direct_register_pipeline
assert StageConfigFactory is DirectStageConfigFactory
assert register_pipeline is direct_register_pipeline
def test_npu_platform_defers_minimax_h3_encoder_patch() -> None:
src = (REPO_ROOT / "vllm_omni" / "platforms" / "npu" / "platform.py").read_text(encoding="utf-8")
init_start = src.index("def __init__(self) -> None:")
runtime_start = src.index("def init_diffusion_model_runner_runtime")
init_src = src[init_start:runtime_start]
runtime_src = src[runtime_start:]
assert "apply_minimax_h3_qwen3vl_patch" not in init_src
assert "apply_minimax_h3_qwen3vl_patch" in runtime_src
assert "apply_minimax_h3_qwen3vl_swiglu_patch" in runtime_src
def test_lora_config_import_does_not_load_pipeline_registry() -> None:
"""Import LoRAConfig and diffusion.data in a fresh interpreter.
The NPU failure was ``data.py`` importing LoRAConfig while still loading,
then ``vllm_omni.config`` re-entering ``pipeline_registry`` → PI0 →
DiffusionOutput. This child process must not preload those exports.
"""
_assert_isolated_import_succeeds(
"""
import sys
from vllm_omni.config.lora import LoRAConfig
if "vllm_omni.config.pipeline_registry" in sys.modules:
raise SystemExit("LoRAConfig import loaded pipeline_registry")
if "vllm_omni.diffusion.models.pi0.pipeline_pi0" in sys.modules:
raise SystemExit("LoRAConfig import loaded PI0_PIPELINE")
from vllm_omni.diffusion.data import DiffusionOutput
if DiffusionOutput is None or LoRAConfig is None:
raise SystemExit("expected LoRAConfig and DiffusionOutput")
"""
)
+25 -2
View File
@@ -2,7 +2,6 @@
Configuration module for vLLM-Omni.
"""
from vllm_omni.config.config_factory import StageConfigFactory
from vllm_omni.config.lora import LoRAConfig
from vllm_omni.config.model import OmniModelConfig
from vllm_omni.config.omni_config import (
@@ -22,7 +21,6 @@ from vllm_omni.config.omni_config import (
VllmOmniGenerationStageConfig,
VllmOmniOrchestratorConfig,
)
from vllm_omni.config.pipeline_registry import register_pipeline
from vllm_omni.config.stage_config import (
PIPELINE_WIDE_ENGINE_FIELDS,
DeployConfig,
@@ -42,6 +40,15 @@ from vllm_omni.config.yaml_util import (
to_dict,
)
# StageConfigFactory / register_pipeline pull pipeline_registry, which eagerly
# imports PI0_PIPELINE → diffusion.data. Keep those lazy so
# `from vllm_omni.config.lora import LoRAConfig` (used while data.py is still
# loading) cannot close a circular import through DiffusionOutput.
_LAZY_ATTRS = {
"StageConfigFactory": ("vllm_omni.config.config_factory", "StageConfigFactory"),
"register_pipeline": ("vllm_omni.config.pipeline_registry", "register_pipeline"),
}
__all__ = [
# Legacy model-level configs.
"LoRAConfig",
@@ -82,3 +89,19 @@ __all__ = [
"merge_configs",
"to_dict",
]
def __getattr__(name: str):
spec = _LAZY_ATTRS.get(name)
if spec is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
module_name, attr_name = spec
import importlib
value = getattr(importlib.import_module(module_name), attr_name)
globals()[name] = value
return value
def __dir__() -> list[str]:
return sorted(set(__all__) | set(globals()))
+8 -5
View File
@@ -39,7 +39,6 @@ class NPUOmniPlatform(OmniPlatform, NPUPlatform):
from vllm_omni.platforms.npu.models.minicpmo_4_5_code2wav import (
apply_minicpmo_4_5_code2wav_patch,
)
from vllm_omni.platforms.npu.models.minimax_h3 import apply_minimax_h3_qwen3vl_patch
from vllm_omni.platforms.npu.models.qwen3_tts_code2wav import (
apply_qwen3_tts_code2wav_patch,
)
@@ -48,7 +47,6 @@ class NPUOmniPlatform(OmniPlatform, NPUPlatform):
)
adapt_patch(is_global_patch=True)
apply_minimax_h3_qwen3vl_patch()
apply_minicpmo_4_5_code2wav_patch()
apply_qwen3_tts_code2wav_patch()
apply_qwen3_tts_tokenizer_v2_patch()
@@ -87,12 +85,17 @@ class NPUOmniPlatform(OmniPlatform, NPUPlatform):
from vllm_ascend.ascend_forward_context import set_mc2_mask, set_mc2_tokens_capacity
from vllm_omni.platforms.npu.models.minimax_h3 import (
apply_minimax_h3_qwen3vl_patch,
apply_minimax_h3_qwen3vl_swiglu_patch,
)
# The patch imports the MiniMax encoder, which depends on
# current_omni_platform. Run it only after platform construction has
# completed, but before the diffusion pipeline is loaded.
# Both patches import the MiniMax encoder package, whose __init__ loads
# pipeline_minimax_h3 → diffusion.data. Doing that during platform
# construction races vllm_omni/__init__.py (patch before config) and
# closes a cycle through pipeline_registry → PI0_PIPELINE →
# DiffusionOutput. Apply them only after the platform exists, before
# the diffusion pipeline is loaded.
apply_minimax_h3_qwen3vl_patch()
apply_minimax_h3_qwen3vl_swiglu_patch()
set_mc2_tokens_capacity(vllm_config, od_config.max_num_seqs, 1)
set_mc2_mask(vllm_config, device)