Files
paddlepaddle--paddle/python/paddle/nn/quant/quantized_linear.py
T
2026-07-13 12:40:42 +08:00

396 lines
14 KiB
Python

# Copyright (c) 2023 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.
from __future__ import annotations
from typing import TYPE_CHECKING, Literal
import paddle
from paddle import _C_ops
from paddle.base.data_feeder import check_dtype
from paddle.device import (
is_compiled_with_cuda,
)
from paddle.device.cuda import get_device_capability
from paddle.framework import (
LayerHelper,
in_dynamic_or_pir_mode,
)
if TYPE_CHECKING:
from typing import TypeAlias
from paddle import Tensor
from paddle._typing import DTypeLike
_Algo: TypeAlias = Literal[
'weight_only_int8', 'weight_only_int4', 'llm.int8'
]
_GroupSize: TypeAlias = Literal[-1, 64, 128]
def _get_arch_info():
# Get SMVersion from device.
if is_compiled_with_cuda():
cuda_version = paddle.version.cuda()
if (
cuda_version is not None and cuda_version != 'False'
) or paddle.is_compiled_with_rocm():
major, minor = get_device_capability()
arch = int(major * 10 + minor)
return arch
else:
raise ValueError(
"Paddle is not compiled with CUDA, we cannot get SMVersion from device, please try to compile Paddle with CUDA"
)
else:
# Default arch value for type checking.
return 0
def weight_quantize(
x: Tensor,
algo: _Algo = "weight_only_int8",
arch: int | None = None,
group_size: _GroupSize = -1,
) -> tuple[Tensor, Tensor]:
"""
Quantization function for weight_only and llm.int8's weight.
Args:
x (Tensor): The input Tensor to be quantized, the data type is float16 or bfloat16.
algo (str): The algo that is x will be apply, must be one of 'weight_only_int8',
'weight_only_int4', 'llm.int8', 'w4a8' and 'w4afp8, default: 'weight_only_int8'.
arch (int): The compute arch for target device. For example, A100 is 80, v100 is 70, if you do not assign arch, we will get arch from your device, default: None.
group_size (int): The group size for weight quantization. -1 stands for default per-channel mode. Currently only support 64 or 128.
Returns:
out (Tensor): The Tensor which is the quantitative results, the data type is int8, the shape is transposition of x.
scale (Tensor): The scale Tensor which is the scale of pre-channel, the data type is float32.
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('No testing required')
>>> import paddle
>>> from paddle.nn.quant import weight_quantize
>>> paddle.seed(2023)
>>> x = paddle.rand(shape=[64, 32], dtype=paddle.float16)
>>> out, scale = weight_quantize(x, algo='weight_only_int8')
>>> print(out.shape)
paddle.Size([32, 64])
>>> print(scale.shape)
paddle.Size([32])
"""
if arch is None:
arch = _get_arch_info()
if is_compiled_with_cuda():
assert (
arch == 70
or arch == 75
or arch == 80
or arch == 86
or arch == 89
or arch == 90
or arch == 92
or arch == 100
), (
f"Currently weight_quantize only support SM70/75/80/86/89/90/92/100. but got {arch} "
)
assert group_size == -1 or group_size == 64 or group_size == 128, (
f"Currently group_size only support -1/64/128. but got {group_size} "
)
if in_dynamic_or_pir_mode():
return _C_ops.weight_quantize(x, algo, arch, group_size)
else:
type = "weight_quantize"
helper = LayerHelper(type, **locals())
out = helper.create_variable_for_type_inference('int8')
scale = helper.create_variable_for_type_inference('float')
helper.append_op(
type=type,
inputs={"x": x},
outputs={'out': out, "scale": scale},
attrs={"algo": algo, "arch": arch, "group_size": group_size},
)
return (out, scale)
def weight_dequantize(
x: Tensor,
scale: Tensor,
algo: _Algo = "weight_only_int8",
out_dtype: DTypeLike = "float16",
group_size: _GroupSize = -1,
) -> Tensor:
"""
Dequantization function for weight_only and llm.int8's weight.
Args:
x (Tensor): The input Tensor to be dequantized, the data type is int8.
scale (Tensor): The scale Tensor which is the output of weight_quantize, the data type is float32.
algo (str): The algo that is x will be apply, must be one of 'weight_only_int8',
'weight_only_int4' and 'llm.int8', default: 'weight_only_int8'.
out_dtype (str|np.dtype): [Deprecated][Not used] The output Tensor's data type, must be one of 'float16' and 'bfloat16', default: 'float16'.
Returns:
out (Tensor): The Tensor which is the dequantitative results, the data type is float16 or bfloat16, the shape is transposition of x.
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('No testing required')
>>> import paddle
>>> from paddle.nn.quant import weight_quantize, weight_dequantize
>>> paddle.seed(2023)
>>> x = paddle.rand(shape=[64, 32], dtype=paddle.float16)
>>> out, scale = weight_quantize(x, algo='weight_only_int8')
>>> x_dequant = weight_dequantize(out, scale)
"""
assert group_size == -1 or group_size == 64 or group_size == 128, (
f"Currently group_size only support -1/64/128. but got {group_size} "
)
if in_dynamic_or_pir_mode():
return _C_ops.weight_dequantize(x, scale, algo, group_size)
else:
type = "weight_dequantize"
helper = LayerHelper(type, **locals())
out_dtype = scale.dtype
out = helper.create_variable_for_type_inference(out_dtype)
helper.append_op(
type=type,
inputs={"x": x, "scale": scale},
outputs={'out': out},
attrs={
"algo": algo,
"group_size": group_size,
},
)
return out
def weight_only_linear(
x: Tensor,
weight: Tensor,
bias: Tensor | None = None,
weight_scale: Tensor | None = None,
weight_dtype: DTypeLike = "int8",
arch: int | None = None,
group_size: _GroupSize = -1,
) -> Tensor:
"""
Applies matrix multiplication of two tensors and then bias addition if provided.
This method requires CUDA version >= 11.2.
Args:
x (Tensor): The first input Tensor to be multiplied, the data type is float16 or bfloat16.
weight (Tensor): The second input Tensor to be multiplied. Its rank must be 2.
bias (Tensor|None): The input bias Tensor. If it is None, no bias addition would
be performed. Otherwise, The bias is added to the matrix multiplication result.
weight_scale (Tensor|None): The input scale Tensor Provided to weight for dequantization. Its rank must be 1.
weight_dtype(str): The dtype of weight Tensor, must be one of 'int8', 'int4', Defaulted to 'int8'.
arch (int): The compute arch for target device. For example, A100 is 80, v100 is 70, if you do not assign arch, we will get arch from your device, default: None.
group_size (int): The group size for weight quantization. -1 stands for default per-channel mode. Currently only support 64 or 128.
Returns:
Tensor: the output Tensor, the data type is the same as that of x.
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('No testing required')
>>> import paddle
>>> from paddle.nn.quant import weight_only_linear
>>> x = paddle.cast(paddle.randn([1, 2, 64]), dtype='float16')
>>> weight = paddle.cast(paddle.randint(0, 127, [32, 64]), dtype='int8')
>>> scale = paddle.randn([32], dtype='float32')
>>> bias = paddle.cast(paddle.randn([32]), dtype='float16')
>>> if paddle.device.cuda.get_device_capability()[0] >= 8:
... out = weight_only_linear(
... x,
... weight,
... bias=bias,
... weight_scale=scale,
... weight_dtype='int8',
... )
... print(out.shape)
paddle.Size([1, 2, 32])
"""
if arch is None:
arch = _get_arch_info()
if is_compiled_with_cuda():
assert (
arch == 70
or arch == 75
or arch == 80
or arch == 86
or arch == 89
or arch == 90
or arch == 92
or arch == 100
), (
f"Currently weight_quantize only support SM70/75/80/86/89/90/92/100. but got {arch} "
)
assert group_size == -1 or group_size == 64 or group_size == 128, (
f"Currently weight_quantize only support group size of -1, 64 or 128. but got {group_size} "
)
if in_dynamic_or_pir_mode():
out = _C_ops.weight_only_linear(
x, weight, bias, weight_scale, weight_dtype, arch, group_size
)
return out
else:
check_dtype(
weight_dtype, 'weight_dtype', ['int8', 'int4'], 'weight_only_linear'
)
type = "weight_only_linear"
helper = LayerHelper(type, **locals())
dtype = x.dtype
inputs = {
'x': [x],
'weight': [weight],
'weight_scale': [weight_scale],
}
if bias is not None:
inputs["bias"] = [bias]
attrs = {
'weight_dtype': weight_dtype,
'arch': arch,
'group_size': group_size,
}
out = helper.create_variable_for_type_inference(dtype)
helper.append_op(
type=type,
inputs=inputs,
outputs={'out': out},
attrs=attrs,
)
return out
def llm_int8_linear(
x: Tensor,
weight: Tensor,
bias: Tensor | None = None,
weight_scale: Tensor | None = None,
threshold: float = 6.0,
) -> Tensor:
"""
Applies matrix multiplication of two tensors and then bias addition if provided.
This method requires CUDA version >= 11.2.
Args:
x (Tensor): the first input Tensor to be multiplied, the data type is float16 or bfloat16.
weight (Tensor): the second input Tensor to be multiplied. Its rank must be 2.
bias (Tensor|None): the input bias Tensor. If it is None, no bias addition would
be performed. Otherwise, the bias is added to the matrix multiplication result.
weight_scale (Tensor|None): the input scale Tensor Provided to weight for dequantization. Its rank must be 1.
threshold(float): The min value of outlier in activation, outlier's channel will be apply multiply with x.dtype.
Returns:
Tensor: the output Tensor, the data type is the same as that of x.
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('No testing required')
>>> import paddle
>>> from paddle.nn.quant import llm_int8_linear
>>> x = paddle.cast(paddle.randn([1, 2, 64]), dtype='float16')
>>> weight = paddle.cast(paddle.randint(0, 127, [32, 64]), dtype='int8')
>>> scale = paddle.randn([32], dtype='float32')
>>> bias = paddle.cast(paddle.randn([32]), dtype='float16')
>>> if paddle.device.cuda.get_device_capability()[0] >= 8:
... out = llm_int8_linear(x, weight, bias=bias, weight_scale=scale, threshold=6.0)
... print(out.shape)
paddle.Size([1, 2, 32])
"""
if in_dynamic_or_pir_mode():
out = _C_ops.llm_int8_linear(x, weight, bias, weight_scale, threshold)
return out
else:
type = "llm_int8_linear"
helper = LayerHelper(type, **locals())
dtype = x.dtype
inputs = {
'x': [x],
'weight': [weight],
'weight_scale': [weight_scale],
}
if bias:
inputs["bias"] = [bias]
attrs = {'threshold': threshold}
out = helper.create_variable_for_type_inference(dtype)
helper.append_op(
type=type,
inputs=inputs,
outputs={'out': out},
attrs=attrs,
)
return out
def apply_per_channel_scale(x: Tensor, scales: Tensor) -> Tensor:
"""
Apply pre-quant per channel scale on activations
Args:
x (Tensor): Input tensor representing the activations, the data type can be float16 or bfloat16.
scales(Tensor): Per-channel scale factors for pre-quantization. Data type should be compatible with x.
Returns:
out (Tensor): The Tensor which is the pre-quant results, the data type is compatible with x.
Examples:
.. code-block:: pycon
>>> # doctest: +SKIP('No testing required')
>>> import paddle
>>> from paddle.nn.quant import apply_per_channel_scale
>>> paddle.seed(2023)
>>> x = paddle.rand(shape=[64, 32], dtype=paddle.float16)
>>> scales = paddle.rand(shape=[32], dtype=paddle.float16)
>>> out = apply_per_channel_scale(x, scales)
"""
if in_dynamic_or_pir_mode():
return _C_ops.apply_per_channel_scale(x, scales)
else:
type = "apply_per_channel_scale"
helper = LayerHelper(type, **locals())
out = helper.create_variable_for_type_inference(x.dtype)
helper.append_op(
type=type,
inputs={"x": [x], "scales": [scales]},
outputs={"out": out},
)
return out