test_conv2d_int8_mkldnn_op.py 15.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
#   Copyright (c) 2018 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.

15
import os
16
import unittest
17

18
import numpy as np
19

20
from paddle.fluid import core
21
from paddle.fluid.tests.unittests.eager_op_test import OpTest
22 23
from paddle.fluid.tests.unittests.test_conv2d_op import (
    TestConv2DOp,
24
    conv2d_forward_naive,
25
)
26 27 28


def conv2d_forward_refer(input, filter, group, conv_param):
29
    out, _, _, _, _ = conv2d_forward_naive(input, filter, group, conv_param)
30
    return out
X
xiaolil1 已提交
31 32


33 34 35
@unittest.skipIf(
    not core.supports_int8(), "place does not support int8 computation"
)
C
cnn 已提交
36
class TestConv2DInt8Op(TestConv2DOp):
37 38 39 40 41 42
    def setUp(self):
        self.op_type = "conv2d"
        self.use_cudnn = False
        self.exhaustive_search = False
        self.use_cuda = False
        self.use_mkldnn = False
43
        self.data_format = "NCHW"
44
        self.mkldnn_data_type = "int8"
45 46 47 48 49
        self.weighttype = np.float32
        self.use_mkldnn = True
        self.init_group()
        self.init_dilation()
        self.init_test_case()
50
        self.init_fuse_activation()
X
xiaolil1 已提交
51
        self.init_fuse_residual()
X
xiaolil1 已提交
52
        self.init_data_type()
53 54 55 56

        conv2d_param = {
            'stride': self.stride,
            'pad': self.pad,
57
            'dilation': self.dilations,
58
        }
59 60
        # This implementation of convolution quantization is based on OneDNN documentation
        # https://oneapi-src.github.io/oneDNN/dev_guide_int8_computations.html#doxid-dev-guide-int8-computations-1dg-i8-comp-s11
61 62 63 64 65
        inner_scale = 1.0 if self.fuse_activation != "" else self.scale_out
        activation_scale = self.scale_out if self.fuse_activation != "" else 1.0
        scale_output_shift = inner_scale / (
            self.scale_in * self.scale_weights[0]
        )
66
        filter = np.random.random(self.filter_size).astype(self.weighttype)
67 68 69 70 71

        # When the Intel AVX2 or Intel AVX512 Instruction Set is used
        # the reorder additionally scales the weights by 0.5
        # to overcome the potential overflow issue. If the processor supports VNNI instructions,
        # modification of the weights is not necessary.
72 73 74 75 76 77
        avx_scale = (
            0.5 if not core.supports_vnni() and self.srctype == np.int8 else 1.0
        )
        filter_int = np.round(
            filter * self.scale_weights[0] * avx_scale
        ).astype(np.int32)
78 79 80
        scale_output_shift = scale_output_shift / avx_scale

        def conv2d_forward_refer_helper(input_):
81 82 83 84 85 86 87 88 89
            return (
                conv2d_forward_refer(
                    input_.astype(np.int32),
                    filter_int,
                    self.groups,
                    conv2d_param,
                ).astype(np.float32)
                * scale_output_shift
            )
90 91 92

        def residual_helper(init_low, init_high, output_):
            input_residual_ = np.random.randint(
93 94 95 96 97 98
                init_low, init_high, self.input_residual_size
            ).astype(self.srctype)
            return (
                output_
                + input_residual_ * (inner_scale / self.scale_in_eltwise)
            ), input_residual_
99 100 101

        if self.srctype == np.int8:
            init_low, init_high = (-5, 5)
102 103 104
            input = np.random.randint(
                init_low, init_high, self.input_size
            ).astype(self.srctype)
105 106 107
            input_shift = (np.ones(self.input_size) * 128).astype(np.uint8)

            output1 = conv2d_forward_refer_helper(
108 109
                np.round(input + input_shift).astype(np.int32)
            )
110
            output2 = conv2d_forward_refer_helper(
111 112
                np.round(input_shift).astype(np.int32)
            )
113
            output = output1 - output2
114
        else:
115
            init_low, init_high = (0, 10)
116 117 118
            input = np.random.randint(
                init_low, init_high, self.input_size
            ).astype(self.srctype)
119
            output = conv2d_forward_refer_helper(input)
120

121
        if self.fuse_residual:
122 123 124
            output, input_residual = residual_helper(
                init_low, init_high, output
            )
X
xiaolil1 已提交
125

126 127 128 129 130
        if self.fuse_activation == "":
            pass
        elif self.fuse_activation == "relu":
            output = activation_scale * np.maximum(output, 0)
        elif self.fuse_activation == "hard_swish":
131 132 133 134 135 136
            output = (
                activation_scale
                * output
                / 6.0
                * np.minimum(np.maximum(0, output + 3.0), 6)
            )
137 138 139
        elif self.fuse_activation == "relu6":
            output = activation_scale * np.maximum(0, np.minimum(6, output))
        elif self.fuse_activation == "swish":
140
            output = activation_scale * output / (1.0 + np.exp(-1.0 * output))
141 142 143
        elif self.fuse_activation == "leaky_relu":
            output = activation_scale * np.maximum(output, 0.02 * output)
        else:
144 145 146 147 148
            raise NotImplementedError(
                "test for "
                + self.fuse_activation
                + " activation not implemented"
            )
149

150
        output = np.round(output).astype(self.dsttype)
151 152

        self.inputs = {
153
            'Input': OpTest.np_dtype_to_fluid_dtype(input.astype(self.srctype)),
154
            'Filter': OpTest.np_dtype_to_fluid_dtype(filter),
155
        }
X
xiaolil1 已提交
156 157
        if self.fuse_residual:
            self.inputs['ResidualData'] = OpTest.np_dtype_to_fluid_dtype(
158 159
                input_residual
            )
X
xiaolil1 已提交
160

161 162 163
        if self.fuse_activation != "" or self.fuse_residual:
            self.op_type = "fused_conv2d"

164 165 166 167 168 169 170 171 172 173 174 175
        self.attrs = {
            'strides': self.stride,
            'paddings': self.pad,
            'groups': self.groups,
            'dilations': self.dilations,
            'use_cudnn': self.use_cudnn,
            'use_mkldnn': self.use_mkldnn,
            'data_format': self.data_format,
            'exhaustive_search': self.exhaustive_search,
            'Scale_in': self.scale_in,
            'Scale_out': self.scale_out,
            'Scale_weights': self.scale_weights,
X
xiaolil1 已提交
176
            'Scale_in_eltwise': self.scale_in_eltwise,
177
            'fuse_activation': self.fuse_activation,
178 179
            'fuse_alpha': self.fuse_alpha,
            'fuse_beta': self.fuse_beta,
180
            'fuse_residual_connection': self.fuse_residual,
181
            'mkldnn_data_type': self.mkldnn_data_type,
182 183 184 185
        }
        self.outputs = {'Output': output}

    def test_check_output(self):
186
        # TODO(wangzhongpu): support mkldnn op in dygraph mode
187 188 189
        self.check_output_with_place(
            core.CPUPlace(), atol=0, check_dygraph=False
        )
190 191 192 193 194 195 196 197 198 199 200

    def test_check_grad(self):
        pass

    def test_check_grad_no_filter(self):
        pass

    def test_check_grad_no_input(self):
        pass

    def init_test_case(self):
C
cnn 已提交
201
        TestConv2DOp.init_test_case(self)
X
xiaolil1 已提交
202
        self.input_size = [1, 1, 5, 5]  # NCHW
203
        f_c = self.input_size[1] // self.groups
X
xiaolil1 已提交
204 205
        self.input_residual_size = [1, 2, 3, 3]
        self.filter_size = [2, f_c, 3, 3]
206
        self.scale_in = 0.95
207 208
        self.scale_out = 0.5
        self.scale_weights = [10.0]
X
xiaolil1 已提交
209
        self.scale_in_eltwise = 0.6
210

X
xiaolil1 已提交
211
    def init_data_type(self):
212 213 214
        self.srctype = np.uint8
        self.dsttype = np.int8

215
    def init_fuse_activation(self):
216
        self.fuse_activation = "relu"
217 218
        self.fuse_alpha = 0
        self.fuse_beta = 0
219

X
xiaolil1 已提交
220 221 222
    def init_fuse_residual(self):
        self.fuse_residual = True

X
xiaolil1 已提交
223

224
# --------------------test conv2d u8 in and u8 out with residual fuse--------------------
225 226


C
cnn 已提交
227
class TestConv2D(TestConv2DInt8Op):
228 229 230 231
    def init_test_case(self):
        self.pad = [0, 0]
        self.stride = [1, 1]
        self.input_size = [2, 3, 5, 5]  # NCHW
X
xiaolil1 已提交
232
        self.input_residual_size = [2, 6, 3, 3]
233 234 235
        assert np.mod(self.input_size[1], self.groups) == 0
        f_c = self.input_size[1] // self.groups
        self.filter_size = [6, f_c, 3, 3]
236
        self.scale_in = 0.95
237 238
        self.scale_out = 0.5
        self.scale_weights = [10.0]
X
xiaolil1 已提交
239
        self.scale_in_eltwise = 0.6
240 241


242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
class TestWithHardSwish(TestConv2D):
    def init_fuse_activation(self):
        self.fuse_activation = "hard_swish"
        self.fuse_alpha = 0
        self.fuse_beta = 0


class TestWithRelu6(TestConv2D):
    def init_fuse_activation(self):
        self.fuse_activation = "relu6"
        self.fuse_alpha = 6
        self.fuse_beta = 0


class TestWithSwish(TestConv2D):
    def init_fuse_activation(self):
        self.fuse_activation = "swish"
        self.fuse_alpha = 1
        self.fuse_beta = 0


class TestWithLeakyRelu(TestConv2D):
    def init_fuse_activation(self):
        self.fuse_activation = "leaky_relu"
        self.fuse_alpha = 0.02
        self.fuse_beta = 0


C
cnn 已提交
270
class TestWithPad(TestConv2D):
271
    def init_test_case(self):
C
cnn 已提交
272
        TestConv2D.init_test_case(self)
273
        self.pad = [1, 1]
X
xiaolil1 已提交
274
        self.input_residual_size = [2, 6, 5, 5]
275 276


C
cnn 已提交
277
class TestWithGroup(TestConv2D):
278 279 280 281
    def init_group(self):
        self.groups = 3


C
cnn 已提交
282
class TestWithStride(TestConv2DInt8Op):
283 284 285 286
    def init_test_case(self):
        self.pad = [1, 1]
        self.stride = [2, 2]
        self.input_size = [2, 3, 6, 6]
X
xiaolil1 已提交
287
        self.input_residual_size = [2, 6, 3, 3]
288 289 290
        assert np.mod(self.input_size[1], self.groups) == 0
        f_c = self.input_size[1] // self.groups
        self.filter_size = [6, f_c, 3, 3]
291
        self.scale_in = 0.95
292 293
        self.scale_out = 0.8
        self.scale_weights = [10.0]
X
xiaolil1 已提交
294
        self.scale_in_eltwise = 0.5
295 296


C
cnn 已提交
297
class TestWithDilations(TestConv2DInt8Op):
298 299 300 301 302 303 304 305 306
    def init_test_case(self):
        self.pad = [1, 1]
        self.stride = [1, 1]
        self.dilations = [2, 2]
        self.input_size = [2, 3, 10, 10]
        self.input_residual_size = [2, 6, 8, 8]
        assert np.mod(self.input_size[1], self.groups) == 0
        f_c = self.input_size[1] // self.groups
        self.filter_size = [6, f_c, 3, 3]
307
        self.scale_in = 0.95
308 309 310 311 312
        self.scale_out = 0.8
        self.scale_weights = [10.0]
        self.scale_in_eltwise = 0.5


C
cnn 已提交
313
class TestWith1x1(TestConv2DInt8Op):
314 315 316 317
    def init_test_case(self):
        self.pad = [0, 0]
        self.stride = [1, 1]
        self.input_size = [1, 3, 5, 5]
X
xiaolil1 已提交
318
        self.input_residual_size = [1, 6, 5, 5]
319 320 321
        assert np.mod(self.input_size[1], self.groups) == 0
        f_c = self.input_size[1] // self.groups
        self.filter_size = [6, f_c, 1, 1]
322
        self.scale_in = 0.95
323 324
        self.scale_out = 0.5
        self.scale_weights = [12.0]
X
xiaolil1 已提交
325
        self.scale_in_eltwise = 0.5
326 327


C
cnn 已提交
328
class TestWithInput1x1Filter1x1(TestConv2DInt8Op):
329 330 331 332
    def init_test_case(self):
        self.pad = [0, 0]
        self.stride = [1, 1]
        self.input_size = [2, 3, 1, 1]
X
xiaolil1 已提交
333
        self.input_residual_size = [2, 6, 1, 1]
334 335 336
        assert np.mod(self.input_size[1], self.groups) == 0
        f_c = self.input_size[1] // self.groups
        self.filter_size = [6, f_c, 1, 1]
337
        self.scale_in = 0.95
338 339
        self.scale_out = 0.5
        self.scale_weights = [10.0]
X
xiaolil1 已提交
340
        self.scale_in_eltwise = 0.8
341 342 343 344 345

    def init_group(self):
        self.groups = 3


346
def init_data_type_with_fusion(self, input_dt, fuse_activation, fuse_residual):
347
    self.op_type = "fused_conv2d"
X
xiaolil1 已提交
348
    self.srctype = input_dt
349
    self.dsttype = np.uint8 if fuse_activation == "relu" else np.int8
X
xiaolil1 已提交
350

351
    self.fuse_activation = fuse_activation
352

353
    self.fuse_residual = fuse_residual
X
xiaolil1 已提交
354

355 356

def create_test_int8_class(parent):
X
xiaolil1 已提交
357

358
    # --------------------test conv2d s8 in and u8 out--------------------
X
xiaolil1 已提交
359 360
    class TestS8U8Case(parent):
        def init_data_type(self):
361
            init_data_type_with_fusion(self, np.int8, "relu", False)
X
xiaolil1 已提交
362

363
    # --------------------test conv2d s8 in and s8 out--------------------
X
xiaolil1 已提交
364 365
    class TestS8S8Case(parent):
        def init_data_type(self):
366
            init_data_type_with_fusion(self, np.int8, "", False)
X
xiaolil1 已提交
367

368
    # --------------------test conv2d u8 in and s8 out--------------------
X
xiaolil1 已提交
369 370
    class TestU8S8Case(parent):
        def init_data_type(self):
371
            init_data_type_with_fusion(self, np.uint8, "", False)
X
xiaolil1 已提交
372

373
    # --------------------test conv2d u8 in and u8 out without residual fuse--------------------
X
xiaolil1 已提交
374 375
    class TestU8U8Case(parent):
        def init_data_type(self):
376
            init_data_type_with_fusion(self, np.uint8, "relu", False)
X
xiaolil1 已提交
377

378
    # --------------------test conv2d s8 in and s8 out with residual fuse--------------------
X
xiaolil1 已提交
379 380
    class TestS8S8ResCase(parent):
        def init_data_type(self):
381
            init_data_type_with_fusion(self, np.int8, "", True)
X
xiaolil1 已提交
382

383
    # --------------------test conv2d u8 in and s8 out with residual fuse--------------------
X
xiaolil1 已提交
384 385
    class TestU8S8ResCase(parent):
        def init_data_type(self):
386
            init_data_type_with_fusion(self, np.uint8, "", True)
X
xiaolil1 已提交
387

388 389 390 391
    cls_name_s8u8 = "{}_relu_{}_residual_0".format(parent.__name__, "1")
    cls_name_s8s8 = "{}_relu_{}_residual_0".format(parent.__name__, "0")
    cls_name_u8s8 = "{}_relu_{}_residual_0".format(parent.__name__, "0")
    cls_name_u8u8 = "{}_relu_{}_residual_0".format(parent.__name__, "1")
392

393
    cls_name_s8s8_re_1 = "{}_relu_{}_residual_{}".format(
394 395
        parent.__name__, "0", "1"
    )
396
    cls_name_u8s8_re_1 = "{}_relu_{}_residual_{}".format(
397 398
        parent.__name__, "0", "1"
    )
X
xiaolil1 已提交
399 400 401
    TestS8U8Case.__name__ = cls_name_s8u8
    TestS8S8Case.__name__ = cls_name_s8s8
    TestU8S8Case.__name__ = cls_name_u8s8
X
xiaolil1 已提交
402 403 404
    TestU8U8Case.__name__ = cls_name_u8u8
    TestS8S8ResCase.__name__ = cls_name_s8s8_re_1
    TestU8S8ResCase.__name__ = cls_name_u8s8_re_1
405

X
xiaolil1 已提交
406 407 408
    globals()[cls_name_s8u8] = TestS8U8Case
    globals()[cls_name_s8s8] = TestS8S8Case
    globals()[cls_name_u8s8] = TestU8S8Case
X
xiaolil1 已提交
409 410 411
    globals()[cls_name_u8u8] = TestU8U8Case
    globals()[cls_name_s8s8_re_1] = TestS8S8ResCase
    globals()[cls_name_u8s8_re_1] = TestU8S8ResCase
412

413
    if os.name != 'nt':
414
        # --------------------test conv2d s8 in and u8 out with residual fuse--------------------
415 416 417 418
        class TestS8U8ResCase(parent):
            def init_data_type(self):
                init_data_type_with_fusion(self, np.int8, "relu", True)

419
        cls_name_s8u8_re_1 = "{}_relu_{}_residual_{}".format(
420 421
            parent.__name__, "1", "1"
        )
422 423 424
        TestS8U8ResCase.__name__ = cls_name_s8u8_re_1
        globals()[cls_name_s8u8_re_1] = TestS8U8ResCase

425

C
cnn 已提交
426
create_test_int8_class(TestConv2DInt8Op)
427 428
create_test_int8_class(TestWithPad)
create_test_int8_class(TestWithStride)
429
create_test_int8_class(TestWithDilations)
430 431 432 433
create_test_int8_class(TestWithGroup)
create_test_int8_class(TestWith1x1)
create_test_int8_class(TestWithInput1x1Filter1x1)

434

C
cnn 已提交
435
class TestConv2DOp_AsyPadding_INT_MKLDNN(TestConv2DInt8Op):
436 437 438 439 440 441 442 443
    def init_kernel_type(self):
        self.use_mkldnn = True

    def init_paddings(self):
        self.pad = [0, 0, 1, 2]
        self.padding_algorithm = "EXPLICIT"


C
cnn 已提交
444
class TestConv2DOp_Same_INT_MKLDNN(TestConv2DOp_AsyPadding_INT_MKLDNN):
445 446 447 448 449
    def init_paddings(self):
        self.pad = [0, 0]
        self.padding_algorithm = "SAME"


C
cnn 已提交
450
class TestConv2DOp_Valid_INT_MKLDNN(TestConv2DOp_AsyPadding_INT_MKLDNN):
451 452 453 454 455
    def init_paddings(self):
        self.pad = [1, 1]
        self.padding_algorithm = "VALID"


456
if __name__ == '__main__':
457
    from paddle import enable_static
458

459
    enable_static()
460
    unittest.main()