| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from typing import Any |
| 5 | |
| 6 | import torch |
| 7 | from torch.nn import Module |
| 8 | from torch.nn.parameter import Parameter |
| 9 | |
| 10 | from vllm.config import get_current_vllm_config |
| 11 | from vllm.logger import init_logger |
| 12 | from vllm.model_executor.kernels.linear import ( |
| 13 | init_fp8_linear_kernel, |
| 14 | ) |
| 15 | from vllm.model_executor.layers.linear import ( |
| 16 | LinearBase, |
| 17 | LinearMethodBase, |
| 18 | UnquantizedLinearMethod, |
| 19 | ) |
| 20 | from vllm.model_executor.layers.quantization import QuantizationMethods |
| 21 | from vllm.model_executor.layers.quantization.base_config import ( |
| 22 | QuantizationConfig, |
| 23 | QuantizeMethodBase, |
| 24 | ) |
| 25 | from vllm.model_executor.layers.quantization.utils.marlin_utils_fp8 import ( |
| 26 | prepare_fp8_layer_for_marlin, |
| 27 | ) |
| 28 | from vllm.model_executor.layers.quantization.utils.quant_utils import ( |
| 29 | is_layer_skipped, |
| 30 | kFp8DynamicTokenSym, |
| 31 | kFp8StaticTokenSym, |
| 32 | ) |
| 33 | from vllm.model_executor.layers.quantization.utils.w8a8_utils import ( |
| 34 | normalize_e4m3fn_to_e4m3fnuz, |
| 35 | ) |
| 36 | from vllm.model_executor.parameter import ( |
| 37 | ChannelQuantScaleParameter, |
| 38 | ModelWeightParameter, |
| 39 | ) |
| 40 | from vllm.platforms import current_platform |
| 41 | |
| 42 | logger = init_logger(__name__) |
| 43 | |
| 44 | |
| 45 | class FBGEMMFp8Config(QuantizationConfig): |
| 46 | """Config class for FBGEMM Fp8.""" |
| 47 | |
| 48 | def __init__(self, ignore_list: list[str], input_scale_ub: float): |
| 49 | super().__init__() |
| 50 | self.ignore_list = ignore_list if ignore_list else [] |
| 51 | self.input_scale_ub = input_scale_ub |
| 52 | |
| 53 | # For GPUs that lack FP8 hardware support, we can leverage the Marlin |
| 54 | # kernel for fast weight-only FP8 quantization |
| 55 | self.use_marlin = not current_platform.has_device_capability(89) |
| 56 | |
| 57 | @classmethod |
| 58 | def get_name(cls) -> QuantizationMethods: |
| 59 | return "fbgemm_fp8" |
| 60 | |
| 61 | @classmethod |
| 62 | def get_supported_act_dtypes(cls) -> list[torch.dtype]: |
| 63 | return [torch.bfloat16, torch.float16] |
| 64 | |
| 65 | @classmethod |
| 66 | def get_min_capability(cls) -> int: |
| 67 | return 80 |
| 68 | |
| 69 | @classmethod |
| 70 | def get_config_filenames(cls) -> list[str]: |
| 71 | return [] |
| 72 | |
| 73 | @classmethod |
| 74 | def from_config(cls, config: dict[str, Any]) -> "FBGEMMFp8Config": |
| 75 | ignore_list = cls.get_from_keys(config, ["modules_to_not_convert"]) |
| 76 | input_scale_ub = cls.get_from_keys(config, ["activation_scale_ub"]) |
| 77 | return cls(ignore_list=ignore_list, input_scale_ub=input_scale_ub) |
| 78 | |
| 79 | def get_quant_method( |
| 80 | self, layer: torch.nn.Module, prefix: str |
| 81 | ) -> "QuantizeMethodBase | None": |
| 82 | if isinstance(layer, LinearBase): |
| 83 | if is_layer_skipped( |
| 84 | prefix=prefix, |
| 85 | ignored_layers=self.ignore_list, |
| 86 | fused_mapping=self.packed_modules_mapping, |
| 87 | ): |
| 88 | return UnquantizedLinearMethod() |
| 89 | return FBGEMMFp8LinearMethod(self) |
| 90 | return None |
| 91 | |
| 92 | |
| 93 | class FBGEMMFp8LinearMethod(LinearMethodBase): |
| 94 | def __init__(self, quant_config: FBGEMMFp8Config): |
| 95 | self.quant_config = quant_config |
| 96 | self.out_dtype = torch.get_default_dtype() |
| 97 | self.input_dtype = get_current_vllm_config().model_config.dtype |
| 98 | |
| 99 | def create_weights( |
| 100 | self, |
| 101 | layer: torch.nn.Module, |
| 102 | input_size_per_partition: int, |
| 103 | output_partition_sizes: list[int], |
| 104 | input_size: int, |
| 105 | output_size: int, |
| 106 | params_dtype: torch.dtype, |
| 107 | **extra_weight_attrs, |
| 108 | ): |
| 109 | weight_loader = extra_weight_attrs.get("weight_loader") |
| 110 | del input_size, output_size |
| 111 | output_size_per_partition = sum(output_partition_sizes) |
| 112 | |
| 113 | layer.logical_widths = output_partition_sizes |
| 114 | |
| 115 | layer.input_size_per_partition = input_size_per_partition |
| 116 | layer.output_size_per_partition = output_size_per_partition |
| 117 | layer.orig_dtype = params_dtype |
| 118 | |
| 119 | # WEIGHT |
| 120 | weight = ModelWeightParameter( |
| 121 | data=torch.empty( |
| 122 | output_size_per_partition, |
| 123 | input_size_per_partition, |
| 124 | dtype=torch.float8_e4m3fn, |
| 125 | ), |
| 126 | input_dim=1, |
| 127 | output_dim=0, |
| 128 | weight_loader=weight_loader, |
| 129 | ) |
| 130 | layer.register_parameter("weight", weight) |
| 131 | |
| 132 | # WEIGHT SCALE |
| 133 | weight_scale = ChannelQuantScaleParameter( |
| 134 | data=torch.empty((sum(output_partition_sizes), 1), dtype=torch.float32), |
| 135 | output_dim=0, |
| 136 | weight_loader=weight_loader, |
| 137 | ) |
| 138 | weight_scale[:] = torch.finfo(torch.float32).min |
| 139 | layer.register_parameter("weight_scale", weight_scale) |
| 140 | |
| 141 | # INPUT SCALE UPPER BOUND |
| 142 | input_scale_ub = torch.nn.Parameter( |
| 143 | torch.tensor((self.quant_config.input_scale_ub), dtype=torch.float32), |
| 144 | requires_grad=False, |
| 145 | ) |
| 146 | layer.input_scale_ub = input_scale_ub |
| 147 | |
| 148 | self.fp8_linear = init_fp8_linear_kernel( |
| 149 | activation_quant_key=kFp8DynamicTokenSym, |
| 150 | weight_quant_key=kFp8StaticTokenSym, |
| 151 | weight_shape=layer.weight.shape, |
| 152 | input_dtype=self.input_dtype, |
| 153 | out_dtype=self.out_dtype, |
| 154 | module_name=self.__class__.__name__, |
| 155 | ) |
| 156 | |
| 157 | def process_weights_after_loading(self, layer: Module) -> None: |
| 158 | # required by torch.compile |
| 159 | layer.weight_scale = Parameter(layer.weight_scale.data, requires_grad=False) |
| 160 | layer.weight = Parameter(layer.weight.data, requires_grad=False) |
| 161 | |
| 162 | weight = layer.weight |
| 163 | |
| 164 | if current_platform.is_fp8_fnuz(): |
| 165 | weight, weight_scale, input_scale = normalize_e4m3fn_to_e4m3fnuz( |
| 166 | weight=weight, weight_scale=layer.weight_scale, input_scale=None |
| 167 | ) |
| 168 | if input_scale is not None: |
| 169 | layer.input_scale = Parameter(input_scale, requires_grad=False) |
| 170 | layer.weight_scale = Parameter(weight_scale, requires_grad=False) |
| 171 | |
| 172 | layer.weight = Parameter(weight.t(), requires_grad=False) |
| 173 | if self.quant_config.use_marlin: |
| 174 | prepare_fp8_layer_for_marlin(layer) |
| 175 | # Activations not quantized for marlin. |
| 176 | del layer.input_scale_ub |
| 177 | |
| 178 | self.fp8_linear.process_weights_after_loading(layer) |
| 179 | |
| 180 | def apply( |
| 181 | self, |
| 182 | layer: torch.nn.Module, |
| 183 | x: torch.Tensor, |
| 184 | bias: torch.Tensor | None = None, |
| 185 | ) -> torch.Tensor: |
| 186 | return self.fp8_linear.apply_weights(layer, x, bias) |