# Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 # DeepSpeed Team from typing import Any, Dict, Optional import torch from deepspeed.accelerator import get_accelerator from deepspeed.ops.op_builder import InferenceCoreBuilder from ....allocator import empty_from from ....inference_utils import is_gated from ....kernels.core_ops import ( CUDAWf6Af16Linear, CUDABiasActivation, CUDAGatedActivation, ) from ...interfaces import DSLinearBase, DSLinearRegistry from ...configs import DSLinearConfig from ....inference_parameter import InferenceParameter def fp_quantize(input: torch.FloatTensor, num_bits: int = 6, exp_bits: int = 3, min_value: torch.FloatTensor = None, max_value: torch.FloatTensor = None, group_size: int = -1): """ Args: inputs (`torch.FloatTensor`) The input which needs to be quantized num_bits (int, >=4) Number of bits to use for quantization exp_bits: fp exp_bits min_value/max_vlue (torch.FloatTensor) Used for static activation quantization group_size (int) N The quantization block size, each N numbers has its own scaling factor and off-site. -1 means use the last dim as the group_size Returns: quantized_fake_fp6 The quantized weights, in fp16 format and contains fp6 value. scales Quantization scales """ try: from qtorch.quant import float_quantize except ImportError: raise ImportError("Please install qtorch to use this function") assert (min_value is None and max_value is None) or (min_value is not None and max_value is not None) assert input.dtype == torch.float16 orig_device = input.device input = input.to(torch.float32).to(get_accelerator().current_device()) if num_bits == 6 and exp_bits == 3: # this is default q_range = 28 else: raise NotImplementedError man_bits = num_bits - exp_bits - 1 input_shape = input.shape if group_size == -1: group_size = input_shape[-1] else: # Only support per-channel quantization raise NotImplementedError num_groups = input.numel() // group_size input = input.reshape(num_groups, -1) if min_value is None: max_input = torch.amax(torch.abs(input), dim=-1).view(num_groups, -1) else: max_input = torch.max(min_value.abs(), max_value) # .view(-1) scales = max_input / q_range # q_range + 1 scales[scales == 0] = 1 # avoid zero scales scaled_input = input / scales quantized_fake_fp6 = float_quantize(scaled_input, exp_bits, man_bits, rounding="nearest") quantized_fake_fp6 = quantized_fake_fp6.reshape(input_shape).contiguous().to(torch.float16).to(orig_device) scales = scales.to(torch.float16).to(orig_device) # Now the dequantized value is quantized_fake_fp6 * scales return quantized_fake_fp6, scales @DSLinearRegistry.register_module class QuantizedWf6Af16Linear(DSLinearBase): """ Linear DSModule for FP6 weight-only quantization kernel, where weight is FP6 and activation is FP16. """ @staticmethod def name(): return 'quantized_wf6af16_linear' @staticmethod def supports_config(config: DSLinearConfig) -> bool: if config.input_dtype != config.output_dtype: return False # As for fp6 data items, they are packed and stored in a set of fp16 # tensors. E.g., 8 fp6 data items are stored in 3 fp16 tensor. if config.input_dtype != torch.float16: return False if is_gated(config.activation): try: _ = CUDAGatedActivation(config.out_channels, config.output_dtype, config.activation) except ValueError: return False else: try: _ = CUDABiasActivation(config.out_channels, config.output_dtype, config.activation) except ValueError: return False return True def __init__(self, config: DSLinearConfig, implementation_config: Dict[str, Any]) -> None: super().__init__(config, implementation_config) self._linear_impl = CUDAWf6Af16Linear() if is_gated(config.activation): # In the FP6 kernel implementation, the MatMul is W * A, where W is # the weight and A is activation. M is the output channel size. self.out_channels = self._config.out_channels * 2 self.in_channels = self._config.in_channels self._is_gated = True self._act_fn = CUDAGatedActivation(config.out_channels, config.output_dtype, config.activation) self._double_buffer = torch.empty((config.max_tokens, config.out_channels * 2), dtype=config.output_dtype, device=get_accelerator().current_device()) else: self.out_channels = self._config.out_channels self.in_channels = self._config.in_channels self._is_gated = False self._act_fn = CUDABiasActivation(config.out_channels, config.output_dtype, config.activation) self._output = torch.empty((config.max_tokens, config.out_channels), dtype=config.output_dtype, device=get_accelerator().current_device()) self.inf_module = InferenceCoreBuilder().load() self.inf_module.create_handle() self.preprocess_weight = self.inf_module.preprocess_weight self.quantizer = fp_quantize def transform_param(self, param: torch.Tensor) -> InferenceParameter: """ Converts param to same data type as input and output. Parameters: param (torch.Tensor): Weight or bias tensor. """ # It expects that the quantization scales are store in the attribute `scales`. if param.ndim == 1: # bias, do nothing return InferenceParameter.initialize(param) quantized_fake_fp6, scales = self.quantizer(param, num_bits=6, exp_bits=3) # This is for debugging, will delete before release. assert (quantized_fake_fp6.dtype == torch.float16) assert quantized_fake_fp6.shape[0] == self.out_channels assert scales.numel() == self.out_channels weights_2bit, weights_4bit = self.preprocess_weight(quantized_fake_fp6) return InferenceParameter.initialize(weights_2bit, weights_4bit=weights_4bit, scales=scales) def forward(self, hidden_states: torch.Tensor, w: torch.Tensor, b: Optional[torch.Tensor] = None) -> torch.Tensor: weights_2bit = w weights_4bit = w.weights_4bit scales = w.scales output = empty_from(self._output, (hidden_states.shape[0], self._config.out_channels)) if self._is_gated: staging_output = empty_from(self._double_buffer, (hidden_states.shape[0], self.out_channels)) self._linear_impl(staging_output, hidden_states, weights_2bit, weights_4bit, scales, self.out_channels, hidden_states.shape[0], self.in_channels) self._act_fn(output, staging_output, b) else: self._linear_impl(output, hidden_states, weights_2bit, weights_4bit, scales, self.out_channels, hidden_states.shape[0], self.in_channels) self._act_fn(output, b) return output @property def output(self) -> torch.Tensor: """ Return the padded, pre-allocated output Tensor. """ return self._output