Files
2026-07-13 13:18:33 +08:00

206 lines
7.6 KiB
Python

# 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