main
py 274 lines 10.2 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Utility helpers for MxInt4 + FlashInfer fused-MoE path."""
4
5 import functools
6
7 import torch
8
9 import vllm.envs as envs
10 from vllm.logger import init_logger
11 from vllm.platforms import current_platform
12 from vllm.utils.flashinfer import has_flashinfer_trtllm_fused_moe
13
14 __all__ = [
15 "prepare_static_weights_for_trtllm_mxint4_moe",
16 "flashinfer_trtllm_mxint4_moe",
17 "is_flashinfer_mxint4_moe_available",
18 ]
19
20 logger = init_logger(__name__)
21
22
23 @functools.cache
24 def is_flashinfer_mxint4_moe_available() -> bool:
25 """Return `True` when FlashInfer MxInt4 kernels can be used."""
26 return (
27 envs.VLLM_USE_FLASHINFER_MOE_INT4
28 and has_flashinfer_trtllm_fused_moe()
29 and current_platform.is_cuda()
30 and current_platform.is_device_capability_family(100)
31 )
32
33
34 def prepare_static_weights_for_trtllm_mxint4_moe(
35 gemm1_weights: torch.Tensor,
36 gemm1_scales: torch.Tensor,
37 gemm2_weights: torch.Tensor,
38 gemm2_scales: torch.Tensor,
39 ) -> dict[str, torch.Tensor]:
40 """Prepare MxInt4 weights for TRT-LLM kernel.
41
42 Input:
43 gemm1_weights: [num_experts, 2*intermediate_size, hidden_size//8] int32
44 (checkpoint uint4b8 packed) or uint8 (already packed signed int4)
45 gemm1_scales: [num_experts, 2*intermediate_size, hidden_size//32] bf16
46 gemm2_weights: [num_experts, hidden_size, intermediate_size//8] int32
47 (checkpoint uint4b8 packed) or uint8 (already packed signed int4)
48 gemm2_scales: [num_experts, hidden_size, intermediate_size//32] bf16
49
50 Returns:
51 Dict with keys 'gemm1_weights', 'gemm1_scales', 'gemm2_weights',
52 'gemm2_scales' containing shuffled/packed tensors ready for kernel
53
54 """
55 from flashinfer import block_scale_interleave
56 from flashinfer.fused_moe import (
57 convert_to_block_layout,
58 )
59 from flashinfer.fused_moe.core import (
60 _maybe_get_cached_w3_w1_permute_indices,
61 get_w2_permute_indices_with_cache,
62 )
63
64 from vllm.model_executor.layers.quantization.utils.flashinfer_fp4_moe import (
65 reorder_w1w3_to_w3w1,
66 )
67 from vllm.model_executor.layers.quantization.utils.quant_utils import (
68 convert_packed_uint4b8_to_signed_int4_inplace,
69 )
70
71 device = gemm1_weights.device
72 assert gemm1_weights.ndim == 3, (
73 f"Expected a 3D gemm1_weights tensor, got {gemm1_weights.shape}"
74 )
75 assert gemm1_scales.ndim == 3, (
76 f"Expected a 3D gemm1_scales tensor, got {gemm1_scales.shape}"
77 )
78 assert gemm2_weights.ndim == 3, (
79 f"Expected a 3D gemm2_weights tensor, got {gemm2_weights.shape}"
80 )
81 assert gemm2_scales.ndim == 3, (
82 f"Expected a 3D gemm2_scales tensor, got {gemm2_scales.shape}"
83 )
84
85 # Convert checkpoint format (uint4b8 in int32) to signed int4
86 # Checkpoint stores INT4 as unsigned [0, 15], kernel expects signed [-8, 7]
87 if gemm1_weights.dtype == torch.int32 and gemm2_weights.dtype == torch.int32:
88 convert_packed_uint4b8_to_signed_int4_inplace(gemm1_weights)
89 convert_packed_uint4b8_to_signed_int4_inplace(gemm2_weights)
90
91 gemm1_weights, gemm1_scales = reorder_w1w3_to_w3w1(
92 gemm1_weights, gemm1_scales, dim=-2
93 )
94
95 _cache_permute_indices: dict[torch.Size, torch.Tensor] = {}
96 num_experts = gemm1_weights.shape[0]
97
98 # Convert quantized weights to proper formats -
99 gemm1_weights_mxint4 = gemm1_weights.view(torch.uint8)
100 assert gemm1_scales.dtype == torch.bfloat16
101 gemm2_weights_mxint4 = gemm2_weights.view(torch.uint8)
102 assert gemm2_scales.dtype == torch.bfloat16
103
104 epilogue_tile_m = 128
105 gemm1_weights_mxint4_shuffled = []
106 gemm1_scales_shuffled = []
107 gemm2_weights_mxint4_shuffled = []
108 gemm2_scales_shuffled = []
109
110 for i in range(num_experts):
111 # Calculate the permute indices for the following:
112 # 1. Reorder rows of W1 and scales for fused gated activation
113 # 2. Shuffle weights and scaling factors for transposed mma output
114 # for both w3_w1 and w2 weights and scale factors
115 permute_indices = _maybe_get_cached_w3_w1_permute_indices(
116 _cache_permute_indices,
117 gemm1_weights_mxint4[i],
118 epilogue_tile_m,
119 )
120 gemm1_weights_shuffled = gemm1_weights_mxint4[i][
121 permute_indices.to(gemm1_weights.device)
122 ].contiguous()
123 permute_sf_indices = _maybe_get_cached_w3_w1_permute_indices(
124 _cache_permute_indices,
125 gemm1_scales[i],
126 epilogue_tile_m,
127 num_elts_per_sf=32,
128 ).to(device)
129 gemm1_scales_shuffled.append(
130 block_scale_interleave(gemm1_scales[i][permute_sf_indices].contiguous())
131 )
132
133 permute_indices = get_w2_permute_indices_with_cache(
134 _cache_permute_indices,
135 gemm2_weights_mxint4[i],
136 epilogue_tile_m,
137 )
138 gemm2_weights_shuffled = gemm2_weights_mxint4[i][
139 permute_indices.to(gemm2_weights.device)
140 ].contiguous()
141
142 permute_sf_indices = get_w2_permute_indices_with_cache(
143 _cache_permute_indices,
144 gemm2_scales[i],
145 epilogue_tile_m,
146 num_elts_per_sf=16,
147 )
148 gemm2_scales_shuffled.append(
149 block_scale_interleave(
150 gemm2_scales[i][permute_sf_indices.to(gemm2_scales.device)].contiguous()
151 )
152 )
153
154 block_k = 128
155 gemm1_weights_shuffled = convert_to_block_layout(
156 gemm1_weights_shuffled.view(torch.uint8), block_k
157 )
158 gemm2_weights_shuffled = convert_to_block_layout(
159 gemm2_weights_shuffled.view(torch.uint8), block_k
160 )
161
162 gemm1_weights_mxint4_shuffled.append(gemm1_weights_shuffled)
163 gemm2_weights_mxint4_shuffled.append(gemm2_weights_shuffled)
164
165 gemm1_weights_mxint4_shuffled = torch.stack(gemm1_weights_mxint4_shuffled)
166 gemm2_weights_mxint4_shuffled = torch.stack(gemm2_weights_mxint4_shuffled)
167 gemm1_scales_shuffled = torch.stack(gemm1_scales_shuffled).view(torch.bfloat16)
168 gemm2_scales_shuffled = torch.stack(gemm2_scales_shuffled).view(torch.bfloat16)
169 return {
170 "gemm1_weights": gemm1_weights_mxint4_shuffled,
171 "gemm1_scales": gemm1_scales_shuffled,
172 "gemm2_weights": gemm2_weights_mxint4_shuffled,
173 "gemm2_scales": gemm2_scales_shuffled,
174 }
175
176
177 def flashinfer_trtllm_mxint4_moe(
178 x: torch.Tensor,
179 router_logits: torch.Tensor,
180 w13_weight_packed: torch.Tensor,
181 w13_weight_scale: torch.Tensor,
182 w2_weight_packed: torch.Tensor,
183 w2_weight_scale: torch.Tensor,
184 global_num_experts: int,
185 top_k: int,
186 intermediate_size_per_partition: int,
187 local_num_experts: int,
188 ep_rank: int = 0,
189 num_expert_group: int | None = None,
190 topk_group: int | None = None,
191 e_score_correction_bias: torch.Tensor | None = None,
192 routing_method_type: int | None = None,
193 routing_replay_out: torch.Tensor | None = None,
194 ) -> torch.Tensor:
195 """Apply FlashInfer TensorRT-LLM MxInt4 MoE kernel.
196
197 Args:
198 x: Input hidden states. dtype: bfloat16
199 router_logits: Router logits for expert selection. dtype: bfloat16/float32
200 w13_weight_packed: Packed gate+up weights. dtype: uint8
201 w13_weight_scale: Scales for gate+up weights. dtype: bfloat16
202 w2_weight_packed: Packed down weights. dtype: uint8
203 w2_weight_scale: Scales for down weights. dtype: bfloat16
204 global_num_experts: Total number of experts across all ranks
205 top_k: Number of experts to select per token
206 intermediate_size_per_partition: Intermediate size per partition
207 local_num_experts: Number of experts on this rank
208 ep_rank: Expert parallelism rank (default: 0)
209 num_expert_group: Number of expert groups (default: None -> 0)
210 topk_group: Top-k within groups (default: None -> 0)
211 routing_replay_out: Optional buffer receiving the routing decisions
212 so they can be replayed later
213 e_score_correction_bias: Optional routing bias. dtype: bfloat16
214 routing_method_type: FlashInfer RoutingMethodType enum value
215
216 Returns:
217 Output tensor from MoE layer. dtype: same as x (bfloat16)
218
219 """
220 from flashinfer import RoutingMethodType
221 from flashinfer.fused_moe import trtllm_mxint4_block_scale_moe
222
223 assert x.dtype == torch.bfloat16, f"x dtype must be bfloat16, got {x.dtype}"
224 assert w13_weight_packed.dtype == torch.uint8, (
225 f"w13_weight_packed dtype must be uint8, got {w13_weight_packed.dtype}"
226 )
227 assert w13_weight_scale.dtype == torch.bfloat16, (
228 f"w13_weight_scale dtype must be bfloat16, got {w13_weight_scale.dtype}"
229 )
230 assert w2_weight_packed.dtype == torch.uint8, (
231 f"w2_weight_packed dtype must be uint8, got {w2_weight_packed.dtype}"
232 )
233 assert w2_weight_scale.dtype == torch.bfloat16, (
234 f"w2_weight_scale dtype must be bfloat16, got {w2_weight_scale.dtype}"
235 )
236
237 routing_bias = None
238 if e_score_correction_bias is not None:
239 routing_bias = e_score_correction_bias.to(torch.bfloat16)
240
241 if routing_method_type == RoutingMethodType.DeepSeekV3:
242 router_logits = router_logits.to(torch.float32)
243
244 out = trtllm_mxint4_block_scale_moe(
245 routing_logits=router_logits,
246 routing_bias=routing_bias,
247 hidden_states=x,
248 gemm1_weights=w13_weight_packed.data,
249 gemm1_weights_scale=w13_weight_scale.data,
250 gemm1_alpha=None,
251 gemm1_beta=None,
252 gemm1_clamp_limit=None,
253 gemm2_weights=w2_weight_packed.data,
254 gemm2_weights_scale=w2_weight_scale.data,
255 num_experts=global_num_experts,
256 top_k=top_k,
257 n_group=num_expert_group if num_expert_group is not None else 0,
258 topk_group=topk_group if topk_group is not None else 0,
259 intermediate_size=intermediate_size_per_partition,
260 local_expert_offset=ep_rank * local_num_experts,
261 local_num_experts=local_num_experts,
262 routed_scaling_factor=None,
263 routing_method_type=routing_method_type,
264 enable_pdl=None,
265 do_finalize=True,
266 output=None,
267 tune_max_num_tokens=8192,
268 routing_replay_out=routing_replay_out,
269 )
270 if isinstance(out, (tuple, list)):
271 out = out[0]
272 out = out.to(x.dtype)
273
274 return out