test_quantization_pass.py 36.7 KB
Newer Older
W
WangZhen 已提交
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.

Z
Zhen Wang 已提交
15
import os
W
WangZhen 已提交
16 17 18 19 20
import unittest
import random
import numpy as np
import paddle.fluid as fluid
import six
W
WangZhen 已提交
21
import paddle
22
from paddle.fluid.framework import IrGraph
23
from paddle.fluid.contrib.slim.quantization import QuantizationTransformPass
24
from paddle.fluid.contrib.slim.quantization import QuantizationTransformPassV2
W
WangZhen 已提交
25
from paddle.fluid.contrib.slim.quantization import QuantizationFreezePass
26 27
from paddle.fluid.contrib.slim.quantization import ConvertToInt8Pass
from paddle.fluid.contrib.slim.quantization import TransformForMobilePass
28
from paddle.fluid.contrib.slim.quantization import AddQuantDequantPass
W
WangZhen 已提交
29 30
from paddle.fluid import core

P
pangyoki 已提交
31 32
paddle.enable_static()

Z
Zhen Wang 已提交
33 34 35
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
os.environ["CPU_NUM"] = "1"

W
WangZhen 已提交
36 37 38 39 40 41 42 43 44 45 46 47

def linear_fc(num):
    data = fluid.layers.data(name='image', shape=[1, 32, 32], dtype='float32')
    label = fluid.layers.data(name='label', shape=[1], dtype='int64')
    hidden = data
    for _ in six.moves.xrange(num):
        hidden = fluid.layers.fc(hidden, size=128, act='relu')
    loss = fluid.layers.cross_entropy(input=hidden, label=label)
    loss = fluid.layers.mean(loss)
    return loss


48
def residual_block(num, quant_skip_pattern=None):
49

W
WangZhen 已提交
50 51 52 53 54 55 56
    def conv_bn_layer(input,
                      ch_out,
                      filter_size,
                      stride,
                      padding,
                      act='relu',
                      bias_attr=False):
57 58 59 60 61 62 63
        tmp = fluid.layers.conv2d(input=input,
                                  filter_size=filter_size,
                                  num_filters=ch_out,
                                  stride=stride,
                                  padding=padding,
                                  act=None,
                                  bias_attr=bias_attr)
W
WangZhen 已提交
64 65
        return fluid.layers.batch_norm(input=tmp, act=act)

66 67 68 69 70 71 72 73
    data = fluid.layers.data(name='image',
                             shape=[1, 1, 32, 32],
                             dtype='float32',
                             append_batch_size=False)
    label = fluid.layers.data(name='label',
                              shape=[1, 1],
                              dtype='int64',
                              append_batch_size=False)
W
WangZhen 已提交
74 75 76 77 78
    hidden = data
    for _ in six.moves.xrange(num):
        conv = conv_bn_layer(hidden, 16, 3, 1, 1, act=None, bias_attr=True)
        short = conv_bn_layer(hidden, 16, 1, 1, 0, act=None)
        hidden = fluid.layers.elementwise_add(x=conv, y=short, act='relu')
79 80
    matmul_weight = fluid.layers.create_parameter(shape=[1, 16, 32, 32],
                                                  dtype='float32')
81
    hidden = fluid.layers.matmul(hidden, matmul_weight, True, True)
82 83
    if quant_skip_pattern:
        with fluid.name_scope(quant_skip_pattern):
84 85 86 87
            pool = fluid.layers.pool2d(input=hidden,
                                       pool_size=2,
                                       pool_type='avg',
                                       pool_stride=2)
88
    else:
89 90 91 92
        pool = fluid.layers.pool2d(input=hidden,
                                   pool_size=2,
                                   pool_type='avg',
                                   pool_stride=2)
93
    fc = fluid.layers.fc(input=pool, size=10)
W
WangZhen 已提交
94 95 96 97 98
    loss = fluid.layers.cross_entropy(input=fc, label=label)
    loss = fluid.layers.mean(loss)
    return loss


99
def conv_net(img, label, quant_skip_pattern):
100 101 102 103 104 105 106
    conv_pool_1 = fluid.nets.simple_img_conv_pool(input=img,
                                                  filter_size=5,
                                                  num_filters=20,
                                                  pool_size=2,
                                                  pool_stride=2,
                                                  pool_type='max',
                                                  act="relu")
W
WangZhen 已提交
107
    conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
108 109 110 111 112 113 114
    conv_pool_2 = fluid.nets.simple_img_conv_pool(input=conv_pool_1,
                                                  filter_size=5,
                                                  num_filters=50,
                                                  pool_size=2,
                                                  pool_stride=2,
                                                  pool_type='avg',
                                                  act="relu")
115 116 117
    hidden = fluid.layers.fc(input=conv_pool_2, size=100, act='relu')
    with fluid.name_scope(quant_skip_pattern):
        prediction = fluid.layers.fc(input=hidden, size=10, act='softmax')
W
WangZhen 已提交
118 119 120 121 122
    loss = fluid.layers.cross_entropy(input=prediction, label=label)
    avg_loss = fluid.layers.mean(loss)
    return avg_loss


123
class TestQuantizationTransformPass(unittest.TestCase):
124

W
WangZhen 已提交
125 126 127 128 129 130
    def setUp(self):
        self.quantizable_op_and_inputs = {
            'conv2d': ['Input', 'Filter'],
            'depthwise_conv2d': ['Input', 'Filter'],
            'mul': ['X', 'Y']
        }
131
        self.quantizable_grad_op_inputs = {
W
WangZhen 已提交
132 133 134 135 136
            'conv2d_grad': ['Input', 'Filter'],
            'depthwise_conv2d_grad': ['Input', 'Filter'],
            'mul_grad': ['X', 'Y']
        }

137
    def check_program(self, program):
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
        quantized_ops = set()
        for block in program.blocks:
            for op in block.ops:
                # check forward
                if op.type in self.quantizable_op_and_inputs:
                    for arg_name in op.input_arg_names:
                        self.assertTrue(
                            arg_name.endswith('.quantized.dequantized'))
                        quantized_ops.add(arg_name)

            for op in block.ops:
                # check backward
                if op.type in self.quantizable_grad_op_inputs:
                    for pname in self.quantizable_grad_op_inputs[op.type]:
                        arg_name = op.input(pname)[0]
                        self.assertTrue(
                            arg_name.endswith('.quantized.dequantized'))
                        self.assertTrue(arg_name in quantized_ops)

157 158 159 160
    def linear_fc_quant(self,
                        activation_quant_type,
                        weight_quantize_type,
                        for_ci=True):
W
WangZhen 已提交
161 162 163 164 165 166
        main = fluid.Program()
        startup = fluid.Program()
        with fluid.program_guard(main, startup):
            loss = linear_fc(3)
            opt = fluid.optimizer.Adam(learning_rate=0.001)
            opt.minimize(loss)
167
        place = fluid.CPUPlace()
168
        graph = IrGraph(core.Graph(main.desc), for_test=False)
169 170
        transform_pass = QuantizationTransformPass(
            scope=fluid.global_scope(),
171
            place=place,
172 173
            activation_quantize_type=activation_quant_type,
            weight_quantize_type=weight_quantize_type)
174
        transform_pass.apply(graph)
Z
Zhen Wang 已提交
175
        if not for_ci:
Z
Zhen Wang 已提交
176 177 178 179
            marked_nodes = set()
            for op in graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
180 181
            graph.draw('.', 'quantize_fc_' + activation_quant_type,
                       marked_nodes)
182
        program = graph.to_program()
183
        self.check_program(program)
184
        val_graph = IrGraph(core.Graph(program.desc), for_test=False)
Z
Zhen Wang 已提交
185
        if not for_ci:
Z
Zhen Wang 已提交
186 187 188 189
            val_marked_nodes = set()
            for op in val_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    val_marked_nodes.add(op)
190 191
            val_graph.draw('.', 'val_fc_' + activation_quant_type,
                           val_marked_nodes)
W
WangZhen 已提交
192

193
    def test_linear_fc_quant_abs_max(self):
194
        self.linear_fc_quant('abs_max', 'abs_max', for_ci=True)
W
WangZhen 已提交
195

196
    def test_linear_fc_quant_range_abs_max(self):
197
        self.linear_fc_quant('range_abs_max', 'abs_max', for_ci=True)
W
WangZhen 已提交
198

199
    def test_linear_fc_quant_moving_average_abs_max(self):
200 201 202
        self.linear_fc_quant('moving_average_abs_max',
                             'channel_wise_abs_max',
                             for_ci=True)
203

204 205 206
    def residual_block_quant(self,
                             activation_quant_type,
                             weight_quantize_type,
207
                             quantizable_op_type,
208
                             for_ci=True):
W
WangZhen 已提交
209 210 211 212 213 214
        main = fluid.Program()
        startup = fluid.Program()
        with fluid.program_guard(main, startup):
            loss = residual_block(2)
            opt = fluid.optimizer.Adam(learning_rate=0.001)
            opt.minimize(loss)
215
        place = fluid.CPUPlace()
216
        graph = IrGraph(core.Graph(main.desc), for_test=False)
217 218
        transform_pass = QuantizationTransformPass(
            scope=fluid.global_scope(),
219
            place=place,
220
            activation_quantize_type=activation_quant_type,
221 222
            weight_quantize_type=weight_quantize_type,
            quantizable_op_type=quantizable_op_type)
223
        transform_pass.apply(graph)
Z
Zhen Wang 已提交
224
        if not for_ci:
Z
Zhen Wang 已提交
225 226 227 228
            marked_nodes = set()
            for op in graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
229 230
            graph.draw('.', 'quantize_residual_' + activation_quant_type,
                       marked_nodes)
231
        program = graph.to_program()
232
        self.check_program(program)
233
        val_graph = IrGraph(core.Graph(program.desc), for_test=False)
Z
Zhen Wang 已提交
234
        if not for_ci:
Z
Zhen Wang 已提交
235 236 237 238
            val_marked_nodes = set()
            for op in val_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    val_marked_nodes.add(op)
239 240
            val_graph.draw('.', 'val_residual_' + activation_quant_type,
                           val_marked_nodes)
W
WangZhen 已提交
241

242
    def test_residual_block_abs_max(self):
243
        quantizable_op_type = ['conv2d', 'depthwise_conv2d', 'mul', 'matmul']
244 245 246 247
        self.residual_block_quant('abs_max',
                                  'abs_max',
                                  quantizable_op_type,
                                  for_ci=True)
W
WangZhen 已提交
248

249
    def test_residual_block_range_abs_max(self):
250
        quantizable_op_type = ['conv2d', 'depthwise_conv2d', 'mul', 'matmul']
251 252 253 254
        self.residual_block_quant('range_abs_max',
                                  'abs_max',
                                  quantizable_op_type,
                                  for_ci=True)
W
WangZhen 已提交
255

256
    def test_residual_block_moving_average_abs_max(self):
257
        quantizable_op_type = ['conv2d', 'depthwise_conv2d', 'mul', 'matmul']
258 259 260 261
        self.residual_block_quant('moving_average_abs_max',
                                  'channel_wise_abs_max',
                                  quantizable_op_type,
                                  for_ci=True)
262

W
WangZhen 已提交
263

W
WangZhen 已提交
264
class TestQuantizationFreezePass(unittest.TestCase):
265

266 267 268 269
    def freeze_graph(self,
                     use_cuda,
                     seed,
                     activation_quant_type,
X
XGZhang 已提交
270
                     bias_correction=False,
271
                     weight_quant_type='abs_max',
272 273
                     for_ci=True,
                     quant_skip_pattern='skip_quant'):
274

W
WangZhen 已提交
275 276 277 278 279
        def build_program(main, startup, is_test):
            main.random_seed = seed
            startup.random_seed = seed
            with fluid.unique_name.guard():
                with fluid.program_guard(main, startup):
280 281 282 283 284 285
                    img = fluid.layers.data(name='image',
                                            shape=[1, 28, 28],
                                            dtype='float32')
                    label = fluid.layers.data(name='label',
                                              shape=[1],
                                              dtype='int64')
286
                    loss = conv_net(img, label, quant_skip_pattern)
W
WangZhen 已提交
287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
                    if not is_test:
                        opt = fluid.optimizer.Adam(learning_rate=0.001)
                        opt.minimize(loss)
            return [img, label], loss

        random.seed(0)
        np.random.seed(0)

        main = fluid.Program()
        startup = fluid.Program()
        test_program = fluid.Program()
        feeds, loss = build_program(main, startup, False)
        build_program(test_program, startup, True)
        test_program = test_program.clone(for_test=True)
        main_graph = IrGraph(core.Graph(main.desc), for_test=False)
W
WangZhen 已提交
302
        test_graph = IrGraph(core.Graph(test_program.desc), for_test=True)
W
WangZhen 已提交
303 304 305

        place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
        exe = fluid.Executor(place)
W
WangZhen 已提交
306 307 308
        scope = fluid.Scope()
        with fluid.scope_guard(scope):
            exe.run(startup)
W
WangZhen 已提交
309
        transform_pass = QuantizationTransformPass(
310 311
            scope=scope,
            place=place,
312
            activation_quantize_type=activation_quant_type,
313 314
            weight_quantize_type=weight_quant_type,
            skip_pattern=quant_skip_pattern)
W
WangZhen 已提交
315 316
        transform_pass.apply(main_graph)
        transform_pass.apply(test_graph)
317
        dev_name = '_gpu_' if use_cuda else '_cpu_'
Z
Zhen Wang 已提交
318
        if not for_ci:
Z
Zhen Wang 已提交
319 320 321 322
            marked_nodes = set()
            for op in main_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
323 324 325
            main_graph.draw(
                '.', 'main' + dev_name + activation_quant_type + '_' +
                weight_quant_type, marked_nodes)
Z
Zhen Wang 已提交
326 327 328 329
            marked_nodes = set()
            for op in test_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
330 331 332
            test_graph.draw(
                '.', 'test' + dev_name + activation_quant_type + '_' +
                weight_quant_type, marked_nodes)
W
WangZhen 已提交
333

Z
Zhen Wang 已提交
334 335 336
        build_strategy = fluid.BuildStrategy()
        build_strategy.memory_optimize = False
        build_strategy.enable_inplace = False
337
        build_strategy.fuse_all_reduce_ops = False
Z
Zhen Wang 已提交
338 339
        binary = fluid.CompiledProgram(main_graph.graph).with_data_parallel(
            loss_name=loss.name, build_strategy=build_strategy)
340
        quantized_test_program = test_graph.to_program()
341
        iters = 5
342
        batch_size = 8
W
WangZhen 已提交
343

344 345 346 347 348
        train_reader = paddle.batch(paddle.reader.shuffle(
            paddle.dataset.mnist.train(), buf_size=500),
                                    batch_size=batch_size)
        test_reader = paddle.batch(paddle.dataset.mnist.test(),
                                   batch_size=batch_size)
W
WangZhen 已提交
349
        feeder = fluid.DataFeeder(feed_list=feeds, place=place)
W
WangZhen 已提交
350
        with fluid.scope_guard(scope):
W
WangZhen 已提交
351 352
            for _ in range(iters):
                data = next(train_reader())
Z
Zhen Wang 已提交
353
                loss_v = exe.run(binary,
354 355
                                 feed=feeder.feed(data),
                                 fetch_list=[loss])
Z
Zhen Wang 已提交
356
                if not for_ci:
357 358 359
                    print('{}: {}'.format(
                        'loss' + dev_name + activation_quant_type + '_' +
                        weight_quant_type, loss_v))
W
WangZhen 已提交
360

361 362 363 364 365 366 367 368 369 370 371
        test_data = next(test_reader())
        with fluid.program_guard(quantized_test_program):
            w_var = fluid.framework._get_var('conv2d_1.w_0.quantized',
                                             quantized_test_program)
        # Testing
        with fluid.scope_guard(scope):
            test_loss1, w_quant = exe.run(program=quantized_test_program,
                                          feed=feeder.feed(test_data),
                                          fetch_list=[loss, w_var])

        # Freeze graph for inference, but the weight of fc/conv is still float type.
372
        freeze_pass = QuantizationFreezePass(
X
XGZhang 已提交
373 374
            scope=scope, place=place, bias_correction=bias_correction, \
            weight_quantize_type=weight_quant_type)
375
        freeze_pass.apply(test_graph)
Z
Zhen Wang 已提交
376
        if not for_ci:
Z
Zhen Wang 已提交
377 378 379 380
            marked_nodes = set()
            for op in test_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
381 382 383
            test_graph.draw(
                '.', 'test_freeze' + dev_name + activation_quant_type + '_' +
                weight_quant_type, marked_nodes)
W
WangZhen 已提交
384

385 386 387 388 389 390
        server_program = test_graph.to_program()
        with fluid.scope_guard(scope):
            test_loss2, = exe.run(program=server_program,
                                  feed=feeder.feed(test_data),
                                  fetch_list=[loss])
        self.assertAlmostEqual(test_loss1, test_loss2, delta=5e-3)
Z
Zhen Wang 已提交
391
        if not for_ci:
392 393 394 395 396 397
            print('{}: {}'.format(
                'test_loss1' + dev_name + activation_quant_type + '_' +
                weight_quant_type, test_loss1))
            print('{}: {}'.format(
                'test_loss2' + dev_name + activation_quant_type + '_' +
                weight_quant_type, test_loss2))
398 399
        w_freeze = np.array(scope.find_var('conv2d_1.w_0').get_tensor())
        # Maybe failed, this is due to the calculation precision
400
        # self.assertAlmostEqual(np.sum(w_freeze), np.sum(w_quant))
Z
Zhen Wang 已提交
401
        if not for_ci:
402 403 404 405 406 407
            print('{}: {}'.format(
                'w_freeze' + dev_name + activation_quant_type + '_' +
                weight_quant_type, np.sum(w_freeze)))
            print('{}: {}'.format(
                'w_quant' + dev_name + activation_quant_type + '_' +
                weight_quant_type, np.sum(w_quant)))
408 409 410 411

        # Convert parameter to 8-bit.
        convert_int8_pass = ConvertToInt8Pass(scope=scope, place=place)
        convert_int8_pass.apply(test_graph)
Z
Zhen Wang 已提交
412
        if not for_ci:
Z
Zhen Wang 已提交
413 414 415 416
            marked_nodes = set()
            for op in test_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
417 418 419
            test_graph.draw(
                '.', 'test_int8' + dev_name + activation_quant_type + '_' +
                weight_quant_type, marked_nodes)
420 421 422
        server_program_int8 = test_graph.to_program()
        # Save the 8-bit parameter and model file.
        with fluid.scope_guard(scope):
423 424 425 426
            fluid.io.save_inference_model(
                'server_int8' + dev_name + activation_quant_type + '_' +
                weight_quant_type, ['image', 'label'], [loss], exe,
                server_program_int8)
427 428
            # Test whether the 8-bit parameter and model file can be loaded successfully.
            [infer, feed, fetch] = fluid.io.load_inference_model(
429 430
                'server_int8' + dev_name + activation_quant_type + '_' +
                weight_quant_type, exe)
431 432 433 434
        # Check the loaded 8-bit weight.
        w_8bit = np.array(scope.find_var('conv2d_1.w_0.int8').get_tensor())
        self.assertEqual(w_8bit.dtype, np.int8)
        self.assertEqual(np.sum(w_8bit), np.sum(w_freeze))
Z
Zhen Wang 已提交
435
        if not for_ci:
436 437 438 439 440 441
            print('{}: {}'.format(
                'w_8bit' + dev_name + activation_quant_type + '_' +
                weight_quant_type, np.sum(w_8bit)))
            print('{}: {}'.format(
                'w_freeze' + dev_name + activation_quant_type + '_' +
                weight_quant_type, np.sum(w_freeze)))
442 443 444

        mobile_pass = TransformForMobilePass()
        mobile_pass.apply(test_graph)
Z
Zhen Wang 已提交
445
        if not for_ci:
Z
Zhen Wang 已提交
446 447 448 449
            marked_nodes = set()
            for op in test_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
450 451 452
            test_graph.draw(
                '.', 'test_mobile' + dev_name + activation_quant_type + '_' +
                weight_quant_type, marked_nodes)
453 454 455

        mobile_program = test_graph.to_program()
        with fluid.scope_guard(scope):
456 457 458 459
            fluid.io.save_inference_model(
                'mobile_int8' + dev_name + activation_quant_type + '_' +
                weight_quant_type, ['image', 'label'], [loss], exe,
                mobile_program)
W
WangZhen 已提交
460

461
    def test_freeze_graph_cuda_dynamic(self):
W
WangZhen 已提交
462 463
        if fluid.core.is_compiled_with_cuda():
            with fluid.unique_name.guard():
464 465 466 467 468
                self.freeze_graph(True,
                                  seed=1,
                                  activation_quant_type='abs_max',
                                  weight_quant_type='abs_max',
                                  for_ci=True)
469
            with fluid.unique_name.guard():
470 471 472 473 474
                self.freeze_graph(True,
                                  seed=1,
                                  activation_quant_type='abs_max',
                                  weight_quant_type='channel_wise_abs_max',
                                  for_ci=True)
W
WangZhen 已提交
475

476
    def test_freeze_graph_cpu_dynamic(self):
W
WangZhen 已提交
477
        with fluid.unique_name.guard():
478 479 480 481 482 483 484 485 486 487
            self.freeze_graph(False,
                              seed=2,
                              activation_quant_type='abs_max',
                              weight_quant_type='abs_max',
                              for_ci=True)
            self.freeze_graph(False,
                              seed=2,
                              activation_quant_type='abs_max',
                              weight_quant_type='channel_wise_abs_max',
                              for_ci=True)
W
WangZhen 已提交
488

489
    def test_freeze_graph_cuda_static(self):
W
WangZhen 已提交
490 491
        if fluid.core.is_compiled_with_cuda():
            with fluid.unique_name.guard():
492 493 494 495 496 497 498 499 500 501 502
                self.freeze_graph(True,
                                  seed=1,
                                  activation_quant_type='range_abs_max',
                                  bias_correction=True,
                                  weight_quant_type='abs_max',
                                  for_ci=True)
                self.freeze_graph(True,
                                  seed=1,
                                  activation_quant_type='range_abs_max',
                                  weight_quant_type='abs_max',
                                  for_ci=True)
503 504 505 506 507 508
                self.freeze_graph(
                    True,
                    seed=1,
                    activation_quant_type='moving_average_abs_max',
                    weight_quant_type='abs_max',
                    for_ci=True)
509 510 511 512 513
                self.freeze_graph(True,
                                  seed=1,
                                  activation_quant_type='range_abs_max',
                                  weight_quant_type='channel_wise_abs_max',
                                  for_ci=True)
514 515 516 517 518 519
                self.freeze_graph(
                    True,
                    seed=1,
                    activation_quant_type='moving_average_abs_max',
                    weight_quant_type='channel_wise_abs_max',
                    for_ci=True)
X
XGZhang 已提交
520 521 522 523 524 525 526
                self.freeze_graph(
                    True,
                    seed=1,
                    activation_quant_type='moving_average_abs_max',
                    bias_correction=True,
                    weight_quant_type='channel_wise_abs_max',
                    for_ci=True)
W
WangZhen 已提交
527

528
    def test_freeze_graph_cpu_static(self):
W
WangZhen 已提交
529
        with fluid.unique_name.guard():
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
            self.freeze_graph(False,
                              seed=2,
                              activation_quant_type='range_abs_max',
                              weight_quant_type='abs_max',
                              for_ci=True)
            self.freeze_graph(False,
                              seed=2,
                              activation_quant_type='moving_average_abs_max',
                              weight_quant_type='abs_max',
                              for_ci=True)
            self.freeze_graph(False,
                              seed=2,
                              activation_quant_type='range_abs_max',
                              weight_quant_type='channel_wise_abs_max',
                              for_ci=True)
            self.freeze_graph(False,
                              seed=2,
                              activation_quant_type='moving_average_abs_max',
                              weight_quant_type='channel_wise_abs_max',
                              for_ci=True)
W
WangZhen 已提交
550 551


552
def quant_dequant_residual_block(num, quant_skip_pattern=None):
553

554 555 556 557 558 559 560
    def conv_bn_layer(input,
                      ch_out,
                      filter_size,
                      stride,
                      padding,
                      act='relu',
                      bias_attr=False):
561 562 563 564 565 566 567
        tmp = fluid.layers.conv2d(input=input,
                                  filter_size=filter_size,
                                  num_filters=ch_out,
                                  stride=stride,
                                  padding=padding,
                                  act=None,
                                  bias_attr=bias_attr)
568 569
        return fluid.layers.batch_norm(input=tmp, act=act)

570
    data1 = fluid.layers.data(name='image', shape=[1, 32, 32], dtype='float32')
571 572 573
    data2 = fluid.layers.data(name='matmul_input',
                              shape=[16, 32, 32],
                              dtype='float32')
574
    label = fluid.layers.data(name='label', shape=[1], dtype='int64')
575
    hidden = data1
576 577 578 579
    for _ in six.moves.xrange(num):
        conv = conv_bn_layer(hidden, 16, 3, 1, 1, act=None, bias_attr=True)
        short = conv_bn_layer(hidden, 16, 1, 1, 0, act=None)
        hidden = fluid.layers.elementwise_add(x=conv, y=short, act='relu')
580
    hidden = fluid.layers.matmul(hidden, data2, True, True)
581
    if isinstance(quant_skip_pattern, str):
582
        with fluid.name_scope(quant_skip_pattern):
583 584 585 586 587 588 589 590 591 592 593
            pool1 = fluid.layers.pool2d(input=hidden,
                                        pool_size=2,
                                        pool_type='avg',
                                        pool_stride=2)
            pool2 = fluid.layers.pool2d(input=hidden,
                                        pool_size=2,
                                        pool_type='max',
                                        pool_stride=2)
            pool_add = fluid.layers.elementwise_add(x=pool1,
                                                    y=pool2,
                                                    act='relu')
594 595 596 597 598
    elif isinstance(quant_skip_pattern, list):
        assert len(
            quant_skip_pattern
        ) > 1, 'test config error: the len of quant_skip_pattern list should be greater than 1.'
        with fluid.name_scope(quant_skip_pattern[0]):
599 600 601 602 603 604 605 606
            pool1 = fluid.layers.pool2d(input=hidden,
                                        pool_size=2,
                                        pool_type='avg',
                                        pool_stride=2)
            pool2 = fluid.layers.pool2d(input=hidden,
                                        pool_size=2,
                                        pool_type='max',
                                        pool_stride=2)
607
        with fluid.name_scope(quant_skip_pattern[1]):
608 609 610
            pool_add = fluid.layers.elementwise_add(x=pool1,
                                                    y=pool2,
                                                    act='relu')
611
    else:
612 613 614 615 616 617 618 619
        pool1 = fluid.layers.pool2d(input=hidden,
                                    pool_size=2,
                                    pool_type='avg',
                                    pool_stride=2)
        pool2 = fluid.layers.pool2d(input=hidden,
                                    pool_size=2,
                                    pool_type='max',
                                    pool_stride=2)
620 621 622 623 624 625 626
        pool_add = fluid.layers.elementwise_add(x=pool1, y=pool2, act='relu')
    fc = fluid.layers.fc(input=pool_add, size=10)
    loss = fluid.layers.cross_entropy(input=fc, label=label)
    loss = fluid.layers.mean(loss)
    return loss


627
class TestAddQuantDequantPass(unittest.TestCase):
628

629 630 631 632
    def setUp(self):
        self._target_ops = {'elementwise_add', 'pool2d'}
        self._target_grad_ops = {'elementwise_add_grad', 'pool2d_grad'}

633
    def check_graph(self, graph, skip_pattern=None):
634 635 636
        ops = graph.all_op_nodes()
        for op_node in ops:
            if op_node.name() in self._target_ops:
637 638 639 640 641 642 643 644 645
                user_skipped = False
                if isinstance(skip_pattern, list):
                    user_skipped = op_node.op().has_attr("op_namescope") and \
                                   any(pattern in op_node.op().attr("op_namescope") for pattern in skip_pattern)
                elif isinstance(skip_pattern, str):
                    user_skipped = op_node.op().has_attr("op_namescope") and \
                                   op_node.op().attr("op_namescope").find(skip_pattern) != -1

                if user_skipped:
646 647
                    continue

648 649 650 651
                in_nodes_all_not_persistable = True
                for input_name in op_node.input_arg_names():
                    in_node = graph._find_node_by_name(op_node.inputs,
                                                       input_name)
652 653 654
                    in_nodes_all_not_persistable = (in_nodes_all_not_persistable
                                                    and
                                                    not in_node.persistable())
655 656 657 658 659 660
                if not in_nodes_all_not_persistable:
                    continue
                input_names = op_node.input_arg_names()
                for input_name in input_names:
                    self.assertTrue(input_name.endswith('.quant_dequant'))

661 662 663 664
    def residual_block_quant(self,
                             quantizable_op_type,
                             skip_pattern=None,
                             for_ci=True):
665 666 667
        main = fluid.Program()
        startup = fluid.Program()
        with fluid.program_guard(main, startup):
668
            loss = quant_dequant_residual_block(2, skip_pattern)
669 670 671 672 673
            opt = fluid.optimizer.Adam(learning_rate=0.001)
            opt.minimize(loss)
        place = fluid.CPUPlace()
        graph = IrGraph(core.Graph(main.desc), for_test=False)
        add_quant_dequant_pass = AddQuantDequantPass(
674 675 676 677
            scope=fluid.global_scope(),
            place=place,
            skip_pattern=skip_pattern,
            quantizable_op_type=quantizable_op_type)
678 679 680 681 682 683 684
        add_quant_dequant_pass.apply(graph)
        if not for_ci:
            marked_nodes = set()
            for op in graph.all_op_nodes():
                if op.name().find('quant') > -1:
                    marked_nodes.add(op)
            graph.draw('.', 'add_quant_dequant_graph', marked_nodes)
685
        self.check_graph(graph, skip_pattern)
686 687 688 689 690 691 692 693 694 695
        program = graph.to_program()
        val_graph = IrGraph(core.Graph(program.desc), for_test=False)
        if not for_ci:
            val_marked_nodes = set()
            for op in val_graph.all_op_nodes():
                if op.name().find('quant') > -1:
                    val_marked_nodes.add(op)
            val_graph.draw('.', 'val_add_quant_dequant_graph', val_marked_nodes)

    def test_residual_block(self):
696
        quantizable_op_type = ['elementwise_add', 'pool2d', 'mul', 'matmul']
697 698 699
        self.residual_block_quant(quantizable_op_type,
                                  skip_pattern=None,
                                  for_ci=True)
700 701

    def test_residual_block_skip_pattern(self):
702
        quantizable_op_type = ['elementwise_add', 'pool2d', 'mul', 'matmul']
703 704 705
        self.residual_block_quant(quantizable_op_type,
                                  skip_pattern='skip_quant',
                                  for_ci=True)
706

Z
zhangchunle 已提交
707
    def test_residual_block_skip_pattern_1(self):
708
        quantizable_op_type = ['elementwise_add', 'pool2d', 'mul', 'matmul']
709 710 711
        self.residual_block_quant(quantizable_op_type,
                                  skip_pattern=['skip_quant1', 'skip_quant2'],
                                  for_ci=True)
712

713

714
class TestQuantizationTransformPassV2(unittest.TestCase):
715

716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829
    def setUp(self):
        self.quantizable_op_and_inputs = {
            'conv2d': ['Input', 'Filter'],
            'depthwise_conv2d': ['Input', 'Filter'],
            'mul': ['X', 'Y']
        }
        self.quantizable_grad_op_inputs = {
            'conv2d_grad': ['Input', 'Filter'],
            'depthwise_conv2d_grad': ['Input', 'Filter'],
            'mul_grad': ['X', 'Y']
        }

    def check_program(self, program):
        quantized_ops = set()
        for block in program.blocks:
            for op in block.ops:
                # check forward
                if op.type in self.quantizable_op_and_inputs:
                    for arg_name in op.input_arg_names:
                        self.assertTrue(
                            arg_name.endswith('.quantized.dequantized'))
                        quantized_ops.add(arg_name)

            for op in block.ops:
                # check backward
                if op.type in self.quantizable_grad_op_inputs:
                    for pname in self.quantizable_grad_op_inputs[op.type]:
                        arg_name = op.input(pname)[0]
                        self.assertTrue(
                            arg_name.endswith('.quantized.dequantized'))
                        self.assertTrue(arg_name in quantized_ops)

    def linear_fc_quant(self,
                        activation_quant_type,
                        weight_quantize_type,
                        for_ci=True):
        main = fluid.Program()
        startup = fluid.Program()
        with fluid.program_guard(main, startup):
            loss = linear_fc(3)
            opt = fluid.optimizer.Adam(learning_rate=0.001)
            opt.minimize(loss)
        place = fluid.CPUPlace()
        graph = IrGraph(core.Graph(main.desc), for_test=False)
        transform_pass = QuantizationTransformPassV2(
            scope=fluid.global_scope(),
            place=place,
            activation_quantize_type=activation_quant_type,
            weight_quantize_type=weight_quantize_type)
        transform_pass.apply(graph)
        if not for_ci:
            marked_nodes = set()
            for op in graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
            graph.draw('.', 'quantize_fc_' + activation_quant_type,
                       marked_nodes)
        program = graph.to_program()
        self.check_program(program)
        val_graph = IrGraph(core.Graph(program.desc), for_test=False)
        if not for_ci:
            val_marked_nodes = set()
            for op in val_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    val_marked_nodes.add(op)
            val_graph.draw('.', 'val_fc_' + activation_quant_type,
                           val_marked_nodes)

    def test_linear_fc_quant_abs_max(self):
        self.linear_fc_quant('abs_max', 'abs_max', for_ci=True)

    def test_linear_fc_quant_channel_wise_abs_max(self):
        self.linear_fc_quant('abs_max', 'channel_wise_abs_max', for_ci=True)

    def residual_block_quant(self,
                             activation_quant_type,
                             weight_quantize_type,
                             quantizable_op_type,
                             for_ci=True):
        main = fluid.Program()
        startup = fluid.Program()
        with fluid.program_guard(main, startup):
            loss = residual_block(2)
            opt = fluid.optimizer.Adam(learning_rate=0.001)
            opt.minimize(loss)
        place = fluid.CPUPlace()
        graph = IrGraph(core.Graph(main.desc), for_test=False)
        transform_pass = QuantizationTransformPass(
            scope=fluid.global_scope(),
            place=place,
            activation_quantize_type=activation_quant_type,
            weight_quantize_type=weight_quantize_type,
            quantizable_op_type=quantizable_op_type)
        transform_pass.apply(graph)
        if not for_ci:
            marked_nodes = set()
            for op in graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    marked_nodes.add(op)
            graph.draw('.', 'quantize_residual_' + activation_quant_type,
                       marked_nodes)
        program = graph.to_program()
        self.check_program(program)
        val_graph = IrGraph(core.Graph(program.desc), for_test=False)
        if not for_ci:
            val_marked_nodes = set()
            for op in val_graph.all_op_nodes():
                if op.name().find('quantize') > -1:
                    val_marked_nodes.add(op)
            val_graph.draw('.', 'val_residual_' + activation_quant_type,
                           val_marked_nodes)

    def test_residual_block_abs_max(self):
        quantizable_op_type = ['conv2d', 'depthwise_conv2d', 'mul', 'matmul']
830 831 832 833
        self.residual_block_quant('abs_max',
                                  'abs_max',
                                  quantizable_op_type,
                                  for_ci=True)
834 835 836

    def test_residual_block_channel_wise_abs_max(self):
        quantizable_op_type = ['conv2d', 'depthwise_conv2d', 'mul', 'matmul']
837 838 839 840
        self.residual_block_quant('abs_max',
                                  'channel_wise_abs_max',
                                  quantizable_op_type,
                                  for_ci=True)
841 842


W
WangZhen 已提交
843 844
if __name__ == '__main__':
    unittest.main()