# 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 from paddle.autograd import PyLayer from .hadamard_utils import apply_hadamard_matmul try: from transformer_engine import transformer_engine_paddle as tex from transformer_engine.paddle.constants import FP8BwdTensors, FP8FwdTensors from transformer_engine.paddle.cpp_extensions import fp8_gemm from transformer_engine.paddle.layer.base import get_workspace TE_DType = { paddle.float8_e4m3fn: tex.DType.kFloat8E4M3, paddle.float8_e5m2: tex.DType.kFloat8E5M2, } SUPPORT_TE = True except ImportError: SUPPORT_TE = False from paddle.linalg import fp8_fp8_half_gemm_fused QMIN_QMAX_MAPPING = { "a8w8linear_activation": (-128, 127), "a8w4linear_activation": (-128, 127), "a8w8linear_weight": (-128, 127), "a8w4linear_weight": (-8, 7), "float8_e4m3fn": (-488, 488), "float8_e5m2": (-57344, 57344), } def quantize( x, weight_quantize_algo, tensor_type, quantization_config, side="right", apply_hadamard=False, act_scale=None, state=0, training=False, group=None, ): if apply_hadamard: target_x = apply_hadamard_matmul(x, side, quantization_config.hadamard_block_size) hadamard_scale = quantization_config.hadamard_block_size else: target_x, hadamard_scale = x, 1.0 if weight_quantize_algo in ["fp8linear"]: qmin, qmax = QMIN_QMAX_MAPPING[quantization_config.fp8_format[tensor_type]] else: qmin, qmax = QMIN_QMAX_MAPPING[weight_quantize_algo + "_" + tensor_type] if tensor_type == "activation": if act_scale is not None: if training: scale = (paddle.max(paddle.abs(target_x)) / qmax + quantization_config.scale_epsilon).reshape([1]) if group is not None: paddle.distributed.all_reduce(scale, op=paddle.distributed.ReduceOp.MAX, group=group, sync_op=True) if state < quantization_config.apply_online_actscale_step: act_scale[:] = (state * act_scale + scale) / (state + 1) else: scale = ( 1 - quantization_config.actscale_moving_rate ) * act_scale + quantization_config.actscale_moving_rate * scale act_scale[:] = scale else: scale = act_scale else: scale = (paddle.max(paddle.abs(target_x)) / qmax + quantization_config.scale_epsilon).reshape([1]) if weight_quantize_algo in ["a8w8linear", "a8w4linear"]: quant_x = paddle.clip((target_x / scale).round(), qmin, qmax).astype("int8") elif weight_quantize_algo in ["fp8linear"]: quant_x = (target_x / scale).astype(quantization_config.fp8_format[tensor_type]) else: raise NotImplementedError(f"Unknown {weight_quantize_algo}.") elif tensor_type == "weight": if weight_quantize_algo in ["a8w8linear", "a8w4linear"]: # channelwise scale = paddle.max(paddle.abs(target_x), axis=0, keepdim=True) / qmax + quantization_config.scale_epsilon if group is not None: paddle.distributed.all_reduce(scale, op=paddle.distributed.ReduceOp.MAX, group=group, sync_op=True) quant_x = paddle.clip((target_x / scale).round(), qmin, qmax).astype("int8").T scale = scale.squeeze(0) / hadamard_scale elif weight_quantize_algo in ["fp8linear"]: scale = paddle.max(paddle.abs(target_x)) / qmax + quantization_config.scale_epsilon if group is not None: paddle.distributed.all_reduce(scale, op=paddle.distributed.ReduceOp.MAX, group=group, sync_op=True) quant_x = (target_x / scale).astype(quantization_config.fp8_format[tensor_type]).view("int8").T scale = (scale / hadamard_scale).reshape([1]) else: raise NotImplementedError(f"Unknown {weight_quantize_algo}.") elif tensor_type == "grad_output": if weight_quantize_algo in ["fp8linear"]: scale = (paddle.max(paddle.abs(target_x)) / qmax + quantization_config.scale_epsilon).reshape([1]) quant_x = (target_x / scale).astype(quantization_config.fp8_format[tensor_type]) scale = scale / hadamard_scale else: raise NotImplementedError(f"Unknown {weight_quantize_algo}.") else: raise NotImplementedError(f"Unknown {tensor_type}.") scale.stop_gradient = True return quant_x, scale def dequantize( quant_x, scale, tensor_type, weight_quantize_algo, quantization_config, apply_hadamard=False, side="left" ): if tensor_type == "weight": if weight_quantize_algo in ["a8w8linear", "a8w4linear"]: x = quant_x.T.astype(scale.dtype) elif weight_quantize_algo in ["fp8linear"]: x = quant_x.view(quantization_config.fp8_format[tensor_type]).T.astype(scale.dtype) else: raise NotImplementedError(f"Unknown weight_quantize_algo: {weight_quantize_algo}") if apply_hadamard: x = apply_hadamard_matmul(x, side, quantization_config.hadamard_block_size) x *= scale else: raise NotImplementedError(f"Unknown {tensor_type}.") return x def int8_forward( x, quant_w, scale_w, weight_quantize_algo, bias=None, quantization_config=None, state=0, training=False, act_scale=None, group=None, ): quant_x, scale_x = quantize( x=x, weight_quantize_algo=weight_quantize_algo, tensor_type="activation", quantization_config=quantization_config, side="right", apply_hadamard=quantization_config.apply_hadamard, act_scale=act_scale, state=state, training=training, group=group, ) out = paddle.matmul(quant_x, quant_w.T).astype(scale_w.dtype) * (scale_x * scale_w) if bias is not None: out += bias return out, quant_x, scale_x def int8_backward(ctx, x, grad_output, quant_weight, quant_scale, quant_x, x_scale): if not ctx.x_stop_gradient: qdq_weight = dequantize( quant_weight, quant_scale, "weight", ctx.weight_quantize_algo, ctx.quantization_config, ctx.quantization_config.apply_hadamard, "left", ) input_grad = paddle.matmul(grad_output, qdq_weight.T) else: input_grad = None if not ctx.w_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 return input_grad, weight_grad def fp8_forward( x, w_fp8, w_scale, weight_quantize_algo, bias=None, dtype=None, quantization_config=None, state=0, training=False, act_scale=None, group=None, ): x_fp8, x_scale = quantize( x, weight_quantize_algo, "activation", quantization_config, side="right", apply_hadamard=quantization_config.apply_hadamard, act_scale=act_scale, state=state, training=training, group=group, ) w_fp8 = w_fp8.view(quantization_config.fp8_format["weight"]) out = fp8_fp8_half_gemm_fused( x_fp8, w_fp8, transpose_x=False, transpose_y=True, bias=bias, scale=x_scale * w_scale, output_dtype=dtype, ) return out, x_fp8, x_scale def fp8_backward(ctx, x, grad_output, quant_weight, quant_scale, quant_x, x_scale): if not ctx.x_stop_gradient: if ctx.quantization_config.quant_input_grad: grad_output_fp8, grad_output_scale = quantize( grad_output, ctx.weight_quantize_algo, "grad_output", ctx.quantization_config, side="left", apply_hadamard=False, ) quant_weight = quant_weight.view(ctx.quantization_config.fp8_format["weight"]) if SUPPORT_TE: grad_output_shape = grad_output_fp8.shape grad_output_fp8 = grad_output_fp8.view((-1, grad_output_fp8.shape[-1])) fwd_scales = paddle.concat([x_scale.astype("float32"), quant_scale.astype("float32")]) bwd_scales = grad_output_scale[None].astype("float32") input_grad, _ = fp8_gemm( A=quant_weight.T, A_scale_inv=fwd_scales, A_fp8_tensor=FP8FwdTensors.GEMM1_WEIGHT, A_dtype=TE_DType[quant_weight.dtype], B=grad_output_fp8, B_scale_inv=bwd_scales, B_fp8_tensor=FP8BwdTensors.GRAD_OUTPUT1, B_dtype=TE_DType[grad_output_fp8.dtype], out_dtype=ctx.dtype, workspace=get_workspace(), use_split_accumulator=True, ) input_grad = input_grad.view((*grad_output_shape[:-1], -1)) else: grad_output_ = grad_output_fp8.astype(ctx.dtype) * grad_output_scale weight_ = quant_weight.astype(ctx.dtype) * quant_scale input_grad = paddle.matmul(grad_output_, weight_).astype(ctx.dtype) if ctx.quantization_config.apply_hadamard: input_grad = apply_hadamard_matmul(input_grad, "right", ctx.quantization_config.hadamard_block_size) else: qdq_weight = dequantize( quant_weight, quant_scale, "weight", ctx.weight_quantize_algo, ctx.quantization_config, apply_hadamard=ctx.quantization_config.apply_hadamard, side="left", ) input_grad = paddle.matmul(grad_output, qdq_weight.T) else: input_grad = None if not ctx.w_stop_gradient: if ctx.quantization_config.quant_weight_grad: grad_output_fp8, grad_output_scale = quantize( x=grad_output, weight_quantize_algo=ctx.weight_quantize_algo, tensor_type="grad_output", quantization_config=ctx.quantization_config, apply_hadamard=False, ) if SUPPORT_TE: quant_x = quant_x.view((-1, quant_x.shape[-1])) grad_output_fp8 = grad_output_fp8.view((-1, grad_output_fp8.shape[-1])) fwd_scales = paddle.concat([x_scale.astype("float32"), quant_scale.astype("float32")]) bwd_scales = grad_output_scale[None].astype("float32") # FP8 gemm need k % 16 = 0 ALIGNMENT_SIZE = 16 def pad_tensor_to_multiple(tensor, dtype): current_size = tensor.shape[0] padding_size = ALIGNMENT_SIZE - current_size % ALIGNMENT_SIZE # Create padding zeros with matching shape and dtype padding_shape = [padding_size, tensor.shape[1]] padding = paddle.zeros(padding_shape, dtype=dtype) padded_tensor = paddle.concat([tensor, padding], axis=0) return padded_tensor if quant_x.shape[0] % ALIGNMENT_SIZE != 0: quant_x = pad_tensor_to_multiple(quant_x, ctx.quantization_config.fp8_format["activation"]) grad_output_fp8 = pad_tensor_to_multiple( grad_output_fp8, ctx.quantization_config.fp8_format["grad_output"] ) weight_grad, _ = fp8_gemm( A=grad_output_fp8.T, A_scale_inv=bwd_scales, A_fp8_tensor=FP8BwdTensors.GRAD_OUTPUT1, A_dtype=TE_DType[grad_output_fp8.dtype], B=quant_x.T, B_scale_inv=fwd_scales, B_fp8_tensor=FP8FwdTensors.GEMM1_INPUT, B_dtype=TE_DType[quant_x.dtype], out_dtype=ctx.dtype, workspace=get_workspace(), use_split_accumulator=True, ) else: grad_output_ = grad_output_fp8.astype(ctx.dtype) * grad_output_scale x_ = quant_x.astype(ctx.dtype) * x_scale if len(x_.shape) == 2: weight_grad = paddle.matmul(x_.transpose([1, 0]), grad_output_).astype(ctx.dtype) else: weight_grad = paddle.matmul( x_.reshape([-1, x_.shape[-1]]).transpose([1, 0]), grad_output_.reshape([-1, grad_output_.shape[-1]]), ).astype(ctx.dtype) if ctx.quantization_config.apply_hadamard: weight_grad = weight_grad / ctx.quantization_config.hadamard_block_size weight_grad = apply_hadamard_matmul(weight_grad, "left", ctx.quantization_config.hadamard_block_size) else: 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 return input_grad, weight_grad class QATFunc(PyLayer): @staticmethod def forward( ctx, x, quant_weight, bias, quant_scale, quantization_config, dtype, state, training, act_scale, weight_quantize_algo, group, ): quant_x, x_scale = None, None if weight_quantize_algo in ["fp8linear"]: output, quant_x, x_scale = fp8_forward( x, quant_weight, w_scale=quant_scale, weight_quantize_algo=weight_quantize_algo, bias=bias, dtype=dtype, quantization_config=quantization_config, state=state, training=training, act_scale=act_scale, group=group, ) else: output, quant_x, x_scale = int8_forward( x, quant_w=quant_weight, scale_w=quant_scale, weight_quantize_algo=weight_quantize_algo, bias=bias, quantization_config=quantization_config, state=state, training=training, act_scale=act_scale, group=group, ) ctx.quantization_config = quantization_config ctx.weight_quantize_algo = weight_quantize_algo ctx.dtype = dtype ctx.x_stop_gradient = x.stop_gradient ctx.w_stop_gradient = quant_weight.stop_gradient ctx.b_stop_gradient = bias.stop_gradient if bias is not None else True ctx.save_for_backward( x if not quantization_config.quant_weight_grad else None, quant_weight, bias, quant_scale, quant_x if quantization_config.quant_weight_grad else None, x_scale if quantization_config.quant_weight_grad else None, ) return output @staticmethod def backward(ctx, grad_output): x, quant_weight, bias, quant_scale, quant_x, x_scale = ctx.saved_tensor() if ctx.quantization_config.weight_quantize_algo in ["fp8linear"]: input_grad, weight_grad = fp8_backward(ctx, x, grad_output, quant_weight, quant_scale, quant_x, x_scale) else: input_grad, weight_grad = int8_backward(ctx, x, grad_output, quant_weight, quant_scale, quant_x, x_scale) if not ctx.b_stop_gradient: bias_grad = grad_output.sum(axis=[0, 1]) else: bias_grad = None return input_grad, weight_grad, bias_grad