|
| 1 | +# Copyright 2026 FlagOS Contributors |
| 2 | +# |
| 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +# you may not use this file except in compliance with the License. |
| 5 | +# You may obtain a copy of the License at |
| 6 | +# |
| 7 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +# |
| 9 | +# Unless required by applicable law or agreed to in writing, software |
| 10 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +# See the License for the specific language governing permissions and |
| 13 | +# limitations under the License. |
| 14 | + |
| 15 | +"""PPU-native DeepGEMM BF16 unquantized MoE experts (env-gated). |
| 16 | +
|
| 17 | +Opt-in via ``VLLM_FL_MOE=deepgemm`` (see ``vllm_fl.utils.use_deepgemm_moe``). |
| 18 | +Replaces the default FlagGems Triton ``fused_moe`` expert compute with |
| 19 | +``deep_gemm``'s grouped BF16 GEMM — the same kernels the vendor's native vLLM |
| 20 | +0.19 build used (``m_grouped_gemm_bf16_bf16_bf16_nt`` / ``..._gemv``). |
| 21 | +
|
| 22 | +Uses the **nopad** grouped GEMM with a compact (block_align=1) permute: each |
| 23 | +expert's rows are packed with NO 128-row padding, and small-M decode auto- |
| 24 | +dispatches to the GEMV kernel. (The contiguous/128-aligned layout wastes ~128x |
| 25 | +compute per active expert on sparse decode — do NOT use it here.) |
| 26 | +
|
| 27 | +Pipeline (BF16, no FP8 scales): |
| 28 | + deepgemm_moe_permute(block_align=1) → nopad GEMM1 → silu_and_mul |
| 29 | + → nopad GEMM2 → weighted unpermute+reduce (ep_gather) |
| 30 | +
|
| 31 | +Permute/gather are vendor/vLLM Triton kernels (CUDA-graph safe); ``m_rows`` |
| 32 | +(exact per-expert token counts) is fed to the nopad kernel so no host sync / |
| 33 | +internal bincount is needed. |
| 34 | +""" |
| 35 | + |
| 36 | +import deep_gemm |
| 37 | +import torch |
| 38 | +from deep_gemm.deep_gemm_tuner.deepgemm_tools import deepgemm_moe_permute |
| 39 | + |
| 40 | +import vllm.model_executor.layers.fused_moe.modular_kernel as mk |
| 41 | +from vllm.model_executor.layers.fused_moe.activation import MoEActivation |
| 42 | +from vllm.model_executor.layers.fused_moe.deep_gemm_utils import ( |
| 43 | + compute_aligned_M, |
| 44 | + ep_gather, |
| 45 | +) |
| 46 | +from vllm.model_executor.layers.fused_moe.fused_moe import TritonExperts |
| 47 | +from vllm.model_executor.layers.fused_moe.utils import _resize_cache |
| 48 | + |
| 49 | +from vllm_fl.ops.fused_moe.activation import apply_moe_activation |
| 50 | + |
| 51 | + |
| 52 | +class DeepGemmExpertsFL(TritonExperts): |
| 53 | + """OOT unquantized BF16 MoE experts backed by deep_gemm nopad grouped GEMM. |
| 54 | +
|
| 55 | + Subclasses ``TritonExperts`` to inherit ``moe_problem_size``, |
| 56 | + ``adjust_N_for_activation`` and the ``TopKWeightAndReduceNoOP`` finalize |
| 57 | + contract; overrides ``workspace_shapes`` (compact M_sum = M*topk) and |
| 58 | + ``apply``. |
| 59 | + """ |
| 60 | + |
| 61 | + def workspace_shapes( |
| 62 | + self, |
| 63 | + M: int, |
| 64 | + N: int, |
| 65 | + K: int, |
| 66 | + topk: int, |
| 67 | + global_num_experts: int, |
| 68 | + local_num_experts: int, |
| 69 | + expert_tokens_meta: "mk.ExpertTokensMetadata | None", |
| 70 | + activation: MoEActivation, |
| 71 | + ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...]]: |
| 72 | + # Compact layout (block_align=1): no per-expert 128-row padding. |
| 73 | + M_sum = compute_aligned_M(M, topk, local_num_experts, 1, expert_tokens_meta) |
| 74 | + activation_out_dim = self.adjust_N_for_activation(N, activation) |
| 75 | + workspace1 = (M_sum, max(activation_out_dim, K)) |
| 76 | + workspace2 = (M_sum, max(N, K)) |
| 77 | + output = (M, K) |
| 78 | + return (workspace1, workspace2, output) |
| 79 | + |
| 80 | + def apply( |
| 81 | + self, |
| 82 | + output: torch.Tensor, |
| 83 | + hidden_states: torch.Tensor, |
| 84 | + w1: torch.Tensor, |
| 85 | + w2: torch.Tensor, |
| 86 | + topk_weights: torch.Tensor, |
| 87 | + topk_ids: torch.Tensor, |
| 88 | + activation: MoEActivation, |
| 89 | + global_num_experts: int, |
| 90 | + expert_map: torch.Tensor | None, |
| 91 | + a1q_scale: torch.Tensor | None, |
| 92 | + a2_scale: torch.Tensor | None, |
| 93 | + workspace13: torch.Tensor, |
| 94 | + workspace2: torch.Tensor, |
| 95 | + expert_tokens_meta: "mk.ExpertTokensMetadata | None", |
| 96 | + apply_router_weight_on_input: bool, |
| 97 | + ): |
| 98 | + assert hidden_states.dtype == torch.bfloat16, ( |
| 99 | + "DeepGemmExpertsFL only supports bf16 unquantized MoE" |
| 100 | + ) |
| 101 | + assert hidden_states.is_contiguous() |
| 102 | + assert expert_map is None, ( |
| 103 | + "DeepGemmExpertsFL does not support expert parallelism (expert_map)" |
| 104 | + ) |
| 105 | + |
| 106 | + a1 = hidden_states # [M, K] |
| 107 | + M, K = a1.shape |
| 108 | + local_num_experts, N, K_w = w1.shape # w1: [E, 2I, K] |
| 109 | + assert K_w == K |
| 110 | + |
| 111 | + # Kernels use -1 for invalid ids -> topk_ids must be signed (router: int32). |
| 112 | + if not topk_ids.dtype.is_signed: |
| 113 | + topk_ids = topk_ids.to(torch.int32) |
| 114 | + |
| 115 | + # ---- compact permute: pack tokens per-expert (no 128 padding) ---- |
| 116 | + # returns: a1_perm [M_sum, K], m_indices [M_sum], inv_perm [M, topk], |
| 117 | + # m_rows (expert_num_tokens) [E]. M_sum == M * topk. |
| 118 | + a1_perm, _scale_out, m_indices, inv_perm, m_rows = deepgemm_moe_permute( |
| 119 | + a1, None, topk_ids, local_num_experts, block_align=1, block_k=K |
| 120 | + ) |
| 121 | + M_sum = a1_perm.size(0) |
| 122 | + |
| 123 | + # ---- grouped GEMM 1 (nopad): [M_sum, K] x [E, 2I, K]^T -> [M_sum, 2I] ---- |
| 124 | + mm1_out = _resize_cache(workspace2, (M_sum, N)) |
| 125 | + deep_gemm.m_grouped_gemm_bf16_bf16_bf16_nt_nopad( |
| 126 | + a1_perm, w1, mm1_out, m_indices, m_rows |
| 127 | + ) |
| 128 | + |
| 129 | + # ---- activation: silu_and_mul -> [M_sum, I] ---- |
| 130 | + activation_out_dim = self.adjust_N_for_activation(N, activation) |
| 131 | + act_out = _resize_cache(workspace13, (M_sum, activation_out_dim)) |
| 132 | + apply_moe_activation(activation, act_out, mm1_out.view(-1, N)) |
| 133 | + |
| 134 | + # ---- grouped GEMM 2 (nopad): [M_sum, I] x [E, K, I]^T -> [M_sum, K] ---- |
| 135 | + mm2_out = _resize_cache(workspace2, (M_sum, K)) |
| 136 | + deep_gemm.m_grouped_gemm_bf16_bf16_bf16_nt_nopad( |
| 137 | + act_out, w2, mm2_out, m_indices, m_rows |
| 138 | + ) |
| 139 | + |
| 140 | + # ---- weighted unpermute + reduce over topk -> output [M, K] ---- |
| 141 | + if apply_router_weight_on_input: |
| 142 | + topk_weights = torch.ones_like(topk_weights) |
| 143 | + ep_gather( |
| 144 | + input_tensor=mm2_out, |
| 145 | + recv_topk_ids=topk_ids, |
| 146 | + recv_topk_weight=topk_weights, |
| 147 | + input_index=inv_perm, |
| 148 | + expert_map=None, |
| 149 | + output_tensor=output, |
| 150 | + ) |
0 commit comments