diff --git a/afd_plugin/compat/npu/runtime.py b/afd_plugin/compat/npu/runtime.py index c71ab7ec..e360096e 100644 --- a/afd_plugin/compat/npu/runtime.py +++ b/afd_plugin/compat/npu/runtime.py @@ -29,8 +29,8 @@ def apply_afd_ascend_patches_if_needed() -> None: apply_afd_ascend_dbo_config_patch, ) - apply_afd_ascend_dbo_config_patch() - _PATCHES_APPLIED = True + if apply_afd_ascend_dbo_config_patch(): + _PATCHES_APPLIED = True __all__ = [ diff --git a/afd_plugin/compat/patches/config_validation.py b/afd_plugin/compat/patches/config_validation.py index 17c5edb7..71654580 100644 --- a/afd_plugin/compat/patches/config_validation.py +++ b/afd_plugin/compat/patches/config_validation.py @@ -49,74 +49,78 @@ def create_engine_config( """Create the VllmConfig.""" assert _original_create_engine_config is not None - if not _should_relax_engine_args_backend(self): - return _original_create_engine_config( - self, - usage_context, - headless, - ) + # ### PATCH START: AFD automatic worker selection + worker_cls_was_auto = _uses_auto_worker_value(self.worker_cls) + # ### PATCH END: AFD automatic worker selection + # ### PATCH START: AFD Ascend config patch ordering + if parse_optional_afd_config(self.additional_config) is not None: + from vllm.platforms import current_platform - # ### PATCH START: AFD ubatching all2all backend validation - # vLLM validates native ubatching against DeepEP backends. AFD ubatching - # uses plugin connectors, so temporarily present a supported backend only - # while upstream builds and validates VllmConfig. - original_backend = self.all2all_backend - self.all2all_backend = _AFD_TEMP_BACKEND - try: + if current_platform.device_type == "npu": + from afd_plugin.compat.npu import apply_afd_ascend_patches_if_needed + + apply_afd_ascend_patches_if_needed() + # ### PATCH END: AFD Ascend config patch ordering + if not _should_relax_engine_args_backend(self): config = _original_create_engine_config( self, usage_context, headless, ) - finally: - self.all2all_backend = original_backend - config.parallel_config.all2all_backend = original_backend - # ### PATCH END: AFD ubatching all2all backend validation + else: + # ### PATCH START: AFD ubatching all2all backend validation + # vLLM validates native ubatching against DeepEP backends. AFD ubatching + # uses plugin connectors, so temporarily present a supported backend while + # upstream builds and validates VllmConfig. The Ascend platform wrapper + # preserves this temporary value across its default-worker normalization. + original_backend = self.all2all_backend + self.all2all_backend = _AFD_TEMP_BACKEND + try: + config = _original_create_engine_config( + self, + usage_context, + headless, + ) + finally: + self.all2all_backend = original_backend + config.parallel_config.all2all_backend = original_backend + # ### PATCH END: AFD ubatching all2all backend validation + + # ### PATCH START: AFD automatic worker selection + if worker_cls_was_auto: + _select_afd_worker_for_auto(config) + # ### PATCH END: AFD automatic worker selection return config -# Patch reason: VllmConfig validation can rerun the native ubatching all2all -# backend assertion after EngineArgs construction, and upstream auto-selects a -# platform worker that does not contain AFD role behavior. -# Patch functionality: temporarily relaxes the backend assertion for AFD -# configs, restores the real backend, then replaces an auto-selected platform -# worker with the platform- and role-specific AFD worker. +# Patch reason: EngineCore handshakes explicitly rerun VllmConfig.__post_init__ +# after the config's actual AFD all2all backend has been restored. +# Patch functionality: temporarily presents a validation-safe backend during +# explicit AFD ubatching revalidation, then restores the actual backend. # Expansion exception: upstream VllmConfig.__post_init__ is a large validation -# pipeline; keep a narrow original-function delegation so this patch only owns -# AFD validation and worker normalization. +# pipeline; keep narrow original-function delegation so this patch only owns +# the AFD backend validation bypass. # Signature: matches upstream; no added parameters. def __post_init__(self: VllmConfig): """Verify configs are valid & consistent with each other.""" assert _original_vllm_config_post_init is not None - # ### PATCH START: AFD automatic worker selection - worker_cls_was_auto = _uses_auto_worker(self) - # ### PATCH END: AFD automatic worker selection if not _should_relax_vllm_config_backend(self): - result = _original_vllm_config_post_init(self) - else: - # ### PATCH START: AFD ubatching all2all backend validation - # Repeated VllmConfig validation can run after EngineArgs construction. - # Keep AFD's real all2all backend on the config, but use a temporary DeepEP - # value while upstream performs its native ubatching assertion. - parallel_config = self.parallel_config - original_backend = parallel_config.all2all_backend - parallel_config.all2all_backend = _AFD_TEMP_BACKEND - try: - result = _original_vllm_config_post_init(self) - finally: - parallel_config.all2all_backend = original_backend - # ### PATCH END: AFD ubatching all2all backend validation + return _original_vllm_config_post_init(self) - # ### PATCH START: AFD automatic worker selection - if worker_cls_was_auto: - _select_afd_worker_for_auto(self) - # ### PATCH END: AFD automatic worker selection + # ### PATCH START: AFD repeated ubatching backend validation + parallel_config = self.parallel_config + original_backend = parallel_config.all2all_backend + parallel_config.all2all_backend = _AFD_TEMP_BACKEND + try: + result = _original_vllm_config_post_init(self) + finally: + parallel_config.all2all_backend = original_backend + # ### PATCH END: AFD repeated ubatching backend validation return result -def _uses_auto_worker(vllm_config: VllmConfig) -> bool: - worker_cls = vllm_config.parallel_config.worker_cls +def _uses_auto_worker_value(worker_cls: str | type[Any]) -> bool: return isinstance(worker_cls, str) and worker_cls.strip() == "auto" diff --git a/afd_plugin/compat/patches/npu/ascend_platform.py b/afd_plugin/compat/patches/npu/ascend_platform.py index d501da4a..7a35b6af 100644 --- a/afd_plugin/compat/patches/npu/ascend_platform.py +++ b/afd_plugin/compat/patches/npu/ascend_platform.py @@ -2,12 +2,13 @@ # SPDX-FileCopyrightText: Copyright contributors to the AFD plugin project """Patch vLLM-Ascend platform config normalization for AFD-owned DBO. -Upstream source: ``vllm_ascend/platform.py``. +Upstream source: ``vllm_ascend/platform.py`` at tag ``v0.19.1rc1``. """ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from dataclasses import dataclass +from typing import TYPE_CHECKING from afd_plugin.config import parse_optional_afd_config @@ -17,83 +18,95 @@ _ASCEND_PLATFORM_PATCH_ATTR = "_afd_plugin_ascend_platform_patch_state" -def apply_afd_ascend_dbo_config_patch() -> None: +@dataclass(frozen=True) +class _AFDDBOConfigSnapshot: + enable_dbo: bool + ubatch_size: int + all2all_backend: str + + +def apply_afd_ascend_dbo_config_patch() -> bool: """Preserve AFD-owned DBO settings during vLLM-Ascend config normalization. vLLM-Ascend's platform compatibility pass disables DBO/ubatching fields for ordinary NPU runs. AFD owns its NPU ubatching path, so this patch snapshots those fields for AFD-enabled configs, lets upstream normalization run, then restores the AFD DBO values. The patch is a no-op when vLLM-Ascend is not - importable or when this process has already installed the wrapper. + importable. Returns whether this process has installed the wrapper (or had + already installed it), so callers do not cache a failed early import during + plugin initialization. """ try: from vllm_ascend.platform import NPUPlatform - except Exception: - return + except ImportError: + return False if hasattr(NPUPlatform, _ASCEND_PLATFORM_PATCH_ATTR): - return + return True - original_fix_incompatible_config = NPUPlatform._fix_incompatible_config + original_check_and_update_config = NPUPlatform.check_and_update_config - # Patch reason: vLLM-Ascend resets DBO fields inside NPUPlatform config - # normalization, while AFD now owns the Ascend DBO/ubatching path. + # Patch reason: vLLM-Ascend v0.19.1rc1 resets DBO fields in + # _fix_incompatible_config and later rewrites all2all_backend in + # check_and_update_config, while AFD owns the Ascend DBO/ubatching path and + # temporarily supplies a validation-safe backend. # Patch functionality: preserves upstream normalization for non-AFD configs and - # restores AFD DBO fields after upstream normalization for AFD-enabled configs. - # Expansion exception: upstream _fix_incompatible_config is platform-owned + # restores AFD DBO fields plus the temporary ubatching backend after upstream + # normalization for AFD-enabled configs. + # Expansion exception: upstream check_and_update_config is platform-owned # normalization; keep narrow original-function delegation so this patch only # owns the AFD DBO preservation. # Signature: matches upstream; no added parameters. - def _fix_incompatible_config(vllm_config: VllmConfig) -> Any: + def check_and_update_config(cls, vllm_config: VllmConfig) -> None: + del cls # ### PATCH START: AFD DBO config preservation saved = _snapshot_afd_dbo_config(vllm_config) + try: + original_check_and_update_config(vllm_config) + finally: + if saved is not None: + _restore_afd_dbo_config(vllm_config, saved) # ### PATCH END: AFD DBO config preservation - result = original_fix_incompatible_config(vllm_config) - # ### PATCH START: AFD DBO config preservation - if saved is not None: - _restore_afd_dbo_config(vllm_config, saved) - # ### PATCH END: AFD DBO config preservation - return result - NPUPlatform._fix_incompatible_config = staticmethod(_fix_incompatible_config) + NPUPlatform.check_and_update_config = classmethod(check_and_update_config) setattr( NPUPlatform, _ASCEND_PLATFORM_PATCH_ATTR, - original_fix_incompatible_config, + original_check_and_update_config, ) + return True -def _snapshot_afd_dbo_config(vllm_config: VllmConfig) -> dict[str, bool | int] | None: +def _snapshot_afd_dbo_config( + vllm_config: VllmConfig, +) -> _AFDDBOConfigSnapshot | None: if not _has_valid_afd_config(vllm_config): return None parallel_config = vllm_config.parallel_config - return { - "enable_dbo": parallel_config.enable_dbo, - "use_ubatching": parallel_config.use_ubatching, - "ubatch_size": parallel_config.ubatch_size, - } + return _AFDDBOConfigSnapshot( + enable_dbo=parallel_config.enable_dbo, + ubatch_size=parallel_config.ubatch_size, + all2all_backend=parallel_config.all2all_backend, + ) def _restore_afd_dbo_config( vllm_config: VllmConfig, - saved: dict[str, bool | int], + saved: _AFDDBOConfigSnapshot, ) -> None: parallel_config = vllm_config.parallel_config - if not ( - saved["enable_dbo"] - or saved["use_ubatching"] - or int(saved["ubatch_size"] or 0) != 0 - ): + if not saved.enable_dbo and saved.ubatch_size == 0: return - parallel_config.enable_dbo = saved["enable_dbo"] - parallel_config.ubatch_size = saved["ubatch_size"] + parallel_config.enable_dbo = saved.enable_dbo + parallel_config.ubatch_size = saved.ubatch_size + parallel_config.all2all_backend = saved.all2all_backend def _has_valid_afd_config(vllm_config: VllmConfig) -> bool: try: return parse_optional_afd_config(vllm_config, validate=True) is not None - except Exception: + except (TypeError, ValueError): return False diff --git a/tests/unit/compat/patches/test_config_validation.py b/tests/unit/compat/patches/test_config_validation.py index 4230575b..e7928830 100644 --- a/tests/unit/compat/patches/test_config_validation.py +++ b/tests/unit/compat/patches/test_config_validation.py @@ -7,6 +7,7 @@ import pytest +from afd_plugin.compat.npu import runtime as ascend_runtime from afd_plugin.validation import ( ATTENTION_WORKER_FQCN, FFN_WORKER_FQCN, @@ -87,7 +88,7 @@ def _engine_args(*, active, role="attention", worker_cls="auto"): args = sys.modules["vllm.engine.arg_utils"].EngineArgs() args.additional_config = {"afd": {"role": role}} if active else {} args.enable_dbo = True - args.ubatch_size = 1 + args.ubatch_size = 0 args.all2all_backend = "allgather_reducescatter" args.worker_cls = worker_cls return args @@ -100,6 +101,114 @@ def _set_fake_platform(*, is_cuda, device_type): ) +def _install_fake_npu_config(monkeypatch): + arg_utils_module, config_module = _install_fake_vllm_config(monkeypatch) + events = [] + + class FakeParallelConfig: + def __init__( + self, + *, + enable_dbo, + ubatch_size, + all2all_backend, + worker_cls, + ): + self.enable_dbo = enable_dbo + self.ubatch_size = ubatch_size + self.all2all_backend = all2all_backend + self.worker_cls = worker_cls + + @property + def use_ubatching(self): + return self.enable_dbo or self.ubatch_size > 1 + + class NPUPlatform: + device_type = "npu" + last_config = None + + @staticmethod + def is_cuda(): + return False + + @staticmethod + def _fix_incompatible_config(vllm_config): + parallel_config = vllm_config.parallel_config + events.append( + ( + "fix_incompatible_config", + parallel_config.enable_dbo, + parallel_config.ubatch_size, + ), + ) + parallel_config.enable_dbo = False + parallel_config.ubatch_size = 0 + + @classmethod + def check_and_update_config(cls, vllm_config): + cls.last_config = vllm_config + cls._fix_incompatible_config(vllm_config) + parallel_config = vllm_config.parallel_config + if ( + parallel_config.worker_cls == "auto" + and not vllm_config.compilation_config.pass_config.enable_sp + ): + parallel_config.all2all_backend = "flashinfer_all2allv" + parallel_config.worker_cls = VLLM_ASCEND_NPU_WORKER_FQCN + events.append( + ( + "ascend_normalization", + parallel_config.all2all_backend, + parallel_config.worker_cls, + ), + ) + if vllm_config.fail_update: + raise RuntimeError("upstream config failure") + + def post_init(vllm_config): + NPUPlatform.check_and_update_config(vllm_config) + parallel_config = vllm_config.parallel_config + if parallel_config.use_ubatching: + events.append( + ("native_dbo_validation", parallel_config.all2all_backend), + ) + assert parallel_config.all2all_backend in { + "deepep_low_latency", + "deepep_high_throughput", + }, "native all2all backend assertion" + vllm_config.post_init_backend = parallel_config.all2all_backend + + def create_engine_config(engine_args, usage_context=None, headless=False): + del usage_context, headless + config = config_module.VllmConfig() + config.additional_config = engine_args.additional_config + config.parallel_config = FakeParallelConfig( + enable_dbo=engine_args.enable_dbo, + ubatch_size=engine_args.ubatch_size, + all2all_backend=engine_args.all2all_backend, + worker_cls=engine_args.worker_cls, + ) + config.compilation_config = SimpleNamespace( + pass_config=SimpleNamespace(enable_sp=engine_args.enable_sp), + ) + config.fail_update = engine_args.fail_update + config.__post_init__() + return config + + config_module.VllmConfig.__post_init__ = post_init + arg_utils_module.EngineArgs.create_engine_config = create_engine_config + + fake_package = types.ModuleType("vllm_ascend") + fake_package.__path__ = [] + fake_platform = types.ModuleType("vllm_ascend.platform") + fake_platform.NPUPlatform = NPUPlatform + monkeypatch.setitem(sys.modules, "vllm_ascend", fake_package) + monkeypatch.setitem(sys.modules, "vllm_ascend.platform", fake_platform) + sys.modules["vllm.platforms"].current_platform = NPUPlatform + monkeypatch.setattr(ascend_runtime, "_PATCHES_APPLIED", False) + return arg_utils_module, NPUPlatform, events + + def test_config_validation_patch_relaxes_backend_for_afd_ubatching(monkeypatch): arg_utils_module, _config_module = _install_fake_vllm_config(monkeypatch) patch_module = _load_patch_module() @@ -151,6 +260,138 @@ def test_config_validation_patch_relaxes_repeated_vllm_post_init(monkeypatch): assert cfg.parallel_config.worker_cls == ATTENTION_WORKER_FQCN +@pytest.mark.parametrize( + ("role", "expected_worker_cls"), + [ + ("attention", NPU_ATTENTION_WORKER_FQCN), + ("ffn", NPU_FFN_WORKER_FQCN), + ], +) +def test_config_validation_preserves_npu_dbo_through_auto_worker_normalization( + monkeypatch, + role, + expected_worker_cls, +): + arg_utils_module, _npu_platform, events = _install_fake_npu_config(monkeypatch) + _load_patch_module() + args = _engine_args(active=True, role=role) + args.ubatch_size = 2 + args.enable_sp = False + args.fail_update = False + + cfg = arg_utils_module.EngineArgs.create_engine_config(args) + + assert ( + "ascend_normalization", + "flashinfer_all2allv", + VLLM_ASCEND_NPU_WORKER_FQCN, + ) in events + assert ("native_dbo_validation", "deepep_low_latency") in events + assert cfg.post_init_backend == "deepep_low_latency" + assert args.all2all_backend == "allgather_reducescatter" + assert cfg.parallel_config.all2all_backend == "allgather_reducescatter" + assert cfg.parallel_config.enable_dbo is True + assert cfg.parallel_config.ubatch_size == 2 + assert cfg.parallel_config.use_ubatching is True + assert cfg.parallel_config.worker_cls == expected_worker_cls + + +def test_config_validation_revalidates_npu_dbo_and_restores_backend(monkeypatch): + arg_utils_module, _npu_platform, events = _install_fake_npu_config(monkeypatch) + _load_patch_module() + args = _engine_args(active=True) + args.ubatch_size = 2 + args.enable_sp = False + args.fail_update = False + + cfg = arg_utils_module.EngineArgs.create_engine_config(args) + events.clear() + cfg.__post_init__() + + assert ("native_dbo_validation", "deepep_low_latency") in events + assert cfg.post_init_backend == "deepep_low_latency" + assert cfg.parallel_config.all2all_backend == "allgather_reducescatter" + assert cfg.parallel_config.enable_dbo is True + assert cfg.parallel_config.ubatch_size == 2 + assert cfg.parallel_config.worker_cls == NPU_ATTENTION_WORKER_FQCN + + +def test_config_validation_restores_npu_snapshot_when_platform_update_fails( + monkeypatch, +): + arg_utils_module, npu_platform, _events = _install_fake_npu_config(monkeypatch) + _load_patch_module() + args = _engine_args(active=True) + args.ubatch_size = 2 + args.enable_sp = False + args.fail_update = True + + with pytest.raises(RuntimeError, match="upstream config failure"): + arg_utils_module.EngineArgs.create_engine_config(args) + + cfg = npu_platform.last_config + assert cfg.parallel_config.enable_dbo is True + assert cfg.parallel_config.ubatch_size == 2 + assert cfg.parallel_config.all2all_backend == "deepep_low_latency" + assert args.all2all_backend == "allgather_reducescatter" + + +def test_config_validation_preserves_explicit_npu_worker(monkeypatch): + arg_utils_module, _npu_platform, events = _install_fake_npu_config(monkeypatch) + _load_patch_module() + args = _engine_args( + active=True, + worker_cls=NPU_ATTENTION_WORKER_FQCN, + ) + args.ubatch_size = 2 + args.enable_sp = False + args.fail_update = False + + cfg = arg_utils_module.EngineArgs.create_engine_config(args) + + assert ("native_dbo_validation", "deepep_low_latency") in events + assert cfg.parallel_config.worker_cls == NPU_ATTENTION_WORKER_FQCN + assert cfg.parallel_config.enable_dbo is True + assert cfg.parallel_config.ubatch_size == 2 + assert cfg.parallel_config.all2all_backend == "allgather_reducescatter" + + +def test_config_validation_preserves_non_afd_npu_upstream_behavior(monkeypatch): + arg_utils_module, _npu_platform, events = _install_fake_npu_config(monkeypatch) + _load_patch_module() + args = _engine_args(active=False) + args.ubatch_size = 2 + args.enable_sp = False + args.fail_update = False + + cfg = arg_utils_module.EngineArgs.create_engine_config(args) + + assert not any(event[0] == "native_dbo_validation" for event in events) + assert cfg.parallel_config.enable_dbo is False + assert cfg.parallel_config.ubatch_size == 0 + assert cfg.parallel_config.use_ubatching is False + assert cfg.parallel_config.all2all_backend == "flashinfer_all2allv" + assert cfg.parallel_config.worker_cls == VLLM_ASCEND_NPU_WORKER_FQCN + + +def test_config_validation_preserves_npu_dbo_off_behavior(monkeypatch): + arg_utils_module, _npu_platform, events = _install_fake_npu_config(monkeypatch) + _load_patch_module() + args = _engine_args(active=True, role="ffn") + args.enable_dbo = False + args.ubatch_size = 0 + args.enable_sp = False + args.fail_update = False + + cfg = arg_utils_module.EngineArgs.create_engine_config(args) + + assert not any(event[0] == "native_dbo_validation" for event in events) + assert cfg.parallel_config.enable_dbo is False + assert cfg.parallel_config.ubatch_size == 0 + assert cfg.parallel_config.all2all_backend == "flashinfer_all2allv" + assert cfg.parallel_config.worker_cls == NPU_FFN_WORKER_FQCN + + @pytest.mark.parametrize( ( "role", @@ -230,6 +471,30 @@ def test_config_validation_patch_auto_selects_without_ubatching(monkeypatch): assert cfg.parallel_config.worker_cls == FFN_WORKER_FQCN +def test_config_validation_installs_ascend_patch_only_on_npu(monkeypatch): + arg_utils_module, config_module = _install_fake_vllm_config(monkeypatch) + import afd_plugin.compat.npu as npu_compat + + calls = [] + monkeypatch.setattr( + npu_compat, + "apply_afd_ascend_patches_if_needed", + lambda: calls.append("npu"), + ) + patch_module = _load_patch_module() + importlib.reload(patch_module) + + cuda_args = _engine_args(active=True) + arg_utils_module.EngineArgs.create_engine_config(cuda_args) + assert calls == [] + + config_module.VllmConfig.platform_worker_cls = VLLM_ASCEND_NPU_WORKER_FQCN + _set_fake_platform(is_cuda=False, device_type="npu") + npu_args = _engine_args(active=True) + arg_utils_module.EngineArgs.create_engine_config(npu_args) + assert calls == ["npu"] + + def test_config_validation_patch_preserves_non_afd_platform_default(monkeypatch): arg_utils_module, config_module = _install_fake_vllm_config(monkeypatch) config_module.VllmConfig.platform_worker_cls = VLLM_GPU_WORKER_FQCN diff --git a/tests/unit/compat/test_runtime.py b/tests/unit/compat/test_runtime.py index 9c74a90b..1bb95dfd 100644 --- a/tests/unit/compat/test_runtime.py +++ b/tests/unit/compat/test_runtime.py @@ -6,6 +6,8 @@ from contextlib import contextmanager from types import ModuleType, SimpleNamespace +import pytest + from afd_plugin.compat.npu import runtime as ascend_runtime from afd_plugin.compat.npu.runtime import fix_all2all_backend_for_afd @@ -136,6 +138,7 @@ class FakeParallelConfig: def __init__(self, *, enable_dbo, ubatch_size): self.enable_dbo = enable_dbo self.ubatch_size = ubatch_size + self.all2all_backend = "deepep_low_latency" @property def use_ubatching(self): @@ -147,7 +150,14 @@ def _fix_incompatible_config(vllm_config): parallel_config = vllm_config.parallel_config parallel_config.enable_dbo = False parallel_config.ubatch_size = 0 - return "fixed" + + @classmethod + def check_and_update_config(cls, vllm_config): + cls._fix_incompatible_config(vllm_config) + parallel_config = vllm_config.parallel_config + parallel_config.all2all_backend = "flashinfer_all2allv" + if getattr(vllm_config, "fail_update", False): + raise RuntimeError("upstream config failure") def afd_vllm_config(*, active=True): config = _vllm_config() @@ -162,6 +172,7 @@ def afd_vllm_config(*, active=True): else {} ) config.parallel_config = FakeParallelConfig(enable_dbo=True, ubatch_size=4) + config.fail_update = False return config fake_platform.NPUPlatform = NPUPlatform @@ -172,12 +183,49 @@ def afd_vllm_config(*, active=True): ascend_runtime.apply_afd_ascend_patches_if_needed() config = afd_vllm_config() - assert NPUPlatform._fix_incompatible_config(config) == "fixed" + assert NPUPlatform.check_and_update_config(config) is None assert config.parallel_config.enable_dbo is True assert config.parallel_config.use_ubatching is True assert config.parallel_config.ubatch_size == 4 + assert config.parallel_config.all2all_backend == "deepep_low_latency" + + failing_config = afd_vllm_config() + failing_config.fail_update = True + with pytest.raises(RuntimeError, match="upstream config failure"): + NPUPlatform.check_and_update_config(failing_config) + assert failing_config.parallel_config.enable_dbo is True + assert failing_config.parallel_config.ubatch_size == 4 + assert failing_config.parallel_config.all2all_backend == "deepep_low_latency" inactive_config = afd_vllm_config(active=False) - assert NPUPlatform._fix_incompatible_config(inactive_config) == "fixed" + assert NPUPlatform.check_and_update_config(inactive_config) is None assert inactive_config.parallel_config.enable_dbo is False assert inactive_config.parallel_config.use_ubatching is False + assert inactive_config.parallel_config.all2all_backend == "flashinfer_all2allv" + + +def test_npu_afd_config_patch_retries_after_initial_import_error(monkeypatch): + fake_package = ModuleType("vllm_ascend") + fake_package.__path__ = [] + monkeypatch.setitem(sys.modules, "vllm_ascend", fake_package) + monkeypatch.delitem(sys.modules, "vllm_ascend.platform", raising=False) + monkeypatch.setattr(ascend_runtime, "_PATCHES_APPLIED", False) + + ascend_runtime.apply_afd_ascend_patches_if_needed() + + assert ascend_runtime._PATCHES_APPLIED is False + + fake_platform = ModuleType("vllm_ascend.platform") + + class NPUPlatform: + @classmethod + def check_and_update_config(cls, vllm_config): + del cls, vllm_config + + fake_platform.NPUPlatform = NPUPlatform + monkeypatch.setitem(sys.modules, "vllm_ascend.platform", fake_platform) + + ascend_runtime.apply_afd_ascend_patches_if_needed() + + assert ascend_runtime._PATCHES_APPLIED is True + assert hasattr(NPUPlatform, "_afd_plugin_ascend_platform_patch_state")