# Copyright (c) 2025 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 re import paddle import paddle.nn as nn from paddle.distributed.fleet.meta_parallel import ( ColumnParallelLinear, RowParallelLinear, ) from paddle.distributed.fleet.utils.sequence_parallel_utils import ( ColumnSequenceParallelLinear, RowSequenceParallelLinear, ) from paddle.incubate.nn.layer.fused_linear import FusedLinear from paddle.nn.quant import weight_quantize try: from .qlora import qlora_weight_linear, qlora_weight_quantize except: qlora_weight_linear = None qlora_weight_quantize = None from ..utils.log import logger from .qat_utils import quantize from .quantization_linear import ( ColumnParallelQuantizationLinear, QuantizationLinear, RowParallelQuantizationLinear, ) LINEAR_CLASSES = [ nn.Linear, FusedLinear, ColumnParallelLinear, RowParallelLinear, ColumnSequenceParallelLinear, RowSequenceParallelLinear, ] def parse_weight_quantize_algo(quantization_config, name): if quantization_config.ignore_modules is not None and any( re.fullmatch(ignore_module, name) for ignore_module in quantization_config.ignore_modules ): weight_quantize_algo = None elif isinstance(quantization_config.weight_quantize_algo, str): weight_quantize_algo = quantization_config.weight_quantize_algo else: weight_quantize_algo = None for algo in quantization_config.weight_quantize_algo: if any(re.fullmatch(module, name) for module in quantization_config.weight_quantize_algo[algo]): weight_quantize_algo = algo return weight_quantize_algo def replace_with_quantization_linear(model, quantization_config, llm_int8_threshold=6.0): for name, child in model.named_sublayers(): weight_quantize_algo = parse_weight_quantize_algo(quantization_config, name) if weight_quantize_algo is None: continue if any(isinstance(child, linear_class) for linear_class in LINEAR_CLASSES): if child.bias is None: bias_attr = False else: bias_attr = None parent = model *path, last = name.split(".") for attr in path: parent = getattr(parent, attr) if isinstance(child, nn.Linear) or isinstance(child, FusedLinear): if getattr(child.weight, "transpose_weight", False): out_feature, in_features = child.weight.shape[0], child.weight.shape[1] else: in_features, out_feature = child.weight.shape[0], child.weight.shape[1] quant_linear = QuantizationLinear( in_features=in_features, out_features=out_feature, quantization_config=quantization_config, weight_quantize_algo=weight_quantize_algo, dtype=child._dtype, bias_attr=bias_attr, mp_moe=getattr(child.weight, "mp_moe", False), is_distributed=getattr(child.weight, "is_distributed", False), ) elif isinstance(child, ColumnParallelLinear): quant_linear = ColumnParallelQuantizationLinear( in_features=child.weight.shape[0], output_size_per_partition=child.weight.shape[1], quantization_config=quantization_config, weight_quantize_algo=weight_quantize_algo, dtype=child._dtype, bias_attr=bias_attr, gather_output=child.gather_output, mp_skip_c_identity=child.mp_skip_c_identity, ) elif isinstance(child, RowParallelLinear): quant_linear = RowParallelQuantizationLinear( input_size_per_partition=child.weight.shape[0], out_features=child.weight.shape[1], quantization_config=quantization_config, weight_quantize_algo=weight_quantize_algo, dtype=child._dtype, bias_attr=bias_attr, input_is_parallel=child.input_is_parallel, mp_skip_c_identity=child.mp_skip_c_identity, ) elif isinstance(child, ColumnSequenceParallelLinear): quant_linear = ColumnParallelQuantizationLinear( in_features=child.weight.shape[0], output_size_per_partition=child.weight.shape[1], quantization_config=quantization_config, weight_quantize_algo=weight_quantize_algo, dtype=child._dtype, bias_attr=bias_attr, gather_output=False, sequence_parallel=True, ) elif isinstance(child, RowSequenceParallelLinear): quant_linear = RowParallelQuantizationLinear( input_size_per_partition=child.weight.shape[0], out_features=child.weight.shape[1], quantization_config=quantization_config, weight_quantize_algo=weight_quantize_algo, dtype=child._dtype, bias_attr=bias_attr, input_is_parallel=True, sequence_parallel=True, ) setattr(parent, last, quant_linear) del child def convert_to_weight_quantize_state_dict(state_dict, name, quantization_config, dtype, weight_quantize_algo): weight_name = name + ".weight" quant_weight_name = name + ".quant_weight" quant_scale_name = name + ".quant_scale" act_scale_name = name + ".act_scale" if quant_weight_name in state_dict and quant_scale_name in state_dict: return state_dict if weight_name in state_dict: # gpu weight_quantize will fix in future target_weight = state_dict.pop(weight_name).cast(dtype).cuda() if weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]: quant_weight, quant_scale = quantize( target_weight, weight_quantize_algo, "weight", quantization_config, side="left", apply_hadamard=quantization_config.apply_hadamard, ) act_scale = paddle.ones([1], dtype=dtype).cuda() act_scale.stop_gradient = True state_dict[act_scale_name] = act_scale else: quant_weight, quant_scale = weight_quantize( x=target_weight, algo=weight_quantize_algo, group_size=quantization_config.group_size, ) state_dict[quant_weight_name] = quant_weight state_dict[quant_scale_name] = quant_scale del target_weight return state_dict def convert_to_qlora_state_dict(state_dict, name, quantization_config, dtype, weight_quantize_algo): if qlora_weight_quantize is None: raise ImportError( "Please run the following commands to install qlora related package first: \n" "1) git clone https://github.com/PaddlePaddle/PaddleSlim \n" "2) cd PaddleSlim \n" "3) python ./csrc/setup_cuda.py install" ) weight_name = name + ".weight" quant_weight_name = name + ".quant_weight" quant_name_list = [quant_weight_name] if not quantization_config.qlora_weight_double_quant: quant_scale_name = name + ".quant_scale" quant_name_list += [quant_scale_name] else: qquant_scale_name = name + ".qquant_scale" double_quant_scale_name = name + ".double_quant_scale" quant_sacle_offset_name = name + ".quant_sacle_offset" quant_name_list += [qquant_scale_name, double_quant_scale_name, quant_sacle_offset_name] if all(quant_name in state_dict for quant_name in quant_name_list): return state_dict elif weight_name in state_dict: target_weight = state_dict.pop(weight_name).cast(dtype).cuda() qlora_state_dict = qlora_weight_quantize( weight=target_weight, quant_algo=weight_quantize_algo, double_quant=quantization_config.qlora_weight_double_quant, block_size=quantization_config.qlora_weight_blocksize, double_quant_block_size=quantization_config.qlora_weight_double_quant_block_size, linear_name=name, return_dict=True, ) state_dict.update(qlora_state_dict) del target_weight return state_dict def convert_to_quantize_state_dict(state_dict, quantization_linear_list, quantization_config, dtype): for name in quantization_linear_list: # Get quantization algorithm weight_quantize_algo = parse_weight_quantize_algo(quantization_config, name) if weight_quantize_algo is None: continue # Convert state dict if weight_quantize_algo in [ "weight_only_int8", "weight_only_int4", "llm.int8", "a8w8linear", "a8w4linear", "fp8linear", ]: convert_to_weight_quantize_state_dict(state_dict, name, quantization_config, dtype, weight_quantize_algo) elif weight_quantize_algo in ["fp4", "nf4"]: convert_to_qlora_state_dict(state_dict, name, quantization_config, dtype, weight_quantize_algo) else: raise NotImplementedError( f"Please check the quantization_config.weight_quantize_algo: {quantization_config.weight_quantize_algo}" ) return state_dict def update_loaded_state_dict_keys(state_dict, quantization_linear_list, quantization_config, ignore_warning=False): for name in quantization_linear_list: weight_name = name + ".weight" quant_weight_name = name + ".quant_weight" quant_scale_name = name + ".quant_scale" act_scale_name = name + ".act_scale" qquant_scale_name = name + ".qquant_scale" double_quant_scale_name = name + ".double_quant_scale" quant_sacle_offset_name = name + ".quant_sacle_offset" if quant_weight_name in state_dict and quant_scale_name in state_dict: continue elif weight_name in state_dict: state_dict.remove(weight_name) state_dict.append(quant_weight_name) if quantization_config.qlora_weight_double_quant: state_dict.append(qquant_scale_name) state_dict.append(double_quant_scale_name) state_dict.append(quant_sacle_offset_name) else: state_dict.append(quant_scale_name) weight_quantize_algo = parse_weight_quantize_algo(quantization_config, name) if weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]: state_dict.append(act_scale_name) else: if not ignore_warning: logger.warning( f"Cannot find {weight_name} in state_dict or {quant_weight_name} and {quant_scale_name} in state_dict" ) return state_dict