# 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 paddle import paddle.nn as nn from paddle.autograd import PyLayer from paddle.distributed import fleet from paddle.distributed.fleet.base import topology as tp from paddle.distributed.fleet.layers.mpu import mp_ops from paddle.distributed.fleet.utils.sequence_parallel_utils import ( AllGatherOp, ReduceScatterOp, ) from paddle.nn.quant import llm_int8_linear, weight_dequantize, weight_only_linear from paddlenlp.utils import infohub from .qat_utils import QATFunc try: from .qlora import qlora_weight_dequantize, qlora_weight_linear except: qlora_weight_linear = None qlora_weight_dequantize = None QuantMapping = { # (quant_dtype, quant_weight_bit) "weight_only_int8": ("int8", 8), "weight_only_int4": ("int4", 4), "llm.int8": ("int8", 8), "fp4": ("fp4", 4), "nf4": ("nf4", 4), "a8w8linear": ("int8", 8), "a8w4linear": ("int8", 8), "fp8linear": ("fp8", 8), } def quant_weight_forward( x, quant_weight, bias, quant_scale, quant_state, quant_dtype, quantization_config, weight_quantize_algo, dtype, ): if weight_quantize_algo in ["weight_only_int8", "weight_only_int4"]: output = weight_only_linear( x=x, weight=quant_weight, bias=bias, weight_scale=quant_scale, weight_dtype=quant_dtype, group_size=quantization_config.group_size, ) elif weight_quantize_algo in ["llm.int8"]: output = llm_int8_linear(x, quant_weight, bias, quant_scale, quantization_config.llm_int8_threshold) elif weight_quantize_algo in ["fp4", "nf4"]: output = qlora_weight_linear( x=x, quant_weight=quant_weight, dtype=dtype, state=quant_state if quantization_config.qlora_weight_double_quant else quant_scale, 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, bias=bias, ) return output def dequant_weight( quant_weight, quantization_config, weight_quantize_algo, dtype, quant_scale, quant_state, input_shape, ): if weight_quantize_algo in ["weight_only_int8", "weight_only_int4", "llm.int8"]: quant_dequant_weight = weight_dequantize( x=quant_weight, scale=quant_scale, algo=weight_quantize_algo, out_dtype=dtype, group_size=quantization_config.group_size, ) elif weight_quantize_algo in ["fp4", "nf4"]: quant_dequant_weight = ( qlora_weight_dequantize( quant_weight=quant_weight, quant_algo=weight_quantize_algo, state=quant_state if quantization_config.qlora_weight_double_quant else quant_scale, 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, ) .reshape([input_shape[-1], -1]) .cast(dtype) ) return quant_dequant_weight class QuantizationLinearFunc(PyLayer): @staticmethod def forward( ctx, x, quant_weight, bias, quant_scale, quant_state, quant_dtype, quantization_config, weight_quantize_algo, dtype, ): output = quant_weight_forward( x=x, quant_weight=quant_weight, bias=bias, quant_scale=quant_scale, quant_state=quant_state, quant_dtype=quant_dtype, quantization_config=quantization_config, weight_quantize_algo=weight_quantize_algo, dtype=dtype, ) ctx.quant_dtype = quant_dtype ctx.quantization_config = quantization_config ctx.weight_quantize_algo = weight_quantize_algo ctx.dtype = dtype if ctx.weight_quantize_algo in ["fp4", "nf4"] and ctx.quantization_config.qlora_weight_double_quant: qquant_scale, double_quant_scale, quant_scale_offset = quant_state ctx.save_for_backward(x, quant_weight, bias, qquant_scale, double_quant_scale, quant_scale_offset) else: ctx.save_for_backward(x, quant_weight, bias, quant_scale) return output @staticmethod def backward(ctx, grad_output): if ctx.weight_quantize_algo in ["fp4", "nf4"] and ctx.quantization_config.qlora_weight_double_quant: x, quant_weight, bias, qquant_scale, double_quant_scale, quant_scale_offset = ctx.saved_tensor() quant_state = (qquant_scale, double_quant_scale, quant_scale_offset) quant_scale = None else: x, quant_weight, bias, quant_scale = ctx.saved_tensor() quant_state = None qdq_weight = dequant_weight( quant_weight=quant_weight, quantization_config=ctx.quantization_config, weight_quantize_algo=ctx.weight_quantize_algo, dtype=ctx.dtype, quant_scale=quant_scale, quant_state=quant_state, input_shape=x.shape, ) if not x.stop_gradient: input_grad = paddle.matmul(grad_output, qdq_weight.T) else: input_grad = None if not quant_weight.stop_gradient: if len(x.shape) == 2: weight_grad = paddle.matmul(x.transpose([1, 0]), grad_output) else: weight_grad = paddle.matmul( x.reshape([-1, x.shape[-1]]).transpose([1, 0]), grad_output.reshape([-1, grad_output.shape[-1]]) ) else: weight_grad = None if bias is not None and not bias.stop_gradient: bias_grad = grad_output.sum(axis=[0, 1]) else: bias_grad = None return input_grad, weight_grad, bias_grad def quant_weight_linear( x, quant_weight, quant_dtype, quantization_config, weight_quantize_algo, dtype, quant_scale=None, quant_state=None, bias=None, act_state=None, ): if weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]: state, training, act_scale, group = act_state return QATFunc.apply( x, quant_weight, bias, quant_scale, quantization_config, dtype, state, training, act_scale, weight_quantize_algo, group, ) else: return QuantizationLinearFunc.apply( x, quant_weight, bias, quant_scale, quant_state, quant_dtype, quantization_config, weight_quantize_algo, dtype, ) def get_act_scale_group(is_row=False): if paddle.distributed.is_initialized(): if getattr(infohub, "scale_group") is None: hcg = fleet.get_hybrid_communicate_group() rank = hcg._dp_degree * hcg._sharding_degree group_no_row = hcg.create_fuse_group(["data", "sharding"])[1] if rank > 1 else None rank *= hcg._mp_degree group_row = hcg.create_fuse_group(["data", "sharding", "model"])[1] if rank > 1 else None setattr(infohub, "scale_group", [group_no_row, group_row]) group = infohub.scale_group[1] if is_row else infohub.scale_group[0] else: group = None return group class QuantizationLinear(nn.Layer): """Quantization Linear layer.""" def __init__( self, in_features, out_features, quantization_config, weight_quantize_algo, dtype, bias_attr=None, mp_moe=False, is_distributed=False, ): super().__init__() self.in_features = in_features self.out_features = out_features self.quantization_config = quantization_config self.weight_quantize_algo = weight_quantize_algo self._dtype = dtype self.quant_dtype, self.quant_weight_bit = QuantMapping[self.weight_quantize_algo] self.state = 0 # PaddlePaddle doesn't support 4bit data type, one 8bit data represents two 4bit data. # paddle.nn.quant.weight_quantize will transpose in_features and out_features. if self.weight_quantize_algo in [ "weight_only_int8", "weight_only_int4", "llm.int8", "a8w8linear", "a8w4linear", "fp8linear", ]: self.quant_weight = self.create_parameter( shape=[out_features // 2, in_features] if self.quant_weight_bit == 4 else [out_features, in_features], dtype="int8", is_bias=False, ) if self.quantization_config.group_size == -1: self.quant_scale = self.create_parameter( shape=[out_features] if self.weight_quantize_algo not in ["fp8linear"] else [1], dtype=self._dtype, is_bias=False, ) self.quant_scale.stop_gradient = True else: # TODO(lugimzzz): support groupwise in next PR raise NotImplementedError("Not yet support grouwise weightonly quantization.") if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]: self.act_scale = self.create_parameter( shape=[1], dtype=self._dtype, is_bias=False, default_initializer=nn.initializer.Constant(value=0.0) ) self.act_scale.stop_gradient = True self.group = get_act_scale_group() elif self.weight_quantize_algo in ["fp4", "nf4"]: if qlora_weight_linear 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 && pip install -e .\n" "3) cd csrc && python ./setup_cuda.py install" ) self.quant_weight = self.create_parameter( shape=[out_features * in_features // 2, 1], attr=paddle.nn.initializer.Constant(value=0), dtype="uint8", is_bias=False, ) if self.quantization_config.qlora_weight_double_quant: # quantized quant_scale self.qquant_scale = self.create_parameter( shape=[in_features * out_features // self.quantization_config.qlora_weight_blocksize], dtype="uint8", is_bias=False, ) # double quant_scale: quant_scale of quantized quant_scale self.double_quant_scale = self.create_parameter( shape=[ in_features * out_features // self.quantization_config.qlora_weight_blocksize // self.quantization_config.qlora_weight_double_quant_block_size ], dtype="float32", is_bias=False, ) self.quant_scale_offset = self.create_parameter( shape=[], dtype="float32", is_bias=False, ) else: self.quant_scale = self.create_parameter( shape=[in_features * out_features // self.quantization_config.qlora_weight_blocksize], dtype="float32", is_bias=False, ) else: raise NotImplementedError(f"Not yet support weight_quantize_algo: {self.weight_quantize_algo}") if bias_attr is False: self.bias = None else: self.bias = self.create_parameter( shape=[out_features], attr=bias_attr, dtype=self._dtype, is_bias=True, ) if mp_moe or is_distributed: for p in self.parameters(): p.is_distributed = is_distributed p.mp_moe = mp_moe self.quant_weight.weight_quantize_algo = self.weight_quantize_algo def forward(self, x): output = quant_weight_linear( x=x, quant_weight=self.quant_weight, quant_dtype=self.quant_dtype, quantization_config=self.quantization_config, weight_quantize_algo=self.weight_quantize_algo, dtype=self._dtype, quant_scale=self.quant_scale, quant_state=(self.qquant_scale, self.double_quant_scale, self.quant_scale_offset) if (self.weight_quantize_algo in ["fp4", "nf4"] and self.quantization_config.qlora_weight_double_quant) else None, bias=self.bias, act_state=(self.state, self.training, self.act_scale, self.group) if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"] else None, ) if self.training: self.state += 1 return output class ColumnParallelQuantizationLinear(nn.Layer): """Quantization Linear layer with mp parallelized(column). The code implementation refers to paddle.distributed.fleet.meta_parallel.ColumnParallelLinear. https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/distributed/fleet/layers/mpu/mp_layers.py#L310 Different from ColumnParallelLinear, this class keeps weight in INT8/INT4 with quant scale, and supports matrix multiplication(weight_only_linear/llm_int8_linear) for input tensor(fp16/bf16) and quantized weight(INT8/INT4) and bias addition if provided. Notice: quantized weight shape is transposed of weight shape in ColumnParallelLinear. """ def __init__( self, in_features, output_size_per_partition, quantization_config, weight_quantize_algo, dtype, bias_attr=None, gather_output=True, mp_skip_c_identity=False, mp_group=None, sequence_parallel=False, ): super().__init__() self.in_features = in_features self.output_size_per_partition = output_size_per_partition self.weight_quantize_algo = weight_quantize_algo self.quantization_config = quantization_config self._dtype = dtype self.mp_skip_c_identity = mp_skip_c_identity self.quant_dtype, self.quant_weight_bit = QuantMapping[self.weight_quantize_algo] self.state = 0 self.model_parallel_group = ( tp._HYBRID_PARALLEL_GROUP.get_model_parallel_group() if mp_group is None else mp_group ) self.world_size = ( tp._HYBRID_PARALLEL_GROUP.get_model_parallel_world_size() if mp_group is None else mp_group.nranks ) self.is_mp = self.world_size > 1 self.gather_output = gather_output self.sequence_parallel = sequence_parallel if self.sequence_parallel and self.gather_output: raise ValueError("Sequence parallel does not support gather_output") # PaddlePaddle doesn't support Int4 data type, one Int8 data represents two Int4 data. if self.weight_quantize_algo in [ "weight_only_int8", "weight_only_int4", "llm.int8", "a8w8linear", "a8w4linear", "fp8linear", ]: self.quant_weight = self.create_parameter( shape=[self.output_size_per_partition // 2, in_features] if self.quant_dtype == "int4" else [self.output_size_per_partition, in_features], dtype="int8", is_bias=False, ) self.quant_weight.is_distributed = True if self.is_mp else False if self.quant_weight.is_distributed: self.quant_weight.split_axis = 0 if self.quantization_config.group_size == -1: self.quant_scale = self.create_parameter( shape=[self.output_size_per_partition] if self.weight_quantize_algo not in ["fp8linear"] else [1], dtype=self._dtype, is_bias=False, ) self.quant_scale.stop_gradient = True if self.weight_quantize_algo in ["fp8linear", "a8w4linear", "a8w8linear"]: self.quant_scale.is_distributed = False else: self.quant_scale.is_distributed = True if self.is_mp else False if self.quant_scale.is_distributed: self.quant_scale.split_axis = 0 else: # TODO(lugimzzz): support groupwise in next PR raise NotImplementedError("Not yet support grouwise weightonly quantization.") if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]: self.act_scale = self.create_parameter( shape=[1], dtype=self._dtype, is_bias=False, default_initializer=nn.initializer.Constant(value=0.0) ) self.act_scale.is_distributed = False self.act_scale.stop_gradient = True self.group = get_act_scale_group() else: raise NotImplementedError(f"Not yet support weight_quantize_algo: {self.weight_quantize_algo}") if bias_attr is False: self.bias = None else: self.bias = self.create_parameter( shape=[self.output_size_per_partition], attr=bias_attr, dtype=self._dtype, is_bias=True, ) self.bias.is_distributed = True if self.is_mp else False if self.bias.is_distributed: self.bias.split_axis = 0 self.quant_weight.weight_quantize_algo = self.weight_quantize_algo def forward(self, x): if self.is_mp: if self.sequence_parallel: input_parallel = AllGatherOp.apply(x) else: input_parallel = mp_ops._c_identity( x, group=self.model_parallel_group, skip_c_identity_dynamic=self.mp_skip_c_identity, ) else: input_parallel = x output_parallel = quant_weight_linear( x=input_parallel, quant_weight=self.quant_weight, quant_dtype=self.quant_dtype, quantization_config=self.quantization_config, weight_quantize_algo=self.weight_quantize_algo, dtype=self._dtype, quant_scale=self.quant_scale, quant_state=(self.qquant_scale, self.double_quant_scale, self.quant_scale_offset) if (self.weight_quantize_algo in ["fp4", "nf4"] and self.quantization_config.qlora_weight_double_quant) else None, bias=self.bias, act_state=(self.state, self.training, self.act_scale, self.group) if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"] else None, ) if self.training: self.state += 1 if self.gather_output and self.is_mp: output = mp_ops._c_concat(output_parallel, group=self.model_parallel_group) else: output = output_parallel return output class RowParallelQuantizationLinear(nn.Layer): """Quantization Linear layer with mp parallelized(row). The code implementation refers to paddle.distributed.fleet.meta_parallel.RowParallelLinear. https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/distributed/fleet/layers/mpu/mp_layers.py#L517 Different from RowParallelLinear, this class keeps weight in INT8/INT4 with quant scale, and supports matrix multiplication(weight_only_linear/llm_int8_linear) for input tensor(fp16/bf16) and quantized weight(INT8/INT4) and bias addition if provided. Notice: quantized weight shape is transposed of weight shape in RowParallelLinear. """ def __init__( self, input_size_per_partition, out_features, quantization_config, weight_quantize_algo, dtype, bias_attr=None, input_is_parallel=False, mp_skip_c_identity=False, mp_group=None, sequence_parallel=False, ): super().__init__() self.input_size_per_partition = input_size_per_partition self.out_features = out_features self.quantization_config = quantization_config self.weight_quantize_algo = weight_quantize_algo self._dtype = dtype self.mp_skip_c_identity = mp_skip_c_identity self.quant_dtype, self.quant_weight_bit = QuantMapping[self.weight_quantize_algo] self.state = 0 self.model_parallel_group = ( tp._HYBRID_PARALLEL_GROUP.get_model_parallel_group() if mp_group is None else mp_group ) self.world_size = ( tp._HYBRID_PARALLEL_GROUP.get_model_parallel_world_size() if mp_group is None else mp_group.nranks ) self.is_mp = self.world_size > 1 self.input_is_parallel = input_is_parallel self.sequence_parallel = sequence_parallel if not self.input_is_parallel and self.sequence_parallel: raise ValueError("Sequence parallel only support input_is_parallel.") # PaddlePaddle doesn't support Int4 data type, one Int8 data represents two Int4 data. # paddle.nn.quant.weight_quantize will transpose in_features and out_features. if self.weight_quantize_algo in [ "weight_only_int8", "weight_only_int4", "llm.int8", "a8w8linear", "a8w4linear", "fp8linear", ]: self.quant_weight = self.create_parameter( shape=[out_features // 2, self.input_size_per_partition] if self.quant_dtype == "int4" else [out_features, self.input_size_per_partition], dtype="int8", is_bias=False, ) self.quant_weight.is_distributed = True if self.is_mp else False if self.quant_weight.is_distributed: self.quant_weight.split_axis = 1 if self.quantization_config.group_size == -1: self.quant_scale = self.create_parameter( shape=[out_features] if self.weight_quantize_algo not in ["fp8linear"] else [1], dtype=self._dtype, is_bias=False, ) self.quant_scale.stop_gradient = True if self.weight_quantize_algo in ["fp8linear", "a8w4linear", "a8w8linear"]: self.quant_scale.is_distributed = False else: self.quant_scale.is_distributed = True if self.is_mp else False if self.quant_scale.is_distributed: self.quant_scale.split_axis = 0 else: # TODO(lugimzzz): support groupwise in next PR raise NotImplementedError("Not yet support grouwise weightonly quantization.") if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"]: self.act_scale = self.create_parameter( shape=[1], dtype=self._dtype, is_bias=False, default_initializer=nn.initializer.Constant(value=0.0) ) self.act_scale.is_distributed = False self.act_scale.stop_gradient = True self.group = get_act_scale_group(is_row=True) else: raise NotImplementedError(f"Not yet support weight_quantize_algo: {self.weight_quantize_algo}") if bias_attr is False: self.bias = None else: self.bias = self.create_parameter( shape=[out_features], attr=bias_attr, dtype=self._dtype, is_bias=True, ) self.quant_weight.weight_quantize_algo = self.weight_quantize_algo def forward(self, x): if self.input_is_parallel or (not self.is_mp): input_parallel = x else: # split last dim input_parallel = mp_ops._c_split(x, group=self.model_parallel_group) # with paddle.amp.auto_cast(enable=False): if self.is_mp: output_parallel = quant_weight_linear( x=input_parallel, quant_weight=self.quant_weight, quant_dtype=self.quant_dtype, quantization_config=self.quantization_config, weight_quantize_algo=self.weight_quantize_algo, dtype=self._dtype, quant_scale=self.quant_scale, quant_state=(self.qquant_scale, self.double_quant_scale, self.quant_scale_offset) if (self.weight_quantize_algo in ["fp4", "nf4"] and self.quantization_config.qlora_weight_double_quant) else None, bias=None, act_state=(self.state, self.training, self.act_scale, self.group) if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"] else None, ) if self.sequence_parallel: output_ = ReduceScatterOp.apply(output_parallel) else: output_ = mp_ops._mp_allreduce( output_parallel, group=self.model_parallel_group, use_calc_stream=True, use_model_parallel=True, skip_c_identity_dynamic=self.mp_skip_c_identity, ) output = output_ + self.bias if self.bias is not None else output_ else: output = quant_weight_linear( x=input_parallel, quant_weight=self.quant_weight, quant_dtype=self.quant_dtype, quantization_config=self.quantization_config, weight_quantize_algo=self.weight_quantize_algo, dtype=self._dtype, quant_scale=self.quant_scale, quant_state=(self.qquant_scale, self.double_quant_scale, self.quant_scale_offset) if (self.weight_quantize_algo in ["fp4", "nf4"] and self.quantization_config.qlora_weight_double_quant) else None, bias=self.bias, act_state=(self.state, self.training, self.act_scale, self.group) if self.weight_quantize_algo in ["a8w8linear", "a8w4linear", "fp8linear"] else None, ) if self.training: self.state += 1 return output