Files
vllm-omni/vllm_omni/platforms/npu/platform.py
T

287 lines
10 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from contextlib import nullcontext
from typing import Any
import torch
import torch.nn as nn
from vllm.logger import init_logger
from vllm_ascend.platform import NPUPlatform
from vllm_omni.diffusion.attention.backends.registry import DiffusionAttentionBackendEnum
from vllm_omni.platforms.interface import OmniPlatform, OmniPlatformEnum
logger = init_logger(__name__)
_DIFFUSION_PACKED_MODULES_MAPPING = {
"HunyuanImage3Pipeline": {
"experts": ["experts.0.gate_up_proj", "experts.0.down_proj"],
},
}
class NPUOmniPlatform(OmniPlatform, NPUPlatform):
"""NPU/Ascend implementation of OmniPlatform.
Inherits all NPU-specific implementations from vllm-ascend's NPUPlatform,
and adds Omni-specific interfaces from OmniPlatform.
"""
_omni_enum = OmniPlatformEnum.NPU
dist_backend: str = "hccl"
# conv2d convolution operator in the code2wav module of Qwen3-TTS not being able to run on Aclnn
def __init__(self) -> None:
from vllm_ascend.utils import adapt_patch
from vllm_omni.platforms.npu._310p import apply_patches as apply_310p_patches
from vllm_omni.platforms.npu.models.minicpmo_4_5_code2wav import (
apply_minicpmo_4_5_code2wav_patch,
)
from vllm_omni.platforms.npu.models.qwen3_tts_code2wav import (
apply_qwen3_tts_code2wav_patch,
)
from vllm_omni.platforms.npu.models.qwen3_tts_tokenizer_v2 import (
apply_qwen3_tts_tokenizer_v2_patch,
)
adapt_patch(is_global_patch=True)
apply_minicpmo_4_5_code2wav_patch()
apply_qwen3_tts_code2wav_patch()
apply_qwen3_tts_tokenizer_v2_patch()
apply_310p_patches()
@classmethod
def set_device(cls, device: torch.device) -> None:
super().set_device(device)
# Register vllm_ascend custom ops (torch.ops._C_ascend.*).
from vllm_ascend.utils import enable_custom_op
enable_custom_op()
# Ascend quantized weights are converted from ND to FRACTAL_NZ
# after loading. Enable internal format so the NZ storage layout
# is preserved for fused NPU kernels.
torch.npu.config.allow_internal_format = True
@classmethod
def get_omni_ar_worker_cls(cls) -> str:
return "vllm_omni.platforms.npu.worker.npu_ar_worker.NPUARWorker"
@classmethod
def get_omni_generation_worker_cls(cls) -> str:
return "vllm_omni.platforms.npu.worker.npu_generation_worker.NPUGenerationWorker"
@classmethod
def init_diffusion_worker_vllm_config(cls, vllm_config: Any) -> None:
from vllm_ascend.ascend_config import init_ascend_config
init_ascend_config(vllm_config)
@classmethod
def init_diffusion_model_runner_runtime(cls, vllm_config: Any, od_config: Any, device: torch.device) -> None:
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,
)
# 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)
@classmethod
def get_default_stage_config_path(cls) -> str:
return "vllm_omni/deploy"
@classmethod
def prepare_diffusion_op_runtime(cls, op_name: str, **kwargs: Any) -> None:
if op_name != "fused_moe":
return
from vllm_omni.platforms.npu.layers.fused_moe import prepare_fused_moe_runtime
prepare_fused_moe_runtime()
@classmethod
def register_additional_diffusion_fused_moe_hooks(cls, moe_runner: Any) -> None:
from vllm_omni.platforms.npu.layers.fused_moe import fused_moe_forward_context_pre_hook
moe_runner.register_forward_pre_hook(
fused_moe_forward_context_pre_hook,
with_kwargs=True,
)
@classmethod
def reset_diffusion_fused_moe_forward_context(cls) -> None:
from vllm_omni.platforms.npu.layers.fused_moe import reset_fused_moe_forward_context
reset_fused_moe_forward_context()
@classmethod
def get_diffusion_packed_modules_mapping(
cls,
model_class: type[nn.Module],
) -> dict[str, list[str]] | None:
return _DIFFUSION_PACKED_MODULES_MAPPING.get(model_class.__name__, None)
@classmethod
def get_diffusion_attn_backend_cls(
cls,
selected_backend: str | None,
head_size: int,
allow_trtllm_default: bool = True,
) -> str:
# NPU has no TRTLLM backend; accepted for signature parity, ignored.
from importlib.util import find_spec
if selected_backend is not None:
backend_upper = selected_backend.upper()
cls.validate_diffusion_attn_backend(backend_upper)
if backend_upper in ("FLASH_ATTN_HUB", "FLASH_ATTN_3_HUB"):
logger.warning(
"HuggingFace kernels-backed FlashAttention is "
"not supported on NPU. Falling back to local "
"FLASH_ATTN."
)
backend_upper = "FLASH_ATTN"
if backend_upper == "FLASH_ATTN" and find_spec("mindiesd"):
# The NPU FLASH_ATTN backend imports mindiesd lazily at first
# forward, but CANN snapshots the custom-op registry at the
# first custom-op regInfo lookup in the process (e.g. a
# vllm-ascend custom op during model load/warmup). Import
# mindiesd here so its env.py prepends the mindiesd vendor
# dirs (aie_ascendc etc.) to ASCEND_CUSTOM_OPP_PATH before
# that snapshot; otherwise aclnnLaserAttention /
# FusedAttentionScore fail with EZ1001 "does not support
# opType" for the rest of the process.
import mindiesd # noqa: F401
backend = DiffusionAttentionBackendEnum[backend_upper]
logger.debug("Using diffusion attention backend '%s'", backend_upper)
return backend.get_path()
# Try FLASH_ATTN if mindiesd is available, otherwise fall back to SDPA
if find_spec("mindiesd"):
# Configure ASCEND_CUSTOM_OPP_PATH for mindiesd custom ops upon import
import mindiesd # noqa: F401
logger.debug("Defaulting to diffusion attention backend FLASH_ATTN")
return DiffusionAttentionBackendEnum.FLASH_ATTN.get_path()
logger.debug("Falling back to diffusion attention backend SDPA")
return DiffusionAttentionBackendEnum.TORCH_SDPA.get_path()
@classmethod
def supports_torch_inductor(cls) -> bool:
return False
@classmethod
def get_torch_device(cls, local_rank: int | None = None) -> torch.device:
if local_rank is None:
return torch.device("npu")
return torch.device("npu", local_rank)
@classmethod
def get_device_count(cls) -> int:
return torch.npu.device_count()
@classmethod
def get_device_version(cls) -> str | None:
return None
@classmethod
def synchronize(cls) -> None:
torch.npu.synchronize()
@classmethod
def record_device_event(cls) -> torch.Event | None:
"""Record a NPU event on the default stream to mark tensor readiness.
On NPU/Ascend with HCCL, distributed communication may use internal
streams not visible to the default stream. Synchronize the default
stream first so that HCCL results are written back before we record
the event, ensuring d2h_stream.wait_event() captures the complete
output data.
"""
try:
torch.npu.current_stream().synchronize()
event = torch.npu.Event()
event.record()
return event
except Exception:
logger.warning("Failed to record NPU event for cross-stream sync")
return None
@classmethod
def get_free_memory(cls, device: torch.device | None = None) -> int:
free, _ = torch.npu.mem_get_info(device)
return free
@classmethod
def get_device_memory(cls, device: torch.device | None = None) -> tuple[int, int]:
free, total = torch.npu.mem_get_info(device)
return free, total
@classmethod
def get_device_total_memory(cls, device_id: int = 0) -> int:
device_props = torch.npu.get_device_properties(device_id)
return device_props.total_memory
@classmethod
def create_autocast_context(cls, *, device_type, dtype, enabled=True):
if device_type != "npu":
return super().create_autocast_context(
device_type=device_type,
dtype=dtype,
enabled=enabled,
)
if not enabled:
return nullcontext()
# NPU-specific fallback
try:
return torch.npu.amp.autocast(dtype=dtype)
except (RuntimeError, TypeError, ValueError) as exc:
logger.warning("autocast unavailable for device_type=%s dtype=%s: %s", device_type, dtype, exc)
return nullcontext()
@classmethod
def get_profiler_cls(cls) -> str:
return "vllm_omni.platforms.npu.profiler.NPUTorchProfilerWrapper"
@classmethod
def get_graph_wrapper_cls(cls) -> type:
from vllm_ascend.compilation.acl_graph import ACLGraphWrapper
return ACLGraphWrapper
@classmethod
def set_forward_context(
cls,
attn_metadata,
vllm_config,
*,
cudagraph_runtime_mode,
batch_descriptor,
):
from vllm_ascend.ascend_forward_context import set_ascend_forward_context
return set_ascend_forward_context(
attn_metadata,
vllm_config,
aclgraph_runtime_mode=cudagraph_runtime_mode,
batch_descriptor=batch_descriptor,
)