test_fake_quantize_op.py 22.1 KB
Newer Older
1
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
视言's avatar
视言 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14
#
# 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.

15 16
from __future__ import print_function

视言's avatar
视言 已提交
17
import unittest
18
import itertools
视言's avatar
视言 已提交
19
import numpy as np
20
import math
21
from op_test import OpTest
视言's avatar
视言 已提交
22 23


24 25 26 27 28
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))
29 30


31
# rounding to nearest ties away from zero
32 33 34
round_c = np.vectorize(round_c_single_element)


35 36 37 38 39
def get_compute_type(dtype):
    assert dtype in [np.float16, np.float32, np.float64]
    if dtype == np.float16:
        return np.float32
    return dtype
40

Z
Zhen Wang 已提交
41

42
class TestFakeQuantizeAbsMaxOp(OpTest):
43

44
    def setUp(self):
45
        self.op_type = 'fake_quantize_abs_max'
46 47
        self.attrs = {'bit_length': 8}

48 49 50 51
    def _fake_quantize_abs_max(self,
                               dtype,
                               input_shape,
                               distribution,
52
                               round_type='TiesAwayFromZero'):
53 54 55 56
        input_data = distribution(input_shape).astype(dtype)
        compute_type = get_compute_type(dtype)
        scale = np.max(np.abs(input_data))
        bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
57
        inv_scale = 1.0 / (scale + 1e-6) if scale < 1e-30 else 1.0 / scale
58 59 60
        if round_type == 'TiesToEven':
            round_out = np.round(
                input_data.astype(compute_type) * inv_scale * bnt)
61
            output_data = np.clip(round_out, -bnt - 1, bnt)
62 63
            self.attrs['round_type'] = 0
        else:
64
            output_data = round_c(
65 66
                input_data.astype(compute_type) * inv_scale * bnt)
            self.attrs['round_type'] = 1
67 68 69
        self.inputs = {'X': input_data}
        self.outputs = {'Out': output_data, 'OutScale': scale}
        self.dtype = dtype
70 71
        self.check_output()

72 73
    def test_fake_quantize_abs_max(self):
        self._fake_quantize_abs_max(np.float32, (124, 240), np.random.random)
74

75 76 77
    def test_fake_quantize_abs_max_round1(self):
        self._fake_quantize_abs_max(np.float32, (124, 240),
                                    np.random.random,
78
                                    round_type='TiesToEven')
79

80 81
    def test_fake_quantize_abs_max_float16(self):
        self._fake_quantize_abs_max(np.float16, (124, 240), np.random.random)
82

83 84
    def test_fake_quantize_abs_max_underflow(self):
        self._fake_quantize_abs_max(np.float32, (10, 10), np.zeros)
85

86 87 88
    def test_fake_quantize_abs_max_underflow2(self):
        self._fake_quantize_abs_max(np.float32, (10, 10),
                                    lambda shape: np.full(shape, 1e-40))
Z
Zhen Wang 已提交
89

90

91
class TestFakeChannelWiseQuantizeAbsMaxOp(OpTest):
92

93 94 95
    def setUp(self):
        self.op_type = 'fake_channel_wise_quantize_abs_max'
        self.attrs = {'bit_length': 8}
96

97 98 99 100 101 102
    def _fake_channel_wise_quantize_abs_max(self,
                                            dtype,
                                            input_shape,
                                            quant_axis,
                                            distribution,
                                            round_type='TiesToEven'):
103 104 105 106
        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
107 108
        compute_axis = tuple(i for i in range(len(input_shape))
                             if i != quant_axis)
109
        scale_broadcast = np.amax(input_data, axis=compute_axis, keepdims=True)
110 111 112
        if round_type == 'TiesToEven':
            round_out = np.round(
                input_data.astype(compute_type) / scale_broadcast * bnt)
113
            output_data = np.clip(round_out, -bnt - 1, bnt)
114 115
            self.attrs['round_type'] = 0
        else:
116 117
            output_data = round_c(bnt * input_data.astype(compute_type) /
                                  scale_broadcast)
118
            self.attrs['round_type'] = 1
119 120 121 122 123 124 125 126
        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
Z
Zhen Wang 已提交
127
        self.check_output()
128

129 130 131
    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],
132 133 134 135 136 137
                                          [(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):
138
            input_shape, quant_axis = input_shape_quant_axis
139 140
            with self.subTest(dtype=dtype,
                              input_shape=input_shape,
141 142
                              quant_axis=quant_axis,
                              round_type=round_type):
143
                self._fake_channel_wise_quantize_abs_max(
144 145
                    dtype, input_shape, quant_axis, np.random.random,
                    round_type)
146 147


148
class TestFakeQuantizeRangeAbsMaxOp(OpTest):
149

150
    def setUp(self):
151 152 153 154 155 156 157
        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,
158 159
                                     is_test=False,
                                     round_type='TiesToEven'):
160 161 162 163 164 165 166 167
        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
168 169 170 171
        if round_type == 'TiesToEven':
            round_out = np.round(
                input_data.astype(compute_type) / out_scale[0] * bnt)
            self.attrs['round_type'] = 0
172
            output_data = np.clip(round_out, -bnt - 1, bnt)
173
        else:
174 175 176 177 178 179
            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)
180
            self.attrs['round_type'] = 1
视言's avatar
视言 已提交
181
        self.inputs = {
182 183 184
            'X': input_data,
            'Iter': np.zeros(1).astype(np.int64),
            'InScale': in_scale
视言's avatar
视言 已提交
185 186
        }
        self.outputs = {
187 188 189
            'Out': output_data,
            'OutScale': out_scale[0],
            'OutScales': out_scale
视言's avatar
视言 已提交
190
        }
191 192
        self.dtype = dtype
        self.attrs['is_test'] = is_test
视言's avatar
视言 已提交
193 194
        self.check_output()

195
    def test_fake_quantize_range_abs_max(self):
196
        dtype_options = [np.float16, np.float32]
197
        is_test_options = [False, True]
198 199 200
        round_type_options = ['TiesToEven', 'TiesAwayFromZero']
        for dtype, is_test, round_type in itertools.product(
                dtype_options, is_test_options, round_type_options):
201
            self.attrs['bit_length'] = 8 if is_test else 5
202 203 204
            with self.subTest(dtype=dtype,
                              is_test=is_test,
                              round_type=round_type):
205
                self._fake_quantize_range_abs_max(
206 207 208 209
                    dtype, (8, 16, 6, 6),
                    lambda shape: (np.random.random(shape) - 0.4) * 10,
                    is_test=is_test,
                    round_type=round_type)
210 211


Z
Zhen Wang 已提交
212
class TestMovingAverageAbsMaxScaleOp(OpTest):
213

Z
Zhen Wang 已提交
214
    def setUp(self):
215
        self.op_type = 'moving_average_abs_max_scale'
Z
Zhen Wang 已提交
216 217
        self.attrs = {'moving_rate': float(0.9), 'is_test': False}

218 219 220 221 222 223 224
    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[0] + np.max(
            np.abs(input_data))
        out_state = self.attrs['moving_rate'] * in_state[0] + 1.0
Z
Zhen Wang 已提交
225
        out_scale = out_accum / out_state
226 227 228 229 230
        self.inputs = {
            'X': input_data,
            'InAccum': in_accum,
            'InState': in_state
        }
Z
Zhen Wang 已提交
231
        self.outputs = {
232
            'Out': input_data,
Z
Zhen Wang 已提交
233 234
            'OutAccum': out_accum,
            'OutState': out_state,
235
            'OutScale': out_scale
Z
Zhen Wang 已提交
236
        }
237
        self.dtype = dtype
Z
Zhen Wang 已提交
238 239
        self.check_output()

240 241 242
    def test_moving_average_abs_max(self):
        self._moving_average_abs_max_scale(np.float32, (8, 16, 7, 7),
                                           np.random.random)
Z
Zhen Wang 已提交
243

244

245
class TestFakeQuantizeMovingAverageAbsMaxOp(OpTest):
246

247
    def setUp(self):
248 249 250 251 252 253 254 255
        self.op_type = 'fake_quantize_moving_average_abs_max'
        self.attrs = {'bit_length': 5, 'moving_rate': 0.9, 'is_test': False}

    def _fake_quantize_moving_average_abs_max(self,
                                              dtype,
                                              input_shape,
                                              distribution,
                                              dequantize=False,
256
                                              with_gradient=False,
257
                                              round_type='TiesAwayFromZero'):
258 259 260 261 262 263 264 265 266 267 268 269 270
        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 = np.zeros(1).astype(dtype)
        out_state = np.zeros(1).astype(dtype)
        out_scale = np.zeros(1).astype(dtype)
        out_accum[0] = self.attrs['moving_rate'] * in_accum[0] + np.max(
            np.abs(input_data))
        out_state[0] = self.attrs['moving_rate'] * in_state[0] + 1.0
        out_scale = out_accum / out_state
271 272 273
        if round_type == 'TiesToEven':
            round_out = np.round(
                input_data.astype(compute_type) / out_scale * bnt)
274
            quant_data = np.clip(round_out, -bnt - 1, bnt)
275 276
            self.attrs['round_type'] = 0
        else:
277
            quant_data = round_c(
278 279
                input_data.astype(compute_type) / out_scale * bnt)
            self.attrs['round_type'] = 1
280
        if dequantize:
281
            output_data = (quant_data * out_scale / bnt).astype(dtype)
282 283
            self.op_type = 'fake_quantize_dequantize_moving_average_abs_max'
        else:
284
            output_data = quant_data.astype(dtype)
285
        self.inputs = {
286 287 288 289
            'X': input_data,
            'InScale': in_scale,
            'InAccum': in_accum,
            'InState': in_state
290 291
        }
        self.outputs = {
292
            'Out': output_data,
293 294
            'OutAccum': out_accum,
            'OutState': out_state,
295
            'OutScale': out_scale
296
        }
297
        self.dtype = dtype
298
        self.check_output()
299 300 301 302 303
        if with_gradient:
            gradient = [
                np.ones(input_data.shape) / np.product(input_data.shape)
            ]
            self.check_grad(['X'], 'Out', user_defined_grads=gradient)
304

305 306 307
    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)
308

309 310 311
    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)
312

313
    def test_fake_quantize_moving_average_abs_max_round1(self):
314 315 316
        self._fake_quantize_moving_average_abs_max(np.float32, (8, 16, 7, 7),
                                                   np.random.random,
                                                   round_type='TiesToEven')
317

318
    def test_fake_quantize_dequantize_moving_average_abs_max(self):
319 320 321 322
        self._fake_quantize_moving_average_abs_max(np.float32, (8, 16, 7, 7),
                                                   np.random.random,
                                                   dequantize=True,
                                                   with_gradient=True)
323

324

325
class TestFakeQuantizeDequantizeAbsMaxOp(OpTest):
326

327
    def setUp(self):
328
        self.op_type = 'fake_quantize_dequantize_abs_max'
329
        self.attrs = {'bit_length': 8}
330

331 332 333 334
    def _fake_quantize_dequantize_abs_max(self,
                                          dtype,
                                          input_shape,
                                          distribution,
335
                                          round_type='TiesAwayFromZero'):
336 337 338
        input_data = distribution(input_shape).astype(dtype)
        scale = np.max(np.abs(input_data)).astype(dtype)
        bnt = (1 << (self.attrs['bit_length'] - 1)) - 1
339 340
        if round_type == 'TiesToEven':
            round_out = np.round(input_data / scale * bnt)
341
            output_data = np.clip(round_out, -bnt - 1, bnt) * scale / bnt
342 343
            self.attrs['round_type'] = 0
        else:
344
            output_data = round_c(input_data / scale * bnt) * scale / bnt
345
            self.attrs['round_type'] = 1
346
        self.inputs = {'X': input_data}
347
        self.outputs = {
348 349
            'Out': output_data,
            'OutScale': np.array(scale).astype(dtype)
350
        }
351
        self.dtype = dtype
352
        self.check_output()
353 354
        gradient = [np.ones(input_data.shape) / np.product(input_data.shape)]
        self.check_grad(['X'], 'Out', user_defined_grads=gradient)
355

356 357 358
    def test_fake_quantize_dequantize_abs_max(self):
        self._fake_quantize_dequantize_abs_max(np.float32, (124, 240),
                                               np.random.random)
359

360 361 362
    def test_fake_quantize_dequantize_abs_max_round1(self):
        self._fake_quantize_dequantize_abs_max(np.float32, (124, 240),
                                               np.random.random,
363
                                               round_type='TiesToEven')
364

365

366
class TestChannelWiseFakeQuantizeDequantizeAbsMaxOp(OpTest):
367

H
huangxu96 已提交
368
    def setUp(self):
369 370
        self.op_type = 'fake_channel_wise_quantize_dequantize_abs_max'
        self.attrs = {'bit_length': 8}
H
huangxu96 已提交
371

372 373 374
    def _fake_channel_wise_quantize_dequantize_abs_max(self,
                                                       dtype,
                                                       input_shape,
375
                                                       quant_axis,
376 377
                                                       distribution,
                                                       round_type='TiesToEven'):
378 379 380 381 382
        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)
383 384
        compute_axis = tuple(i for i in range(len(input_shape))
                             if i != quant_axis)
385
        scale_broadcast = np.amax(input_data, axis=compute_axis, keepdims=True)
386 387
        if round_type == 'TiesToEven':
            round_out = np.round(bnt * output_data / scale_broadcast)
388 389
            output_data = np.clip(round_out, -bnt - 1,
                                  bnt) * scale_broadcast / bnt
390 391
            self.attrs['round_type'] = 0
        else:
392 393
            output_data = round_c(
                bnt * output_data / scale_broadcast) * scale_broadcast / bnt
394
            self.attrs['round_type'] = 1
395 396 397 398 399 400 401 402
        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
H
huangxu96 已提交
403
        self.check_output()
404 405
        gradient = [np.ones(input_data.shape) / np.product(input_data.shape)]
        self.check_grad(['X'], 'Out', user_defined_grads=gradient)
H
huangxu96 已提交
406

407
    def test_channel_wise_fake_quant_dequant_abs_max(self):
408 409 410
        input_shape_quant_axis_options = [[(3, 4, 64, 64), 0],
                                          [(15, 20, 5, 5), 1], [(30, 15), 0],
                                          [(30, 15), 1]]
411 412 413 414 415 416 417
        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):
418
                self._fake_channel_wise_quantize_dequantize_abs_max(
419 420 421 422 423
                    np.float32,
                    input_shape,
                    quant_axis,
                    np.random.random,
                    round_type=round_type)
H
huangxu96 已提交
424 425


426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
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):
451

452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477
    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()


class TestChannelWiseQuantizeOp1(TestChannelWiseQuantizeOp):
478

479 480 481 482 483 484 485
    def set_args(self):
        self.bit_length = 8
        self.data_type = "float32"
        self.quant_axis = 1


class TestChannelWiseQuantizeOpTrain(OpTest):
486

487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514
    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()


class TestquantizeOp(OpTest):
515

516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
    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()


class TestquantizeOpTrain(TestquantizeOp):
542

543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
    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"
        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,
            'is_test': self.is_test
        }
        self.outputs = {'Y': yq, 'OutScale': scale}

    def test_check_output(self):
        self.check_output()


570
if __name__ == '__main__':
视言's avatar
视言 已提交
571
    unittest.main()