main
py 255 lines 8.54 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 """Online MXFP8 (microscaling FP8, block-32) quantization methods."""
5
6 from typing import TYPE_CHECKING
7
8 import torch
9 from torch.nn import Module
10
11 from vllm.model_executor.layers.fused_moe.config import FusedMoEConfig
12
13 if TYPE_CHECKING:
14 import vllm.model_executor.layers.fused_moe.modular_kernel as mk
15 from vllm.model_executor.layers.fused_moe import (
16 FusedMoEQuantConfig,
17 RoutedExperts,
18 )
19 from vllm.model_executor.layers.fused_moe.oracle.fp8 import Fp8MoeBackend
20
21 from vllm.model_executor.kernels.linear import init_mxfp8_linear_kernel
22 from vllm.model_executor.layers.fused_moe.oracle.mxfp8 import (
23 select_mxfp8_moe_backend,
24 )
25 from vllm.model_executor.layers.quantization.online.fp8 import (
26 OnlineLinearBase,
27 )
28 from vllm.model_executor.layers.quantization.online.moe_base import (
29 OnlineMoEMethodBase,
30 )
31 from vllm.model_executor.layers.quantization.utils.mxfp8_utils import (
32 MXFP8_BLOCK_SIZE,
33 mxfp8_e4m3_quantize,
34 )
35 from vllm.model_executor.utils import replace_parameter
36 from vllm.platforms import current_platform
37
38
39 class Mxfp8OnlineLinearMethod(OnlineLinearBase):
40 """Online MXFP8 linear method.
41 Loads bf16/fp16 checkpoints and quantizes weights to MXFP8 (microscaling
42 FP8 with block-32 scales) during weight loading.
43 """
44
45 def create_weights(
46 self,
47 layer: torch.nn.Module,
48 input_size_per_partition: int,
49 output_partition_sizes: list[int],
50 input_size: int,
51 output_size: int,
52 params_dtype: torch.dtype,
53 **extra_weight_attrs,
54 ):
55 if input_size_per_partition % MXFP8_BLOCK_SIZE != 0:
56 raise ValueError(
57 f"MXFP8 requires input_size_per_partition "
58 f"({input_size_per_partition}) to be divisible by "
59 f"{MXFP8_BLOCK_SIZE}."
60 )
61
62 super().create_weights(
63 layer,
64 input_size_per_partition,
65 output_partition_sizes,
66 input_size,
67 output_size,
68 params_dtype,
69 **extra_weight_attrs,
70 )
71
72 self.kernel = init_mxfp8_linear_kernel(weight_shape=layer.weight.shape)
73
74 def process_weights_after_loading(self, layer: Module) -> None:
75 if getattr(layer, "_already_called_process_weights_after_loading", False):
76 return
77
78 weight_fp8, weight_scale = mxfp8_e4m3_quantize(layer.weight.contiguous())
79
80 layer.input_scale = None
81 replace_parameter(layer, "weight", weight_fp8.data)
82 replace_parameter(layer, "weight_scale", weight_scale.data)
83
84 self.kernel.process_weights_after_loading(layer)
85
86 layer._already_called_process_weights_after_loading = True
87
88 def apply(
89 self,
90 layer: torch.nn.Module,
91 x: torch.Tensor,
92 bias: torch.Tensor | None = None,
93 ) -> torch.Tensor:
94 return self.kernel.apply_weights(layer, x, bias)
95
96
97 class Mxfp8OnlineMoEMethod(OnlineMoEMethodBase):
98 """MoE method for online MXFP8 (block) quantization."""
99
100 fp8_backend: "Fp8MoeBackend"
101 experts_cls: "type[mk.FusedMoEExperts] | None"
102
103 def __init__(self, *, moe: FusedMoEConfig):
104 super().__init__(moe)
105 self.weight_block_size: list[int] = [1, MXFP8_BLOCK_SIZE]
106 self.weight_scale_name = "weight_scale"
107
108 self.fp8_backend, self.experts_cls = select_mxfp8_moe_backend(config=self.moe)
109
110 def create_weights(
111 self,
112 layer: Module,
113 num_experts: int,
114 hidden_size: int,
115 intermediate_size_per_partition: int,
116 params_dtype: torch.dtype,
117 **extra_weight_attrs,
118 ):
119 if (
120 hidden_size % MXFP8_BLOCK_SIZE != 0
121 or intermediate_size_per_partition % MXFP8_BLOCK_SIZE != 0
122 ):
123 raise ValueError(
124 "Online MXFP8 MoE requires hidden/intermediate sizes divisible "
125 f"by {MXFP8_BLOCK_SIZE}."
126 )
127
128 super().create_weights(
129 layer=layer,
130 num_experts=num_experts,
131 hidden_size=hidden_size,
132 intermediate_size_per_partition=intermediate_size_per_partition,
133 params_dtype=params_dtype,
134 **extra_weight_attrs,
135 )
136
137 layer.weight_block_size = [1, MXFP8_BLOCK_SIZE]
138
139 def _quantize_mxfp8_moe_weight(
140 self, weight: torch.Tensor
141 ) -> tuple[torch.Tensor, torch.Tensor]:
142 """Batch quantization: bf16/fp16 weights -> MXFP8 (fp8 + uint8 scales)."""
143 E = weight.size(0)
144 first_q, first_s = mxfp8_e4m3_quantize(weight[0], is_sf_swizzled_layout=False)
145 # Pre-allocate the output tensors rather than stacking.
146 # This is important for consistent memory layout.
147 w_quant = torch.empty(
148 (E, *first_q.shape), dtype=first_q.dtype, device=weight.device
149 )
150 w_scales = torch.empty(
151 (E, *first_s.shape), dtype=first_s.dtype, device=weight.device
152 )
153 w_quant[0] = first_q
154 w_scales[0] = first_s
155 for i in range(1, E):
156 w_quant[i], w_scales[i] = mxfp8_e4m3_quantize(
157 weight[i], is_sf_swizzled_layout=False
158 )
159
160 return w_quant, w_scales
161
162 def _setup_kernel(
163 self,
164 layer: "RoutedExperts",
165 w13: torch.Tensor,
166 w2: torch.Tensor,
167 w13_scale: torch.Tensor,
168 w2_scale: torch.Tensor,
169 w13_input_scale: torch.Tensor | None,
170 w2_input_scale: torch.Tensor | None,
171 ) -> None:
172 from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
173 convert_to_fp8_moe_kernel_format,
174 make_fp8_moe_kernel,
175 )
176
177 # Shuffle weights to runtime format.
178 w13, w2, w13_scale, w2_scale = convert_to_fp8_moe_kernel_format(
179 fp8_backend=self.fp8_backend,
180 layer=layer,
181 w13=w13,
182 w2=w2,
183 w13_scale=w13_scale,
184 w2_scale=w2_scale,
185 w13_input_scale=w13_input_scale,
186 w2_input_scale=w2_input_scale,
187 )
188
189 replace_parameter(layer, "w13_weight", w13)
190 replace_parameter(layer, "w2_weight", w2)
191 replace_parameter(layer, f"w13_{self.weight_scale_name}", w13_scale)
192 replace_parameter(layer, f"w2_{self.weight_scale_name}", w2_scale)
193
194 self.moe_quant_config = self.get_fused_moe_quant_config(layer)
195 if self.moe_quant_config:
196 assert self.experts_cls is not None
197 self.moe_kernel = make_fp8_moe_kernel(
198 moe_quant_config=self.moe_quant_config,
199 moe_config=self.moe,
200 fp8_backend=self.fp8_backend,
201 experts_cls=self.experts_cls,
202 routing_tables=layer._expert_routing_tables(),
203 )
204
205 def get_fused_moe_quant_config(
206 self, layer: torch.nn.Module
207 ) -> "FusedMoEQuantConfig":
208 from vllm.model_executor.layers.fused_moe.oracle.fp8 import (
209 make_fp8_moe_quant_config,
210 )
211
212 w1_scale = getattr(layer, f"w13_{self.weight_scale_name}")
213 w2_scale = getattr(layer, f"w2_{self.weight_scale_name}")
214 a1_scale = layer.w13_input_scale
215 a2_scale = layer.w2_input_scale
216
217 return make_fp8_moe_quant_config(
218 fp8_backend=self.fp8_backend,
219 w1_scale=w1_scale,
220 w2_scale=w2_scale,
221 a1_scale=a1_scale,
222 a2_scale=a2_scale,
223 w1_bias=getattr(layer, "w13_bias", None),
224 w2_bias=getattr(layer, "w2_bias", None),
225 block_shape=self.weight_block_size,
226 swiglu_limit=getattr(layer, "swiglu_limit", None),
227 gemm1_alpha=getattr(layer, "swiglu_alpha", None),
228 gemm1_beta=getattr(layer, "swiglu_beta", None),
229 layer=layer,
230 )
231
232 def process_weights_after_loading(self, layer: Module) -> None:
233 if getattr(layer, "_already_called_process_weights_after_loading", False):
234 return
235
236 fp8_dtype = current_platform.fp8_dtype()
237 w13 = torch.empty_like(layer.w13_weight, dtype=fp8_dtype)
238 w2 = torch.empty_like(layer.w2_weight, dtype=fp8_dtype)
239 layer.w13_input_scale = None
240 layer.w2_input_scale = None
241
242 w13, w13_scale = self._quantize_mxfp8_moe_weight(layer.w13_weight)
243 w2, w2_scale = self._quantize_mxfp8_moe_weight(layer.w2_weight)
244
245 self._setup_kernel(
246 layer,
247 w13,
248 w2,
249 w13_scale,
250 w2_scale,
251 layer.w13_input_scale,
252 layer.w2_input_scale,
253 )
254
255 layer._already_called_process_weights_after_loading = True