# Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import copy import json from dataclasses import dataclass quant_inference_mapping = {"avg": "abs_max", "abs_max_channel_wise": "abs_max_channel_wise", "abs_max": "abs_max"} fp8_format_mapping = { "hybrid": {"weight": "float8_e4m3fn", "activation": "float8_e4m3fn", "grad_output": "float8_e5m2"}, "e4m3": {"weight": "float8_e4m3fn", "activation": "float8_e4m3fn", "grad_output": "float8_e4m3fn"}, } @dataclass class QuantizationConfig: """ This is the configuration class to store quantization configuration. Args: weight_quantize_algo: Weight quantization algorithm. quant_type: Quantization type applied to weight and activation, weight may still keep in float tensor. shift: Whether the model applied the shift strategy. smooth: Whether the model applied the smooth strategy. shift_smooth_all_linears: Whether the model applied shift or smooth strategy for all linears. quant_round_type: The quant round type, 0:-rounding to nearest ties to even, 1: -rounding to nearest ties away from zero. llm_int8_threshold: The threshold for llm.int8 quantization. qlora_weight_double_quant: Whether quant weight scale. qlora_weight_blocksize: Block size for weight quantization. qlora_weight_double_quant_block_size: Block size for quant_scale of weight quant_scale. weight_quant_method: The method for weight quantization. act_quant_method: The method for activation quantization. apply_online_actscale_step: Use online (per-step) activation scales for the first N steps. During these steps, activation scales are also collected to compute their mean for later use. """ def __init__( self, weight_quantize_algo=None, quant_type=None, shift=False, smooth=False, shift_smooth_all_linears=False, quant_round_type=0, llm_int8_threshold=6.0, qlora_weight_double_quant=False, qlora_weight_blocksize=64, qlora_weight_double_quant_block_size=256, weight_quant_method="abs_max_channel_wise", act_quant_method="abs_max", activation_scheme=None, fmt=None, quant_method=None, weight_block_size=None, dtype=None, ignore_modules=None, group_size=-1, apply_hadamard=False, hadamard_block_size=32, quant_input_grad=False, quant_weight_grad=False, apply_online_actscale_step=200, actscale_moving_rate=0.01, fp8_format_type="hybrid", scale_epsilon=1e-8, **kwargs, ): if weight_quantize_algo is not None: if isinstance(weight_quantize_algo, dict): if any( algo not in [ "weight_only_int8", "weight_only_int4", "llm.int8", "a8w8", "nf4", "fp4", "a8w8linear", "a8w4linear", "fp8linear", ] for algo in weight_quantize_algo ): raise ValueError( f"weight_quantize_algo:{weight_quantize_algo.keys()} not in supported list ['weight_only_int8', 'weight_only_int4', 'llm.int8', 'a8w8', 'nf4', 'fp4']" ) elif weight_quantize_algo not in [ "weight_only_int8", "weight_only_int4", "llm.int8", "a8w8", "nf4", "fp4", "a8w8linear", "a8w4linear", "fp8linear", ]: raise ValueError( f"weight_quantize_algo:{weight_quantize_algo} not in supported list ['weight_only_int8', 'weight_only_int4', 'llm.int8', 'a8w8', 'nf4', 'fp4']" ) if quant_type is not None and quant_type not in [ "weight_only_int8", "weight_only_int4", "a8w8", "a8w8c8", "a8w8_fp8", "a8w8c8_fp8", ]: raise ValueError( f"quant_type:{quant_type} not in supported list ['weight_only_int8', 'weight_only_int4', 'a8w8', 'a8w8c8', 'a8w8_fp8', 'a8w8c8_fp8']" ) self.weight_quantize_algo = weight_quantize_algo self.quant_type = quant_type self.shift = shift self.smooth = smooth self.shift = shift self.shift_smooth_all_linears = shift_smooth_all_linears self.quant_round_type = quant_round_type self.llm_int8_threshold = llm_int8_threshold self.qlora_weight_double_quant = qlora_weight_double_quant self.qlora_weight_blocksize = qlora_weight_blocksize self.weight_quant_method = weight_quant_method self.act_quant_method = quant_inference_mapping[act_quant_method] self.qlora_weight_double_quant_block_size = qlora_weight_double_quant_block_size self.activation_scheme = activation_scheme self.fmt = fmt self.quant_method = quant_method self.weight_block_size = weight_block_size self.dtype = dtype self.ignore_modules = ignore_modules self.group_size = group_size self.apply_hadamard = apply_hadamard self.hadamard_block_size = hadamard_block_size self.quant_input_grad = quant_input_grad self.quant_weight_grad = quant_weight_grad self.apply_online_actscale_step = apply_online_actscale_step self.actscale_moving_rate = actscale_moving_rate self.fp8_format_type = fp8_format_type self.scale_epsilon = scale_epsilon @property def fp8_format(self): return fp8_format_mapping[self.fp8_format_type] def is_weight_quantize(self): if isinstance(self.weight_quantize_algo, dict): return True elif self.weight_quantize_algo in [ "weight_only_int8", "weight_only_int4", "llm.int8", "nf4", "fp4", "a8w8", "a8w8linear", "a8w4linear", "fp8linear", ]: return True else: return False def is_support_merge_tensor_parallel(self): if self.weight_quantize_algo in ["weight_only_int8", "weight_only_int4", "llm.int8", "a8w8"]: return False else: return True @classmethod def from_dict(cls, config_dict, return_unused_kwargs=False, **kwargs): """ Instantiates QuantizationConfig from dict """ config = cls(**config_dict) to_remove = [] for key, value in kwargs.items(): if hasattr(config, key): setattr(config, key, value) to_remove.append(key) for key in to_remove: kwargs.pop(key, None) if return_unused_kwargs: return config, kwargs else: return config def to_json_file(self, json_file_path): """ Save this instance to a JSON file. """ with open(json_file_path, "w", encoding="utf-8") as f: f.write(json.dumps(self.to_dict(), indent=2, sort_keys=True) + "\n") def to_dict(self): return copy.deepcopy(self.__dict__) def __repr__(self): return f"{self.__class__.__name__} {self.to_json_string()}" def to_json_string(self, use_diff=True): if use_diff is True: config_dict = self.to_diff_dict() else: config_dict = self.to_dict() return json.dumps(config_dict, indent=2, sort_keys=True) + "\n" def to_diff_dict(self): config_dict = self.to_dict() # get the default config dict default_config_dict = QuantizationConfig().to_dict() serializable_config_dict = {} # only serialize values that differ from the default config for key, value in config_dict.items(): if value != default_config_dict[key]: serializable_config_dict[key] = value return serializable_config_dict