Files
paddlepaddle--paddle/test/legacy_test/test_fake_quantize_op.py
T
2026-07-13 12:40:42 +08:00

731 lines
25 KiB
Python

# Copyright (c) 2022 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 itertools
import math
import unittest
import numpy as np
from op_test import OpTest
from paddle import _C_ops
def round_c_single_element(val):
dtype = type(val)
if val >= 0:
return dtype(np.floor(val + 0.5))
return dtype(np.ceil(val - 0.5))
# rounding to nearest ties away from zero
round_c = np.vectorize(round_c_single_element)
def get_compute_type(dtype):
assert dtype in [np.float16, np.float32, np.float64]
if dtype == np.float16:
return np.float32
return dtype
def fake_channel_wise_quantize_dequantize_abs_max_wrapper(
x, bit_length=8, round_type=1, quant_axis=0
):
return _C_ops.fake_channel_wise_quantize_dequantize_abs_max(
x, bit_length, round_type, quant_axis
)
def fake_quantize_dequantize_moving_average_abs_max_wrapper(
x,
in_scale,
in_accum,
in_state,
moving_rate=0.9,
bit_length=8,
is_test=False,
round_type=1,
):
return _C_ops.fake_quantize_dequantize_moving_average_abs_max(
x,
in_scale,
in_accum,
in_state,
moving_rate,
bit_length,
is_test,
round_type,
)
def fake_quantize_dequantize_abs_max_wrapper(x, bit_length=8, round_type=1):
return _C_ops.fake_quantize_dequantize_abs_max(x, bit_length, round_type)
class TestFakeQuantizeAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_quantize_abs_max'
self.attrs = {'bit_length': 8}
def _fake_quantize_abs_max(
self, dtype, input_shape, distribution, round_type='TiesAwayFromZero'
):
input_data = distribution(input_shape).astype(dtype)
compute_type = get_compute_type(dtype)
scale = np.max(np.abs(input_data)).flatten()
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
inv_scale = 1.0 / (scale + 1e-6) if scale < 1e-30 else 1.0 / scale
if round_type == 'TiesToEven':
round_out = np.round(
input_data.astype(compute_type) * inv_scale * bnt
)
output_data = np.clip(round_out, -bnt - 1, bnt)
self.attrs['round_type'] = 0
else:
output_data = round_c(
input_data.astype(compute_type) * inv_scale * bnt
)
self.attrs['round_type'] = 1
self.inputs = {'X': input_data}
self.outputs = {'Out': output_data, 'OutScale': scale}
self.dtype = dtype
self.check_output(check_dygraph=False)
def test_fake_quantize_abs_max(self):
self._fake_quantize_abs_max(np.float32, (124, 240), np.random.random)
def test_fake_quantize_abs_max_round1(self):
self._fake_quantize_abs_max(
np.float32, (124, 240), np.random.random, round_type='TiesToEven'
)
def test_fake_quantize_abs_max_float16(self):
self._fake_quantize_abs_max(np.float16, (124, 240), np.random.random)
def test_fake_quantize_abs_max_underflow(self):
self._fake_quantize_abs_max(np.float32, (10, 10), np.zeros)
def test_fake_quantize_abs_max_underflow2(self):
self._fake_quantize_abs_max(
np.float32, (10, 10), lambda shape: np.full(shape, 1e-40)
)
class TestFakeChannelWiseQuantizeAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_channel_wise_quantize_abs_max'
self.attrs = {'bit_length': 8}
def _fake_channel_wise_quantize_abs_max(
self,
dtype,
input_shape,
quant_axis,
distribution,
round_type='TiesToEven',
):
assert quant_axis in [0, 1], 'quant_axis should be 0 or 1.'
input_data = distribution(input_shape).astype(dtype)
compute_type = get_compute_type(dtype)
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
compute_axis = tuple(
i for i in range(len(input_shape)) if i != quant_axis
)
scale_broadcast = np.amax(input_data, axis=compute_axis, keepdims=True)
if round_type == 'TiesToEven':
round_out = np.round(
input_data.astype(compute_type) / scale_broadcast * bnt
)
output_data = np.clip(round_out, -bnt - 1, bnt)
self.attrs['round_type'] = 0
else:
output_data = round_c(
bnt * input_data.astype(compute_type) / scale_broadcast
)
self.attrs['round_type'] = 1
if quant_axis == 1:
scale_broadcast = np.transpose(scale_broadcast, (1, *compute_axis))
scale = scale_broadcast.reshape(input_shape[quant_axis], -1)[:, 0]
self.inputs = {'X': input_data}
self.outputs = {'Out': output_data, 'OutScale': scale}
self.dtype = dtype
self.attrs['quant_axis'] = quant_axis
self.check_output(check_dygraph=False)
def test_fake_channel_wise_quantize_abs_max(self):
dtype_options = [np.float32, np.float16]
input_shape_quant_axis_options = [
[(20, 15, 6, 6), 0],
[(20, 15, 6, 6), 1],
[(30, 30), 0],
[(30, 30), 1],
]
round_type_options = ['TiesToEven', 'TiesAwayFromZero']
for dtype, input_shape_quant_axis, round_type in itertools.product(
dtype_options, input_shape_quant_axis_options, round_type_options
):
input_shape, quant_axis = input_shape_quant_axis
with self.subTest(
dtype=dtype,
input_shape=input_shape,
quant_axis=quant_axis,
round_type=round_type,
):
self._fake_channel_wise_quantize_abs_max(
dtype, input_shape, quant_axis, np.random.random, round_type
)
class TestFakeChannelWiseQuantizeDequantizeAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_channel_wise_quantize_dequantize_abs_max'
self.attrs = {'bit_length': 8}
def _fake_channel_wise_quantize_dequantize_abs_max(
self,
dtype,
input_shape,
quant_axis,
distribution,
round_type='TiesToEven',
):
assert quant_axis in [0, 1], 'quant_axis should be 0 or 1.'
input_data = distribution(input_shape).astype(dtype)
compute_type = get_compute_type(dtype)
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
output_data = input_data.copy().astype(compute_type)
compute_axis = tuple(
i for i in range(len(input_shape)) if i != quant_axis
)
scale_broadcast = np.amax(input_data, axis=compute_axis, keepdims=True)
if round_type == 'TiesToEven':
round_out = np.round(bnt * output_data / scale_broadcast)
output_data = (
np.clip(round_out, -bnt - 1, bnt) * scale_broadcast / bnt
)
self.attrs['round_type'] = 0
else:
output_data = (
round_c(bnt * output_data / scale_broadcast)
* scale_broadcast
/ bnt
)
self.attrs['round_type'] = 1
if quant_axis == 1:
scale_broadcast = np.transpose(scale_broadcast, (1, *compute_axis))
scale = scale_broadcast.reshape(input_shape[quant_axis], -1)[:, 0]
self.python_api = fake_channel_wise_quantize_dequantize_abs_max_wrapper
self.inputs = {'X': input_data}
self.outputs = {'Out': output_data, 'OutScale': scale}
self.dtype = dtype
self.attrs['quant_axis'] = quant_axis
self.check_output(check_dygraph=False, check_pir=True)
gradient = [np.ones(input_data.shape) / np.prod(input_data.shape)]
self.check_grad(['X'], 'Out', user_defined_grads=gradient)
def test_channel_wise_fake_quant_dequant_abs_max(self):
input_shape_quant_axis_options = [
[(3, 4, 64, 64), 0],
[(15, 20, 5, 5), 1],
[(30, 15), 0],
[(30, 15), 1],
]
round_type_options = ['TiesToEven', 'TiesAwayFromZero']
for input_shape_quant_axis, round_type in itertools.product(
input_shape_quant_axis_options, round_type_options
):
input_shape, quant_axis = input_shape_quant_axis
with self.subTest(
input_shape=input_shape,
quant_axis=quant_axis,
round_type=round_type,
):
self._fake_channel_wise_quantize_dequantize_abs_max(
np.float32,
input_shape,
quant_axis,
np.random.random,
round_type=round_type,
)
class TestFakeQuantizeRangeAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_quantize_range_abs_max'
self.attrs = {'bit_length': 5, 'window_size': 1}
def _fake_quantize_range_abs_max(
self,
dtype,
input_shape,
distribution,
is_test=False,
round_type='TiesToEven',
):
input_data = distribution(input_shape).astype(dtype)
compute_type = get_compute_type(dtype)
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
in_scale = np.zeros(1).astype(dtype)
out_scale = np.zeros(self.attrs['window_size']).astype(dtype)
out_scale[0] = np.max(np.abs(input_data))
if is_test:
out_scale[0] = in_scale[0] = out_scale[0] - 1.0
if round_type == 'TiesToEven':
round_out = np.round(
input_data.astype(compute_type) / out_scale[0] * bnt
)
self.attrs['round_type'] = 0
output_data = np.clip(round_out, -bnt - 1, bnt)
else:
if is_test:
clip_data = np.clip(input_data, -in_scale, in_scale)
else:
clip_data = input_data
output_data = round_c(
clip_data.astype(compute_type) / out_scale[0] * bnt
)
self.attrs['round_type'] = 1
self.inputs = {
'X': input_data,
'Iter': np.zeros(1).astype(np.int64),
'InScale': in_scale,
}
self.outputs = {
'Out': output_data,
'OutScale': np.array([], dtype) if is_test else out_scale,
'OutScales': np.array([], dtype) if is_test else out_scale,
}
self.dtype = dtype
self.attrs['is_test'] = is_test
self.check_output(check_dygraph=False)
def test_fake_quantize_range_abs_max(self):
dtype_options = [np.float16, np.float32]
is_test_options = [False, True]
round_type_options = ['TiesToEven', 'TiesAwayFromZero']
for dtype, is_test, round_type in itertools.product(
dtype_options, is_test_options, round_type_options
):
self.attrs['bit_length'] = 8 if is_test else 5
with self.subTest(
dtype=dtype, is_test=is_test, round_type=round_type
):
self._fake_quantize_range_abs_max(
dtype,
(8, 16, 6, 6),
lambda shape: (np.random.random(shape) - 0.4) * 10,
is_test=is_test,
round_type=round_type,
)
class TestMovingAverageAbsMaxScaleOp(OpTest):
def setUp(self):
self.op_type = 'moving_average_abs_max_scale'
self.attrs = {'moving_rate': 0.9, 'is_test': False}
def _moving_average_abs_max_scale(self, dtype, input_shape, distribution):
input_data = distribution(input_shape).astype(dtype)
in_accum = np.ones(1).astype(dtype)
in_state = np.ones(1).astype(dtype)
out_accum = self.attrs['moving_rate'] * in_accum + np.max(
np.abs(input_data)
)
out_state = self.attrs['moving_rate'] * in_state + 1.0
out_scale = out_accum / out_state
self.inputs = {
'X': input_data,
'InAccum': in_accum,
'InState': in_state,
}
self.outputs = {
'Out': input_data,
'OutAccum': out_accum,
'OutState': out_state,
'OutScale': out_scale,
}
self.dtype = dtype
self.check_output(check_dygraph=False)
def test_moving_average_abs_max(self):
self._moving_average_abs_max_scale(
np.float32, (8, 16, 7, 7), np.random.random
)
class TestFakeQuantizeMovingAverageAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_quantize_moving_average_abs_max'
self.attrs = {'bit_length': 5, 'moving_rate': 0.9, 'is_test': False}
self.python_api = (
fake_quantize_dequantize_moving_average_abs_max_wrapper
)
def _fake_quantize_moving_average_abs_max(
self,
dtype,
input_shape,
distribution,
dequantize=False,
with_gradient=False,
round_type='TiesAwayFromZero',
):
input_data = distribution(input_shape).astype(dtype)
compute_type = get_compute_type(dtype)
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
in_accum = np.ones(1).astype(dtype)
in_state = np.ones(1).astype(dtype)
in_scale = np.array([0.001]).astype(dtype)
out_accum = self.attrs['moving_rate'] * in_accum + np.max(
np.abs(input_data)
)
out_state = self.attrs['moving_rate'] * in_state + 1.0
out_scale = out_accum / out_state
if round_type == 'TiesToEven':
round_out = np.round(
input_data.astype(compute_type) / out_scale * bnt
)
quant_data = np.clip(round_out, -bnt - 1, bnt)
self.attrs['round_type'] = 0
else:
quant_data = round_c(
input_data.astype(compute_type) / out_scale * bnt
)
self.attrs['round_type'] = 1
if dequantize:
output_data = (quant_data * out_scale / bnt).astype(dtype)
self.op_type = 'fake_quantize_dequantize_moving_average_abs_max'
else:
output_data = quant_data.astype(dtype)
self.inputs = {
'X': input_data,
'InScale': in_scale,
'InAccum': in_accum,
'InState': in_state,
}
self.outputs = {
'Out': output_data,
'OutAccum': out_accum,
'OutState': out_state,
'OutScale': out_scale,
}
self.dtype = dtype
self.check_output(check_dygraph=False)
if with_gradient:
gradient = [np.ones(input_data.shape) / np.prod(input_data.shape)]
self.check_grad(['X'], 'Out', user_defined_grads=gradient)
def test_fake_quantize_moving_average_abs_max(self):
self._fake_quantize_moving_average_abs_max(
np.float32, (8, 16, 7, 7), np.random.random
)
def test_fake_quantize_moving_average_abs_max_float16(self):
self._fake_quantize_moving_average_abs_max(
np.float16, (8, 16, 7, 7), np.random.random
)
def test_fake_quantize_moving_average_abs_max_round1(self):
self._fake_quantize_moving_average_abs_max(
np.float32, (8, 16, 7, 7), np.random.random, round_type='TiesToEven'
)
def test_fake_quantize_dequantize_moving_average_abs_max(self):
self._fake_quantize_moving_average_abs_max(
np.float32,
(8, 16, 7, 7),
np.random.random,
dequantize=True,
with_gradient=True,
)
class TestFakeQuantizeDequantizeAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_quantize_dequantize_abs_max'
self.attrs = {'bit_length': 8}
self.python_api = fake_quantize_dequantize_abs_max_wrapper
def _fake_quantize_dequantize_abs_max(
self, dtype, input_shape, distribution, round_type='TiesAwayFromZero'
):
input_data = distribution(input_shape).astype(dtype)
scale = np.max(np.abs(input_data)).flatten().astype(dtype)
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
if round_type == 'TiesToEven':
round_out = np.round(input_data / scale * bnt)
output_data = np.clip(round_out, -bnt - 1, bnt) * scale / bnt
self.attrs['round_type'] = 0
else:
output_data = round_c(input_data / scale * bnt) * scale / bnt
self.attrs['round_type'] = 1
self.inputs = {'X': input_data}
self.outputs = {
'Out': output_data,
'OutScale': np.array(scale).astype(dtype),
}
self.dtype = dtype
self.check_output(check_dygraph=False)
gradient = [np.ones(input_data.shape) / np.prod(input_data.shape)]
self.check_grad(['X'], 'Out', user_defined_grads=gradient)
def test_fake_quantize_dequantize_abs_max(self):
self._fake_quantize_dequantize_abs_max(
np.float32, (124, 240), np.random.random
)
def test_fake_quantize_dequantize_abs_max_round1(self):
self._fake_quantize_dequantize_abs_max(
np.float32, (124, 240), np.random.random, round_type='TiesToEven'
)
class TestChannelWiseFakeQuantizeDequantizeAbsMaxOp(OpTest):
def setUp(self):
self.op_type = 'fake_channel_wise_quantize_dequantize_abs_max'
self.attrs = {'bit_length': 8}
def _fake_channel_wise_quantize_dequantize_abs_max(
self,
dtype,
input_shape,
quant_axis,
distribution,
round_type='TiesToEven',
):
assert quant_axis in [0, 1], 'quant_axis should be 0 or 1.'
input_data = distribution(input_shape).astype(dtype)
compute_type = get_compute_type(dtype)
bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
output_data = input_data.copy().astype(compute_type)
compute_axis = tuple(
i for i in range(len(input_shape)) if i != quant_axis
)
scale_broadcast = np.amax(input_data, axis=compute_axis, keepdims=True)
if round_type == 'TiesToEven':
round_out = np.round(bnt * output_data / scale_broadcast)
output_data = (
np.clip(round_out, -bnt - 1, bnt) * scale_broadcast / bnt
)
self.attrs['round_type'] = 0
else:
output_data = (
round_c(bnt * output_data / scale_broadcast)
* scale_broadcast
/ bnt
)
self.attrs['round_type'] = 1
if quant_axis == 1:
scale_broadcast = np.transpose(scale_broadcast, (1, *compute_axis))
scale = scale_broadcast.reshape(input_shape[quant_axis], -1)[:, 0]
self.python_api = fake_channel_wise_quantize_dequantize_abs_max_wrapper
self.inputs = {'X': input_data}
self.outputs = {'Out': output_data, 'OutScale': scale}
self.dtype = dtype
self.attrs['quant_axis'] = quant_axis
self.check_output(check_dygraph=False)
gradient = [np.ones(input_data.shape) / np.prod(input_data.shape)]
self.check_grad(['X'], 'Out', user_defined_grads=gradient)
def test_channel_wise_fake_quant_dequant_abs_max(self):
input_shape_quant_axis_options = [
[(3, 4, 64, 64), 0],
[(15, 20, 5, 5), 1],
[(30, 15), 0],
[(30, 15), 1],
]
round_type_options = ['TiesToEven', 'TiesAwayFromZero']
for input_shape_quant_axis, round_type in itertools.product(
input_shape_quant_axis_options, round_type_options
):
input_shape, quant_axis = input_shape_quant_axis
with self.subTest(
input_shape=input_shape,
quant_axis=quant_axis,
round_type=round_type,
):
self._fake_channel_wise_quantize_dequantize_abs_max(
np.float32,
input_shape,
quant_axis,
np.random.random,
round_type=round_type,
)
def quantize_max_abs(x, max_range):
scale = np.max(np.abs(x).flatten())
y = np.round(x / scale * max_range)
return y, scale
def channel_wise_quantize_max_abs(x, quant_bit=8, quant_axis=0):
assert quant_axis in [0, 1], "The quant_axis should be 0 or 1."
scales = []
y = x.copy()
max_range = math.pow(2, quant_bit - 1) - 1
if quant_axis == 0:
for i in range(x.shape[0]):
scale = np.max(np.abs(x[i])).astype("float32")
scales.append(scale)
y[i] = np.round(x[i] * max_range / scale)
elif quant_axis == 1:
for i in range(x.shape[1]):
scale = np.max(np.abs(x[:, i])).astype("float32")
scales.append(scale)
y[:, i] = np.round(x[:, i] * max_range / scale)
return y, scales
class TestChannelWiseQuantizeOp(OpTest):
def set_args(self):
self.bit_length = 8
self.data_type = "float32"
self.quant_axis = 0
def setUp(self):
self.set_args()
self.op_type = "quantize_linear"
x = np.random.randn(4, 3, 64, 64).astype(self.data_type)
yq, scale = channel_wise_quantize_max_abs(
x, self.bit_length, self.quant_axis
)
scale = np.array(scale).astype(self.data_type)
zero_point = np.zeros(scale.shape, dtype="int32")
self.inputs = {'X': x, 'Scale': scale, 'ZeroPoint': zero_point}
self.attrs = {
'bit_length': self.bit_length,
'quant_axis': self.quant_axis,
}
self.outputs = {'Y': yq}
def test_check_output(self):
self.check_output(check_dygraph=False)
class TestChannelWiseQuantizeOp1(TestChannelWiseQuantizeOp):
def set_args(self):
self.bit_length = 8
self.data_type = "float32"
self.quant_axis = 1
class TestChannelWiseQuantizeOpTrain(OpTest):
def set_args(self):
self.bit_length = 8
self.data_type = "float32"
self.quant_axis = 0
self.is_test = False
def setUp(self):
self.set_args()
self.op_type = "quantize_linear"
x = np.random.randn(4, 3, 64, 64).astype(self.data_type)
yq, scale = channel_wise_quantize_max_abs(
x, self.bit_length, self.quant_axis
)
scale = np.array(scale).astype(self.data_type)
zero_point = np.zeros(scale.shape, dtype="int32")
self.inputs = {'X': x, 'Scale': scale, 'ZeroPoint': zero_point}
self.attrs = {
'bit_length': self.bit_length,
'quant_axis': self.quant_axis,
'is_test': self.is_test,
}
self.outputs = {'Y': yq, 'OutScale': scale}
def test_check_output(self):
self.check_output(check_dygraph=False)
class TestquantizeOp(OpTest):
def set_args(self):
self.bit_length = 8
self.quant_axis = -1
self.max_range = math.pow(2, self.bit_length - 1) - 1
self.data_type = "float32"
def setUp(self):
self.set_args()
self.op_type = "quantize_linear"
x = np.random.randn(31, 65).astype(self.data_type)
yq, scale = quantize_max_abs(x, self.max_range)
scale = np.array(scale).astype(self.data_type)
zero_point = np.zeros(scale.shape, dtype="int32")
self.inputs = {'X': x, 'Scale': scale, 'ZeroPoint': zero_point}
self.attrs = {
'bit_length': self.bit_length,
'quant_axis': self.quant_axis,
}
self.outputs = {'Y': yq}
def test_check_output(self):
self.check_output(check_dygraph=False)
class TestquantizeOpTrain(TestquantizeOp):
def set_args(self):
self.bit_length = 8
self.quant_axis = -1
self.max_range = math.pow(2, self.bit_length - 1) - 1
self.data_type = "float32"
self.is_test = False
def setUp(self):
self.set_args()
self.op_type = "quantize_linear"
self.attrs = {
'bit_length': self.bit_length,
'quant_axis': self.quant_axis,
'moving_rate': 0.9,
'is_test': self.is_test,
}
x = np.random.randn(31, 65).astype(self.data_type)
scale = np.array([0.001]).astype(self.data_type)
zero_point = np.zeros(scale.shape, dtype="int32")
in_accum = np.ones(1).astype(self.data_type)
in_state = np.ones(1).astype(self.data_type)
out_accum = self.attrs['moving_rate'] * in_accum + np.max(np.abs(x))
out_state = self.attrs['moving_rate'] * in_state + 1.0
out_scale = out_accum / out_state
round_out = np.round(x / out_scale * self.max_range)
quant_data = np.clip(round_out, -self.max_range - 1, self.max_range)
self.inputs = {
'X': x,
'Scale': scale,
'ZeroPoint': zero_point,
'InAccum': in_accum,
'InState': in_state,
}
self.outputs = {
'Y': quant_data,
'OutScale': out_scale,
'OutAccum': out_accum,
'OutState': out_state,
}
def test_check_output(self):
self.check_output(check_dygraph=False)
if __name__ == '__main__':
unittest.main()