Skip to content

vllm.model_executor.models.transformers.fusers.moe

MoE fuser: route an HF MoE block through FusedMoE with vLLM's own routing.

Classes:

  • MoEBlockFuser

    Fuser for MoE block experts, gate and shared_experts (optional).

  • SharedExpertMLP

    Wraps an HF shared expert, applying the output gating it is paired with.

MoEBlockFuser dataclass

Fuser for MoE block experts, gate and shared_experts (optional).

Methods:

  • gate

    Rebuild the HF gate as a GateLinear for vLLM's fused MoE.

  • rewrite_forward

    Rewrite moe_block.forward to route through vLLM's fused MoE.

  • shared_experts

    Build the HF shared expert (and its optional gate)

Source code in vllm/model_executor/models/transformers/fusers/moe.py
@dataclass
class MoEBlockFuser:
    """Fuser for MoE block `experts`, `gate` and `shared_experts` (optional)."""

    gate_name: str
    scoring_func: str
    shared_name: str | None
    shared_gate_name: str | None
    router_dtype: torch.dtype | None = None

    @staticmethod
    def _match_router(gate: nn.Module) -> tuple[str, torch.dtype | None] | None:
        """Matches `topk(score(linear(x)))`, `score` being `softmax`/`sigmoid`.

        Returns the scoring function and the dtype the router computes in."""
        state = {name for name, _ in named_state(gate)}
        if "weight" not in state or state - {"weight", "e_score_correction_bias"}:
            return None
        graph = trace(gate)
        if graph is None:
            return None
        # The routing top-k is the last one; any earlier one scores expert groups.
        topks = [node for node in graph.nodes if is_op(node, "topk")]
        if not topks:
            return None
        topk = topks[-1]
        # Exactly one scoring op upstream of the top-k, fed (transitively) by a linear.
        scorers = [
            n
            for n in _reaches(topk, "all_input_nodes")
            if is_op(n, "softmax") or is_op(n, "sigmoid")
        ]
        if len(scorers) != 1:
            return None
        scorer = scorers[0]
        logits_cone = _reaches(scorer, "all_input_nodes")
        if not any(is_op(n, "linear") for n in logits_cone):
            return None
        scoring_func = "softmax" if is_op(scorer, "softmax") else "sigmoid"
        return scoring_func, _forced_dtype(logits_cone)

    @staticmethod
    def _match_shared_experts(
        graph: fx.Graph, experts: str
    ) -> tuple[str | None, str | None]:
        """Detects the shared expert and its optional gate by dataflow."""
        experts_predicate = lambda n: n.op == "call_module" and n.target == experts
        if (experts_node := find_node(graph, experts_predicate)) is None:
            return None, None
        from_experts = _reaches(experts_node, "users")
        for add in graph.nodes:
            if not is_op(add, "add"):
                continue
            operands = [a for a in add.args if isinstance(a, fx.Node)]
            # Exactly one side is the experts' output; the other is the shared path.
            sides = [a in from_experts for a in operands]
            if len(operands) != 2 or sides.count(True) != 1:
                continue
            cone = _reaches(operands[sides.index(False)], "all_input_nodes")
            modules = [n for n in cone if n.op == "call_module" and n.target != experts]
            # A sigmoid wrapping one of those modules marks the shared-expert gate.
            gate = next(
                (
                    src
                    for n in cone
                    if is_op(n, "sigmoid")
                    and isinstance(src := peel(n.args[0]), fx.Node)
                    and src in modules
                ),
                None,
            )
            shared = [n for n in modules if n is not gate]
            if len(shared) != 1:
                return None, None
            return shared[0].target, (gate.target if gate is not None else None)
        return None, None

    @classmethod
    def match(cls, moe_block: nn.Module, experts_name: str) -> "MoEBlockFuser | None":
        # Standard MoE block returns a single tensor.
        if _returns_tuple(type(moe_block)):
            return None
        # Router: the child that scores + top-k selects.
        gate_name = scoring_func = router_dtype = None
        for name, child in moe_block.named_children():
            if (
                name != experts_name
                and (router := cls._match_router(child)) is not None
            ):
                gate_name = name
                scoring_func, router_dtype = router
                break
        if gate_name is None or scoring_func is None:
            return None
        # Shared expert: a child the block adds to the experts' output.
        shared_name = shared_gate_name = None
        others = [
            n
            for n, _ in moe_block.named_children()
            if n not in {experts_name, gate_name}
        ]
        if others:
            graph = trace(moe_block)
            if graph is None:
                return None
            shared_name, shared_gate_name = cls._match_shared_experts(
                graph, experts_name
            )
            if shared_gate_name is not None and not _is_scalar_gate(
                getattr(moe_block, shared_gate_name)
            ):
                return None
        # Fail closed: `rewrite_forward` runs only the experts and the detected
        # shared expert, so any other stateful child would be dropped.
        accounted = {experts_name, gate_name, shared_name, shared_gate_name}
        for name, child in moe_block.named_children():
            if name not in accounted and next(named_state(child), None) is not None:
                return None
        return cls(gate_name, scoring_func, shared_name, shared_gate_name, router_dtype)

    def gate(
        self, moe_block: nn.Module, prefix: str, out_dtype: torch.dtype | None = None
    ) -> GateLinear:
        """Rebuild the HF gate as a `GateLinear` for vLLM's fused MoE."""
        hf_gate = getattr(moe_block, self.gate_name)
        num_experts, hidden_size = hf_gate.weight.shape
        gate = GateLinear(
            hidden_size,
            num_experts,
            bias=False,
            out_dtype=out_dtype or self.router_dtype,
            prefix=maybe_prefix(prefix, self.gate_name),
        )
        if (bias := getattr(hf_gate, "e_score_correction_bias", None)) is not None:
            gate.register_buffer("e_score_correction_bias", bias)
        setattr(moe_block, self.gate_name, gate)
        return gate

    def shared_experts(
        self, moe_block: nn.Module, prefix: str
    ) -> SharedExpertMLP | None:
        """Build the HF shared expert (and its optional gate)
        as a `SharedExpertMLP` for vLLM's fused MoE."""
        if self.shared_name is None:
            return None
        shared_experts = getattr(moe_block, self.shared_name)
        gate = None
        if self.shared_gate_name is not None:
            hf_gate = getattr(moe_block, self.shared_gate_name)
            gate = ReplicatedLinear(
                hf_gate.in_features,
                hf_gate.out_features,
                bias=hf_gate.bias is not None,
                prefix=maybe_prefix(prefix, self.shared_gate_name),
            )
            setattr(moe_block, self.shared_gate_name, gate)
        return SharedExpertMLP(shared_experts, gate)

    def rewrite_forward(self, moe_block: nn.Module) -> None:
        """Rewrite `moe_block.forward` to route through vLLM's fused MoE."""
        moe_block.forward = types.MethodType(_moe_block_forward, moe_block)

_match_router(gate) staticmethod

Matches topk(score(linear(x))), score being softmax/sigmoid.

Returns the scoring function and the dtype the router computes in.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
@staticmethod
def _match_router(gate: nn.Module) -> tuple[str, torch.dtype | None] | None:
    """Matches `topk(score(linear(x)))`, `score` being `softmax`/`sigmoid`.

    Returns the scoring function and the dtype the router computes in."""
    state = {name for name, _ in named_state(gate)}
    if "weight" not in state or state - {"weight", "e_score_correction_bias"}:
        return None
    graph = trace(gate)
    if graph is None:
        return None
    # The routing top-k is the last one; any earlier one scores expert groups.
    topks = [node for node in graph.nodes if is_op(node, "topk")]
    if not topks:
        return None
    topk = topks[-1]
    # Exactly one scoring op upstream of the top-k, fed (transitively) by a linear.
    scorers = [
        n
        for n in _reaches(topk, "all_input_nodes")
        if is_op(n, "softmax") or is_op(n, "sigmoid")
    ]
    if len(scorers) != 1:
        return None
    scorer = scorers[0]
    logits_cone = _reaches(scorer, "all_input_nodes")
    if not any(is_op(n, "linear") for n in logits_cone):
        return None
    scoring_func = "softmax" if is_op(scorer, "softmax") else "sigmoid"
    return scoring_func, _forced_dtype(logits_cone)

_match_shared_experts(graph, experts) staticmethod

Detects the shared expert and its optional gate by dataflow.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
@staticmethod
def _match_shared_experts(
    graph: fx.Graph, experts: str
) -> tuple[str | None, str | None]:
    """Detects the shared expert and its optional gate by dataflow."""
    experts_predicate = lambda n: n.op == "call_module" and n.target == experts
    if (experts_node := find_node(graph, experts_predicate)) is None:
        return None, None
    from_experts = _reaches(experts_node, "users")
    for add in graph.nodes:
        if not is_op(add, "add"):
            continue
        operands = [a for a in add.args if isinstance(a, fx.Node)]
        # Exactly one side is the experts' output; the other is the shared path.
        sides = [a in from_experts for a in operands]
        if len(operands) != 2 or sides.count(True) != 1:
            continue
        cone = _reaches(operands[sides.index(False)], "all_input_nodes")
        modules = [n for n in cone if n.op == "call_module" and n.target != experts]
        # A sigmoid wrapping one of those modules marks the shared-expert gate.
        gate = next(
            (
                src
                for n in cone
                if is_op(n, "sigmoid")
                and isinstance(src := peel(n.args[0]), fx.Node)
                and src in modules
            ),
            None,
        )
        shared = [n for n in modules if n is not gate]
        if len(shared) != 1:
            return None, None
        return shared[0].target, (gate.target if gate is not None else None)
    return None, None

gate(moe_block, prefix, out_dtype=None)

Rebuild the HF gate as a GateLinear for vLLM's fused MoE.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def gate(
    self, moe_block: nn.Module, prefix: str, out_dtype: torch.dtype | None = None
) -> GateLinear:
    """Rebuild the HF gate as a `GateLinear` for vLLM's fused MoE."""
    hf_gate = getattr(moe_block, self.gate_name)
    num_experts, hidden_size = hf_gate.weight.shape
    gate = GateLinear(
        hidden_size,
        num_experts,
        bias=False,
        out_dtype=out_dtype or self.router_dtype,
        prefix=maybe_prefix(prefix, self.gate_name),
    )
    if (bias := getattr(hf_gate, "e_score_correction_bias", None)) is not None:
        gate.register_buffer("e_score_correction_bias", bias)
    setattr(moe_block, self.gate_name, gate)
    return gate

rewrite_forward(moe_block)

Rewrite moe_block.forward to route through vLLM's fused MoE.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def rewrite_forward(self, moe_block: nn.Module) -> None:
    """Rewrite `moe_block.forward` to route through vLLM's fused MoE."""
    moe_block.forward = types.MethodType(_moe_block_forward, moe_block)

shared_experts(moe_block, prefix)

Build the HF shared expert (and its optional gate) as a SharedExpertMLP for vLLM's fused MoE.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def shared_experts(
    self, moe_block: nn.Module, prefix: str
) -> SharedExpertMLP | None:
    """Build the HF shared expert (and its optional gate)
    as a `SharedExpertMLP` for vLLM's fused MoE."""
    if self.shared_name is None:
        return None
    shared_experts = getattr(moe_block, self.shared_name)
    gate = None
    if self.shared_gate_name is not None:
        hf_gate = getattr(moe_block, self.shared_gate_name)
        gate = ReplicatedLinear(
            hf_gate.in_features,
            hf_gate.out_features,
            bias=hf_gate.bias is not None,
            prefix=maybe_prefix(prefix, self.shared_gate_name),
        )
        setattr(moe_block, self.shared_gate_name, gate)
    return SharedExpertMLP(shared_experts, gate)

SharedExpertMLP

Bases: Module

Wraps an HF shared expert, applying the output gating it is paired with.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
class SharedExpertMLP(nn.Module):
    """Wraps an HF shared expert, applying the output gating it is paired with."""

    def __init__(self, shared_experts: nn.Module, gate: nn.Module | None = None):
        super().__init__()
        self.shared_experts = shared_experts
        self.gate = gate

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        out = self.shared_experts(hidden_states)
        if self.gate is not None:
            out = torch.sigmoid(self.gate(hidden_states)[0]) * out
        return out

_forced_dtype(nodes)

The floating dtype nodes cast to, if any.

Computations that must run in higher precision say so in their forward (e.g. hidden_states.type(torch.float32)), so the dtype is readable from the graph even when the config does not name it.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def _forced_dtype(nodes: Iterable[fx.Node]) -> torch.dtype | None:
    """The floating dtype `nodes` cast to, if any.

    Computations that must run in higher precision say so in their forward (e.g.
    `hidden_states.type(torch.float32)`), so the dtype is readable from the graph
    even when the config does not name it."""
    for node in nodes:
        if node.op not in ("call_method", "call_function"):
            continue
        name = str(node.target).rsplit(".", 1)[-1]
        if name == "float":
            return torch.float32
        if name in ("to", "type"):
            for arg in (*node.args[1:], *node.kwargs.values()):
                if isinstance(arg, torch.dtype) and arg.is_floating_point:
                    return arg
    return None

_is_scalar_gate(module)

A linear projecting to a single logit (the shared-expert sigmoid gate).

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def _is_scalar_gate(module: nn.Module) -> bool:
    """A linear projecting to a single logit (the shared-expert sigmoid gate)."""
    weight = getattr(module, "weight", None)
    return (
        isinstance(module, nn.Linear)
        and weight is not None
        and weight.ndim == 2
        and weight.shape[0] == 1
    )

_moe_block_forward(self, hidden_states)

Standard MoE block forward.

Routing and any shared experts are handled inside self.experts: MoERunner.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def _moe_block_forward(self: nn.Module, hidden_states: torch.Tensor) -> torch.Tensor:
    """Standard MoE block forward.

    Routing and any shared experts are handled inside `self.experts: MoERunner`."""
    orig_shape = hidden_states.shape
    hidden_states = hidden_states.reshape(-1, orig_shape[-1])
    num_tokens = hidden_states.shape[0]
    is_sequence_parallel = self.experts.moe_config.is_sequence_parallel
    if is_sequence_parallel:
        hidden_states = sequence_parallel_chunk(hidden_states)
    out = self.experts(hidden_states, router_logits=hidden_states)
    if is_sequence_parallel:
        out = tensor_model_parallel_all_gather(out, 0)[:num_tokens]
    return out.reshape(orig_shape)

_own_returns(node)

return statements in node's own scope, not in nested functions.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def _own_returns(node: ast.AST) -> Iterator[ast.Return]:
    """`return` statements in `node`'s own scope, not in nested functions."""
    stack = list(ast.iter_child_nodes(node))
    while stack:
        child = stack.pop()
        if isinstance(child, ast.Return):
            yield child
        elif not isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)):
            stack.extend(ast.iter_child_nodes(child))

_reaches(node, key)

Returns the set of nodes reachable from node by following key edges.

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def _reaches(node: fx.Node, key: str) -> set[fx.Node]:
    """Returns the set of nodes reachable from `node` by following `key` edges."""
    seen: set[fx.Node] = set()
    stack = [node]
    while stack:
        n = stack.pop()
        if n in seen:
            continue
        seen.add(n)
        stack.extend(getattr(n, key))
    return seen

_returns_tuple(cls)

Does cls.forward() return a tuple?

Source code in vllm/model_executor/models/transformers/fusers/moe.py
def _returns_tuple(cls: type[nn.Module]) -> bool:
    """Does `cls.forward()` return a tuple?"""
    try:
        source = textwrap.dedent(inspect.getsource(inspect.unwrap(cls.forward)))
        forward = ast.parse(source).body[0]
    except (OSError, SyntaxError, TypeError, IndexError):
        return True
    # Names bound to a tuple literal, e.g. `out = hidden, logits` then `return out`.
    tuple_names = {
        target.id
        for node in ast.walk(forward)
        if isinstance(node, ast.Assign) and isinstance(node.value, ast.Tuple)
        for target in node.targets
        if isinstance(target, ast.Name)
    }

    def yields_tuple(value: ast.expr | None) -> bool:
        if isinstance(value, ast.Tuple):
            return True
        if isinstance(value, ast.Name):
            return value.id in tuple_names
        if isinstance(value, ast.IfExp):
            return yields_tuple(value.body) or yields_tuple(value.orelse)
        return False

    return any(yields_tuple(ret.value) for ret in _own_returns(forward))