| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | """Abstract base class for MoE kernel oracles. |
| 5 | |
| 6 | Each MoE oracle (unquantized / fp8 / nvfp4 / mxfp4 / mxfp8 / int8 / |
| 7 | int_wna16) is responsible for selecting the right MoE kernel backend for a |
| 8 | given (model, hardware, deployment-config) tuple. The current |
| 9 | implementation expresses this responsibility as module-level functions |
| 10 | that follow an informal convention. |
| 11 | |
| 12 | This module declares the abstract contract; concrete oracles inherit from |
| 13 | `MoEKernelOracle` and provide the platform-specific behaviour. |
| 14 | |
| 15 | This is the first PR in the series suggested by @robertgshaw2-redhat in |
| 16 | PR #37776 (see issue #37753). It intentionally only introduces the ABC; |
| 17 | follow-up PRs migrate each oracle to inherit from it. The single concrete |
| 18 | subclass shipped here (`UnquantizedMoEKernelOracle`) delegates to the |
| 19 | existing module-level functions to keep behaviour bit-identical with |
| 20 | pre-class code. |
| 21 | """ |
| 22 | |
| 23 | from abc import ABC, abstractmethod |
| 24 | from enum import Enum |
| 25 | from typing import TYPE_CHECKING, Generic, TypeVar |
| 26 | |
| 27 | import torch |
| 28 | |
| 29 | import vllm.model_executor.layers.fused_moe.modular_kernel as mk |
| 30 | from vllm.config.kernel import MoEBackend |
| 31 | from vllm.model_executor.layers.fused_moe.config import ( |
| 32 | FusedMoEConfig, |
| 33 | FusedMoEQuantConfig, |
| 34 | ) |
| 35 | |
| 36 | if TYPE_CHECKING: |
| 37 | from vllm.model_executor.layers.quantization.utils.quant_utils import QuantKey |
| 38 | |
| 39 | BackendT = TypeVar("BackendT", bound=Enum) |
| 40 | |
| 41 | |
| 42 | class MoEKernelOracle(ABC, Generic[BackendT]): |
| 43 | """Abstract base for MoE kernel-selection oracles. |
| 44 | |
| 45 | Concrete oracles MUST implement: `backend_enum_cls`, |
| 46 | `get_priority_backends`, `backend_to_kernel_cls`, `map_backend`, |
| 47 | `select_backend`, `make_kernel`. |
| 48 | |
| 49 | Concrete oracles MAY override: `convert_to_kernel_format`, |
| 50 | `make_quant_config`. The base class provides default implementations |
| 51 | that are appropriate for oracles which do not need them |
| 52 | (e.g. `make_quant_config` raises on the unquantized oracle). |
| 53 | """ |
| 54 | |
| 55 | @abstractmethod |
| 56 | def backend_enum_cls(self) -> type[BackendT]: |
| 57 | """Return the concrete `Enum` class enumerating this oracle's |
| 58 | backends (e.g. `UnquantizedMoeBackend`, `Fp8MoeBackend`).""" |
| 59 | |
| 60 | @abstractmethod |
| 61 | def get_priority_backends(self, moe_config: FusedMoEConfig) -> list[BackendT]: |
| 62 | """Return platform-appropriate backends in priority order for |
| 63 | this `moe_config`.""" |
| 64 | |
| 65 | @abstractmethod |
| 66 | def backend_to_kernel_cls( |
| 67 | self, backend: BackendT |
| 68 | ) -> list[type[mk.FusedMoEExperts]]: |
| 69 | """Map a backend enum value to its concrete `FusedMoEExperts` |
| 70 | subclasses, in selection priority order.""" |
| 71 | |
| 72 | @abstractmethod |
| 73 | def map_backend(self, runner_backend: MoEBackend) -> BackendT: |
| 74 | """Map a user-facing `MoEBackend` (from the runner config) to |
| 75 | this oracle's enum.""" |
| 76 | |
| 77 | @abstractmethod |
| 78 | def select_backend( |
| 79 | self, |
| 80 | moe_config: FusedMoEConfig, |
| 81 | weight_key: "QuantKey | None" = None, |
| 82 | activation_key: "QuantKey | None" = None, |
| 83 | ) -> tuple[BackendT, type[mk.FusedMoEExperts] | None]: |
| 84 | """Primary entry point: choose the best supported backend for |
| 85 | the given `moe_config`. |
| 86 | |
| 87 | `weight_key` / `activation_key` carry the quantization scheme of |
| 88 | the weights and activations and are consumed by quantized oracles |
| 89 | (fp8, nvfp4, int8, ...) to disambiguate backends. The unquantized |
| 90 | oracle ignores them. Subclasses with additional selection inputs |
| 91 | (e.g. int_wna16 needs `weight_bits`, fp8 needs |
| 92 | `allow_vllm_cutlass`) widen the signature in their override; a |
| 93 | per-oracle config object is the longer-term target tracked in |
| 94 | the #37753 follow-up PRs. |
| 95 | """ |
| 96 | |
| 97 | @abstractmethod |
| 98 | def make_kernel( |
| 99 | self, |
| 100 | quant_config: FusedMoEQuantConfig, |
| 101 | moe_config: FusedMoEConfig, |
| 102 | backend: BackendT, |
| 103 | experts_cls: type[mk.FusedMoEExperts], |
| 104 | routing_tables: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None, |
| 105 | ) -> mk.FusedMoEKernel: |
| 106 | """Construct the `FusedMoEKernel` (Prepare/Finalize + Experts |
| 107 | combinator) for the chosen backend.""" |
| 108 | |
| 109 | def convert_to_kernel_format( |
| 110 | self, |
| 111 | backend: BackendT, |
| 112 | moe_config: FusedMoEConfig, |
| 113 | w13_weight: torch.Tensor, |
| 114 | w2_weight: torch.Tensor, |
| 115 | ) -> tuple[torch.Tensor, torch.Tensor]: |
| 116 | """Shuffle weights into the layout expected by `backend`. |
| 117 | |
| 118 | Default implementation returns the inputs unchanged. Oracles |
| 119 | whose backends need weight permutation should override this |
| 120 | (e.g. `UnquantizedMoEKernelOracle` handles AITER and FlashInfer |
| 121 | layouts). |
| 122 | |
| 123 | `moe_config` carries MoE-layer state (e.g. `is_act_and_mul`) |
| 124 | that the conversion needs without coupling the oracle to a |
| 125 | `Module` reference. Quantized oracles whose conversion |
| 126 | additionally needs scales / zero-points / block shapes will |
| 127 | override with a wider signature (and ultimately a per-oracle |
| 128 | config object — tracked in the #37753 follow-up PRs). |
| 129 | """ |
| 130 | return w13_weight, w2_weight |
| 131 | |
| 132 | def make_quant_config(self, *args, **kwargs) -> FusedMoEQuantConfig: |
| 133 | """Build a `FusedMoEQuantConfig` for this oracle. |
| 134 | |
| 135 | Quantized oracles (fp8, nvfp4, mxfp4, ...) override this with |
| 136 | the appropriate signature for their quantization scheme. |
| 137 | Unquantized oracles inherit the default, which raises because |
| 138 | there is no quantization-specific config to build. |
| 139 | """ |
| 140 | raise NotImplementedError( |
| 141 | f"{type(self).__name__} does not implement make_quant_config; " |
| 142 | "this oracle has no quantization-specific config to build." |
| 143 | ) |