# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import torch from torch._subclasses.fake_tensor import FakeTensor from vllm.platforms import current_platform from vllm.triton_utils import tl, triton @triton.jit def moe_fused_mul_sum_kernel( inputs_ptr, topk_weights_ptr, outputs_ptr, top_ids_ptr, expert_map_ptr, num_tokens, stride_m, has_expert_map: tl.constexpr, top_k: tl.constexpr, size: tl.constexpr, BLOCK_M: tl.constexpr, BLOCK_K: tl.constexpr, ): pid_k = tl.program_id(0) pid_m = tl.program_id(1) offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M) offs_k = pid_k * BLOCK_K + tl.arange(0, BLOCK_K) m_mask = offs_m < num_tokens k_mask = offs_k < size mask = m_mask[:, None] & k_mask[None, :] a_base = inputs_ptr + (offs_m * stride_m)[:, None] + offs_k[None, :] b_base = topk_weights_ptr + offs_m * top_k acc = tl.zeros((BLOCK_M, BLOCK_K), dtype=tl.float32) for n in tl.static_range(top_k): b_val = tl.load(b_base + n, mask=m_mask, other=0.0).to(tl.float32) if has_expert_map: id_val = tl.load(top_ids_ptr + offs_m * top_k + n, mask=m_mask, other=0) expert_mask = tl.load(expert_map_ptr + id_val) >= 0 a_vec = tl.load( a_base + n * size, mask=mask & expert_mask[:, None], other=0.0, ).to(tl.float32) else: a_vec = tl.load( a_base + n * size, mask=mask, other=0.0, ).to(tl.float32) acc += a_vec * b_val[:, None] out_ptrs = outputs_ptr + (offs_m * size)[:, None] + offs_k[None, :] tl.store( out_ptrs, acc.to(outputs_ptr.dtype.element_ty), mask=mask, ) def _heuristic_config( num_tokens: int, top_k: int, size: int, element_size: int, ): is_fp32 = element_size > 2 is_sm90_plus = current_platform.has_device_capability(90) is_sm80_before = not current_platform.has_device_capability(80) if current_platform.has_device_capability(90): # SM90/SM100+: prefer small tiles + many CTAs. if is_fp32: BLOCK_M = 1 if num_tokens <= 4 else 2 else: if num_tokens <= 4: BLOCK_M = 1 elif num_tokens <= 128: BLOCK_M = 2 else: BLOCK_M = 4 elif is_fp32: if num_tokens <= 4: BLOCK_M = 1 elif num_tokens <= 32: BLOCK_M = 2 elif num_tokens <= 128: BLOCK_M = 4 else: BLOCK_M = 4 else: if num_tokens <= 4: BLOCK_M = 1 elif num_tokens <= 32: BLOCK_M = 2 elif num_tokens <= 128: BLOCK_M = 4 elif num_tokens <= 1024: BLOCK_M = 16 else: BLOCK_M = 8 if is_fp32: max_block_k = 256 elif is_sm80_before or is_sm90_plus: max_block_k = 512 else: max_block_k = 1024 BLOCK_K = min(triton.next_power_of_2(size), max_block_k) BLOCK_K = max(BLOCK_K, 256) total = BLOCK_M * BLOCK_K if is_fp32: num_warps = max(8, min(16, total // 64)) else: num_warps = max(4, min(16, total // 256)) if is_sm80_before: num_warps = min(num_warps, 8) num_stages = 2 elif is_sm90_plus: num_warps = min(num_warps, 8) num_stages = 4 if total <= 2048 else 2 else: num_stages = 4 if total <= 2048 else 2 return BLOCK_M, BLOCK_K, num_warps, num_stages def moe_fused_mul_sum( inputs: torch.Tensor, topk_weights: torch.Tensor, outputs: torch.Tensor | None = None, topk_ids: torch.Tensor | None = None, expert_map: torch.Tensor | None = None, ) -> torch.Tensor: """ Fused kernel for MoE (Mixture of Experts) to perform weighted summation of expert outputs. Args: inputs: The output from experts. Shape: (num_tokens, top_k, hidden_size). topk_weights: The weights assigned to each expert for each token. Shape: (num_tokens, top_k). outputs: Optional pre-allocated output tensor. Shape: (num_tokens, hidden_size). topk_ids: Optional indices of the top-k experts. Used when `expert_map` is provided. Shape: (num_tokens, top_k). expert_map: Optional mapping for Expert Parallelism. A value < 0 indicates an invalid token/expert pair that will be skipped. Returns: The fused weighted sum of expert outputs. Shape: (num_tokens, hidden_size). """ assert inputs.ndim == 3 assert topk_weights.ndim == 2 assert inputs.is_contiguous() assert topk_weights.is_contiguous() assert inputs.dtype in (torch.float32, torch.float16, torch.bfloat16) assert topk_weights.dtype in (torch.float32, torch.float16, torch.bfloat16) num_tokens, top_k, size = inputs.shape output_shape = (num_tokens, size) if outputs is None: outputs = torch.empty(output_shape, dtype=inputs.dtype, device=inputs.device) assert outputs.shape == output_shape assert topk_weights.shape == (num_tokens, top_k) if not isinstance(inputs, FakeTensor): BLOCK_M, BLOCK_K, num_warps, num_stages = _heuristic_config( num_tokens, top_k, size, inputs.element_size(), ) grid = (triton.cdiv(size, BLOCK_K), triton.cdiv(num_tokens, BLOCK_M)) moe_fused_mul_sum_kernel[grid]( inputs, topk_weights, outputs, topk_ids, expert_map, num_tokens, top_k * size, expert_map is not None, top_k, size, BLOCK_M, BLOCK_K, num_warps=num_warps, num_stages=num_stages, ) return outputs