qat.py 28.1 KB
Newer Older
1
#   Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
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
import os
16

17
import paddle
18
from paddle.fluid.framework import IrGraph
19
from paddle.framework import core
20
from paddle.nn.quant import quant_layers
21 22 23 24 25 26 27 28 29 30

from ...static.quantization.quantization_pass import (
    QuantWeightPass,
    ReplaceFakeQuantDequantPass,
)
from ...static.quantization.utils import (
    _get_input_name_index,
    _get_op_input_var_names,
    _get_output_name_index,
    move_persistable_var_to_global_block,
31
)
32 33 34 35
from . import fuse_utils, utils

INFER_MODEL_SUFFIX = ".pdmodel"
INFER_PARAMS_SUFFIX = ".pdiparams"
36 37


38 39
def lazy_import_fleet(layer_name_map, fake_quant_input_layers):
    from paddle.distributed import fleet
40

41
    layer_name_map[
42 43
        'ColumnParallelLinear'
    ] = fleet.meta_parallel.parallel_layers.mp_layers.ColumnParallelLinear
44
    layer_name_map[
45 46
        'RowParallelLinear'
    ] = fleet.meta_parallel.parallel_layers.mp_layers.RowParallelLinear
47 48 49 50 51
    fake_quant_input_layers.append(fleet.meta_parallel.RowParallelLinear)
    fake_quant_input_layers.append(fleet.meta_parallel.ColumnParallelLinear)
    return layer_name_map, fake_quant_input_layers


52
class ImperativeQuantAware:
53
    """
54
    Applying quantization aware training (QAT) to the dgraph model.
55 56
    """

57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
    def __init__(
        self,
        quantizable_layer_type=[
            'Conv2D',
            'Linear',
            'Conv2DTranspose',
            'ColumnParallelLinear',
            'RowParallelLinear',
        ],
        weight_quantize_type='abs_max',
        activation_quantize_type='moving_average_abs_max',
        weight_bits=8,
        activation_bits=8,
        moving_rate=0.9,
        fuse_conv_bn=False,
        weight_preprocess_layer=None,
        act_preprocess_layer=None,
        weight_quantize_layer=None,
        act_quantize_layer=None,
        onnx_format=False,
    ):
C
cc 已提交
78
        """
79 80 81
        The constructor for ImperativeQuantAware.

        Args:
82 83
            quantizable_layer_type(list[str | layer]): List the type of
                layers that will be quantized. Default is ['Conv2D', 'Linear'].
84
            weight_quantize_type(str): quantization type for weights,
85
                which supports 'abs_max' and 'channel_wise_abs_max'.
86 87
            activation_quantize_type(str): quantization type for activations,
                which supports 'abs_max' and 'moving_average_abs_max' now.
C
cc 已提交
88 89 90 91 92
                If using 'abs_max' mode, the quantization scale will be
                calculated dynamically each step in both training and testing
                period. If using 'moving_average_abs_max', the static
                quantization scale will be calculated during training and
                used in inference.
93 94
            weight_bits(int): quantization bit number for weights, whereas
                the bias is not quantized.
C
cc 已提交
95 96 97
            activation_bits(int): quantization bit number for activations.
            moving_rate(float): the parameter for 'moving_average_abs_max'
                quantization.
98
            fuse_conv_bn(bool): Whether to fuse conv and bn, default is False.
C
cc 已提交
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
            weight_preprocess_layer(paddle.nn.Layer, optional): A paddle
                Layer that defines how to preprocess weight before quantization.
                Using this can quickly test if user's preprocess method works
                or not. The input is non-quantized weight and function returns
                processed weight to be quantized.
                If None, the weight will be quantized directly.
                Default is None.
            act_preprocess_layer(paddle.nn.Layer, optional): A paddle Layer
                that defines how to preprocess activation before quantization.
                Using this can quickly test if user's preprocess method works
                or not. The input is non-quantized activation and function returns
                processed activation to be quantized.
                If None, the activation will be quantized directly.
                Default is None.
            weight_quantize_layer(paddle.nn.Layer, optional): A paddle Layer that
                defines how to quantize weight.
115 116 117
                Using this can quickly test if user's quantization method works or not.
                In this layer, user should both define quantization method and
                dequantization method, that is, the function's input is non-quantized
C
cc 已提交
118 119 120 121 122
                weight and returns dequantized weight.
                If None, will use uantization op defined by 'weight_quantize_type'.
                Default is None.
            act_quantize_layer(paddle.nn.Layer, optional): A paddle Layer that defines
                how to quantize activation.
123 124 125
                Using this can quickly test if user's quantization method works or not.
                In this layer, user should both define quantization method and
                dequantization method, that is, the function's input is non-quantized
126
                activation and returns dequantized activation.
C
cc 已提交
127 128
                If None, will use quantization op defined by 'activation_quantize_type'.
                Default is None.
129 130
            onnx_format (bool, optional): Whether to export the quantized model
                with format of ONNX. Default is False.
131

132
        Note:
C
cc 已提交
133 134 135 136
            If user sets attribute 'skip_quant' to a Layer that support dynamic
            quantization and sets it to true, the layer would not be quantized
            during training. If this attribute is not sets or the attribute is
            false, the Layer would be qunatized in training.
137 138

        Examples 1:
139 140
        .. code-block:: python

141
            import paddle
142
            from paddle.static.quantization \
143
                import ImperativeQuantAware
144
            from paddle.vision.models \
145
                import resnet
146

147 148 149 150 151
            model = resnet.resnet50(pretrained=True)

            imperative_qat = ImperativeQuantAware(
                weight_quantize_type='abs_max',
                activation_quantize_type='moving_average_abs_max')
152

153 154
            # Add the fake quant logical.
            # The original model will be rewrite.
155
            # The outscale of outputs in supportted layers would be calculated.
156 157 158 159
            imperative_qat.quantize(model)

            # Fine-tune the quantized model
            # ...
160

161
            # Save quant model for the inference.
162
            imperative_qat.save_quantized_model(
163 164 165 166 167
                layer=model,
                model_path="./resnet50_qat",
                input_spec=[
                    paddle.static.InputSpec(
                    shape=[None, 3, 224, 224], dtype='float32')])
168 169 170 171 172

        Examples 2:
        .. code-block:: python

            import paddle
173
            from paddle.static.quantization \
174 175 176 177
                import ImperativeQuantAware

            class ImperativeModel(paddle.nn.Layer):
                def __init__(self):
178
                    super().__init__()
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
                    # self.linear_0 would skip the quantization.
                    self.linear_0 = paddle.nn.Linear(784, 400)
                    self.linear_0.skip_quant = True

                    # self.linear_1 would not skip the quantization.
                    self.linear_1 = paddle.nn.Linear(400, 10)
                    self.linear_1.skip_quant = False

                def forward(self, inputs):
                    x = self.linear_0(inputs)
                    x = self.linear_1(inputs)
                    return x

            model = ImperativeModel()
            imperative_qat = ImperativeQuantAware(
                weight_quantize_type='abs_max',
                activation_quantize_type='moving_average_abs_max')

            # Add the fake quant logical.
            # The original model will be rewrite.
            #
            # There is only one Layer(self.linear1) would be added the
            # fake quant logical.
            imperative_qat.quantize(model)

            # Fine-tune the quantized model
            # ...

            # Save quant model for the inference.
            imperative_qat.save_quantized_model(
                layer=model,
                model_path="./imperative_model_qat")
211
        """
212
        super().__init__()
213
        self.fuse_conv_bn = fuse_conv_bn
H
huangxu96 已提交
214

C
cc 已提交
215 216 217 218 219 220 221 222 223 224
        kwargs = {
            "quantizable_layer_type": quantizable_layer_type,
            "weight_quantize_type": weight_quantize_type,
            "activation_quantize_type": activation_quantize_type,
            "weight_bits": weight_bits,
            "activation_bits": activation_bits,
            "moving_rate": moving_rate,
            "weight_preprocess_layer": weight_preprocess_layer,
            "act_preprocess_layer": act_preprocess_layer,
            "weight_quantize_layer": weight_quantize_layer,
225
            "act_quantize_layer": act_quantize_layer,
226
        }
C
cc 已提交
227 228 229

        self._quantize_inputs = ImperativeQuantizeInputs(**kwargs)

230
        self._quantize_outputs = ImperativeQuantizeOutputs(
231 232
            moving_rate, activation_bits, onnx_format
        )
233 234 235

    def quantize(self, model):
        """
C
cc 已提交
236 237 238 239 240
        According to weights' and activations' quantization types,
        the model will be added some fake quant ops, such as
        fake_quantize_dequantize_moving_average_abs_max,
        fake_quantize_dequantize_abs_max and so on. At the same time,
        the out_scale value of outputs would be calculated.
241 242

        Args:
243
            model(paddle.nn.Layer): the model to be quantized.
244 245
        Returns:
            None
246 247 248 249 250

        Examples:
        .. code-block:: python

            import paddle
251
            from paddle.static.quantization \
252 253 254 255
                import ImperativeQuantAware

            class ImperativeModel(paddle.nn.Layer):
                def __init__(self):
256
                    super().__init__()
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
                    # self.linear_0 would skip the quantization.
                    self.linear_0 = paddle.nn.Linear(784, 400)
                    self.linear_0.skip_quant = True

                    # self.linear_1 would not skip the quantization.
                    self.linear_1 = paddle.nn.Linear(400, 10)
                    self.linear_1.skip_quant = False

                def forward(self, inputs):
                    x = self.linear_0(inputs)
                    x = self.linear_1(inputs)
                    return x

            model = ImperativeModel()
            imperative_qat = ImperativeQuantAware(
                weight_quantize_type='abs_max',
                activation_quantize_type='moving_average_abs_max')

            # Add the fake quant logical.
            # The original model will be rewrite.
            #
            # There is only one Layer(self.linear1) would be added the
            # fake quant logical.
            imperative_qat.quantize(model)
281
        """
282
        assert isinstance(
283 284
            model, paddle.nn.Layer
        ), "The model must be the instance of paddle.nn.Layer."
285 286 287 288

        if self.fuse_conv_bn:
            fuse_utils.fuse_conv_bn(model)

C
cc 已提交
289
        self._quantize_inputs.apply(model)
290
        self._quantize_outputs.apply(model)
291
        return model
C
cc 已提交
292 293

    def save_quantized_model(self, layer, path, input_spec=None, **config):
294 295 296
        self._quantize_outputs.save_quantized_model(
            layer, path, input_spec, **config
        )
C
cc 已提交
297 298


299
class ImperativeQuantizeInputs:
C
cc 已提交
300 301 302 303 304
    """
    Based on the input params, add the quant_dequant computational
    logic both for activation inputs and weight inputs.
    """

305 306 307 308 309 310 311 312 313 314 315 316 317
    def __init__(
        self,
        quantizable_layer_type=['Conv2D', 'Linear', 'Conv2DTranspose'],
        weight_quantize_type='abs_max',
        activation_quantize_type='moving_average_abs_max',
        weight_bits=8,
        activation_bits=8,
        moving_rate=0.9,
        weight_preprocess_layer=None,
        act_preprocess_layer=None,
        weight_quantize_layer=None,
        act_quantize_layer=None,
    ):
C
cc 已提交
318
        """
319
        The constructor for ImperativeQuantizeInputs.
C
cc 已提交
320 321 322

        Please refer to the args of ImperativeQuantAware.
        """
323
        super().__init__()
324
        self.layer_name_map, self.fake_quant_input_layers = lazy_import_fleet(
325 326
            utils.layer_name_map, utils.fake_quant_input_layers
        )
C
cc 已提交
327 328

        self._quantizable_layer_type = tuple(
329 330 331 332 333
            self.layer_name_map[layer]
            if layer in self.layer_name_map
            else layer
            for layer in quantizable_layer_type
        )
C
cc 已提交
334
        for layer in self._quantizable_layer_type:
335 336 337 338
            assert (
                not isinstance(layer, str)
                and layer in self.fake_quant_input_layers
            ), ("%s is unspported to be quantized." % layer)
C
cc 已提交
339 340

        quantize_type = {
341 342 343 344 345
            'abs_max',
            'moving_average_abs_max',
            'channel_wise_abs_max',
            'lsq_weight',
            'channel_wise_lsq_weight',
C
cc 已提交
346
        }
C
Chang Xu 已提交
347
        act_quantize_type = {'moving_average_abs_max', 'lsq_act'}
348 349 350 351 352
        assert (
            weight_quantize_type != 'moving_average_abs_max'
            and weight_quantize_type in quantize_type
        ), (
            "Unsupported weight_quantize_type: %s. It can only "
353
            "be abs_max or channel_wise_abs_max." % weight_quantize_type
354
        )
355
        # TODO (jc): activation_quantize_type supports range_abs_max
356 357 358
        assert activation_quantize_type in act_quantize_type, (
            "Unsupported activation_quantize_type: %s. It can "
            "only be moving_average_abs_max or lsq_act now."
C
cc 已提交
359
            % activation_quantize_type
360 361 362 363 364 365 366 367 368 369 370
        )

        bits_check = (
            lambda bits: isinstance(bits, int) and bits >= 0 and bits <= 16
        )
        assert bits_check(weight_bits), "weight_bits should be 1, 2,... or 16."
        assert bits_check(
            activation_bits
        ), "activation_bits should be 1, 2,... or 16."

        layer_check = lambda method: method is None or issubclass(
371
            method, paddle.nn.Layer
372 373 374 375 376 377 378 379 380 381 382 383 384
        )
        assert layer_check(
            weight_preprocess_layer
        ), "weight_preprocess should be nn.Layer."
        assert layer_check(
            act_preprocess_layer
        ), "act_preprocess should be nn.Layer."
        assert layer_check(
            weight_quantize_layer
        ), "weight_quantize should be nn.Layer."
        assert layer_check(
            act_quantize_layer
        ), "act_quantize should be nn.Layer."
C
cc 已提交
385 386 387 388 389 390 391 392 393 394

        self._kwargs = {
            "weight_quantize_type": weight_quantize_type,
            "activation_quantize_type": activation_quantize_type,
            "weight_bits": weight_bits,
            "activation_bits": activation_bits,
            "moving_rate": moving_rate,
            "weight_pre_layer": weight_preprocess_layer,
            "act_pre_layer": act_preprocess_layer,
            "weight_quant_layer": weight_quantize_layer,
395
            "act_quant_layer": act_quantize_layer,
C
cc 已提交
396 397 398
        }

    def apply(self, model):
399
        """
400
        Quantize the weights and activations to calculate for specific
401 402 403 404 405 406 407 408 409 410
        layers.

        Args:
            model(paddle.nn.Layer): The target model which would
                calculate the input quantization scale.

        Returns:
            None
        """

411
        assert isinstance(
412 413
            model, paddle.nn.Layer
        ), "The model must be the instance of paddle.nn.Layer."
C
cc 已提交
414

415
        for name, cur_layer in model.named_sublayers():
416 417
            if not isinstance(cur_layer, self._quantizable_layer_type) or (
                hasattr(cur_layer, "skip_quant")
418
                and cur_layer.skip_quant is True
419
            ):
420 421
                continue

422 423 424
            parent_layer, sub_name = utils.find_parent_layer_and_sub_name(
                model, name
            )
425 426 427

            cur_quant_layer = self._get_input_quantized_layer(cur_layer)
            setattr(parent_layer, sub_name, cur_quant_layer)
428

429
    def _get_input_quantized_layer(self, layer):
C
cc 已提交
430
        quant_layer_name = None
431

432
        for key, value in self.layer_name_map.items():
C
cc 已提交
433 434 435
            if isinstance(layer, value):
                quant_layer_name = 'Quantized' + key
                break
436 437 438
        assert quant_layer_name is not None, (
            "The layer %s is unsupported to be quantized." % layer.full_name()
        )
439

440
        return quant_layers.__dict__[quant_layer_name](layer, **self._kwargs)
441

442

443
class ImperativeQuantizeOutputs:
444
    """
445
    Calculate the output scales for target layers.
446 447
    """

448
    def __init__(self, moving_rate=0.9, activation_bits=8, onnx_format=False):
449
        """
450
        The constructor for ImperativeQuantizeOutputs.
451 452

        Args:
C
cc 已提交
453 454
            moving_rate(float): The decay coefficient of moving average.
                                The default value is 0.9.
455
            activation_bits(int, optional): quantization bit number for activation. Default is 8.
456
        """
457
        super().__init__()
458
        self._moving_rate = moving_rate
459
        self._activation_bits = activation_bits
460
        self._onnx_format = onnx_format
461

C
cc 已提交
462
    def apply(self, model):
463
        """
464 465
        Insert the `moving_average_abs_max_scale` layers to calculate the
        output scales for specific layers in the dygraph model.
466 467

        Args:
468
            model(paddle.nn.Layer): The target model which would be
469
                calculate the output quantization scale.
470 471 472 473

        Returns:
            None
        """
474
        assert isinstance(
475 476
            model, paddle.nn.Layer
        ), "The model must be the instance of paddle.nn.Layer."
477

478
        for cur_name, cur_layer in model.named_sublayers():
X
XGZhang 已提交
479 480
            if '_act_preprocess' in cur_name:
                continue
481
            if not self._is_target_layer(cur_layer):
482 483
                continue

484 485 486
            parent_layer, sub_name = utils.find_parent_layer_and_sub_name(
                model, cur_name
            )
487

488 489
            reduce_type = None

490
            if isinstance(cur_layer, tuple(utils.fake_quant_output_layers)):
491
                cur_quant_layer = quant_layers.FakeQuantMAOutputScaleLayer(
492 493
                    cur_layer, self._moving_rate, reduce_type=reduce_type
                )
494
            else:
495
                cur_quant_layer = quant_layers.MAOutputScaleLayer(
496 497
                    cur_layer, self._moving_rate, reduce_type=reduce_type
                )
498 499

            setattr(parent_layer, sub_name, cur_quant_layer)
500

501
    def save_quantized_model(self, model, path, input_spec=None, **config):
502 503 504 505
        """
        Save the quantized model for the inference.

        Args:
506
            model (Layer): The model to be saved.
507
            path (str): The path prefix to save model. The format is
508 509 510
                ``dirname/file_prefix`` or ``file_prefix``.
            input_spec (list[InputSpec|Tensor], optional): Describes the input
                of the saved model's forward method, which can be described by
511
                InputSpec or example Tensor. If None, all input variables of
512 513
                the original Layer's forward method would be the inputs of
                the saved model. Default None.
514
            **config (dict, optional): Other save configuration options for
515 516 517
                compatibility. We do not recommend using these configurations,
                they may be removed in the future. If not necessary, DO NOT use
                them. Default None.
518
                The following options are currently supported:
519 520 521
                (1) output_spec (list[Tensor]): Selects the output targets of
                the saved model. By default, all return variables of original
                Layer's forward method are kept as the output of the saved model.
522
                If the provided ``output_spec`` list is not all output variables,
523
                the saved model will be pruned according to the given
524
                ``output_spec`` list.
525 526 527 528

        Returns:
            None
        """
529
        assert isinstance(
530 531
            model, paddle.nn.Layer
        ), "The model must be the instance of paddle.nn.Layer."
532

533 534
        if input_spec:
            paddle.jit.to_static(model, input_spec=input_spec)
535
        paddle.jit.save(layer=model, path=path, input_spec=input_spec, **config)
536 537

        is_dynamic_mode = False
538 539 540 541
        if paddle.in_dynamic_mode():
            is_dynamic_mode = True
            paddle.enable_static()

542
        place = core.CPUPlace()
543 544
        scope = paddle.static.global_scope()
        exe = paddle.static.Executor(place)
545 546

        dirname = os.path.dirname(path)
547 548 549
        basename = os.path.basename(path)
        model_filename = basename + INFER_MODEL_SUFFIX
        params_filename = basename + INFER_PARAMS_SUFFIX
550

551 552 553 554
        [
            infer_program,
            feed_target_names,
            fetch_targets,
555 556
        ] = paddle.static.load_inference_model(
            dirname,
557 558 559 560
            executor=exe,
            model_filename=model_filename,
            params_filename=params_filename,
        )
561

562
        if not self._onnx_format:
563
            self._gather_scales(infer_program, scope, fetch_targets)
564

565 566 567 568 569 570 571 572
            # Remove `moving_average_abs_max_scale` node in sub graphs.
            graph = IrGraph(core.Graph(infer_program.desc), for_test=False)
            for sub_graph in graph.all_sub_graphs():
                for _op in sub_graph.all_op_nodes():
                    if _op.name() == "moving_average_abs_max_scale":
                        sub_graph.safe_remove_nodes(_op)
                sub_graph.resolve_hazard()
            infer_program = graph.to_program()
573

574
            self._set_skip_quant_attr(infer_program)
G
guofei 已提交
575

576 577
            clip_extra = False
        else:
578
            graph = IrGraph(core.Graph(infer_program.desc), for_test=False)
579
            transform_pass = ReplaceFakeQuantDequantPass(
580 581
                scope, place, quant_bits=self._activation_bits
            )
582 583 584
            for sub_graph in graph.all_sub_graphs():
                sub_graph._for_test = True
                transform_pass.apply(sub_graph)
585 586

            quant_weight_pass = QuantWeightPass(scope, place)
587 588 589
            for sub_graph in graph.all_sub_graphs():
                sub_graph._for_test = True
                quant_weight_pass.apply(sub_graph)
590

591 592 593 594
            infer_program = graph.to_program()

            clip_extra = True

595 596
        move_persistable_var_to_global_block(infer_program)

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611
        model_name = None
        if model_filename is None:
            model_name = "model"
        elif model_filename.endswith(".pdmodel"):
            model_name = model_filename.rsplit(".", 1)[0]
        else:
            model_name = model_filename
        path_prefix = os.path.join(dirname, model_name)
        feed_vars = [
            infer_program.global_block().var(name) for name in feed_target_names
        ]
        paddle.static.save_inference_model(
            path_prefix,
            feed_vars,
            fetch_targets,
612
            executor=exe,
613
            program=infer_program.clone(),
614 615
            clip_extra=clip_extra,
        )
616

617 618 619
        if is_dynamic_mode:
            paddle.disable_static()

620
    def _is_target_layer(self, layer):
621
        """
622
        Whether the layer needs to calculate output scales.
623
        """
624
        # exclude fake_quant ops in quant_layers file
625
        if not isinstance(layer, paddle.nn.Layer):
626 627 628
            return False

        if self._onnx_format:
629 630 631 632 633
            return (
                True
                if isinstance(layer, tuple(utils.fake_quant_wrap_layers))
                else False
            )
634

635
        flag = False
636 637 638
        if utils.is_leaf_layer(layer) and not isinstance(
            layer, tuple(utils.fake_quant_leaf_layers)
        ):
639
            flag = True
640

641 642
        if isinstance(layer, tuple(utils.fake_quant_wrap_layers)):
            flag = True
643

644 645
        if isinstance(layer, paddle.nn.quant.FloatFunctionalLayer):
            flag = True
646

647
        return flag
C
cc 已提交
648

649
    def _gather_scales(self, program, scope, fetch_targets):
650
        """
651
        Get all scales from fake ops, save them into the corresponding ops
652
        and delete all moving_average_abs_max_scale ops.
653
        """
654 655 656

        def _gather_input_scale():
            target_ops = []
657 658 659
            skip_ops = utils.fake_quantize_dequantize_op_types + [
                "moving_average_abs_max_scale"
            ]
660 661 662 663 664 665
            for block in program.blocks:
                for op in block.ops:
                    if op.type not in skip_ops:
                        target_ops.append(op)

            for op in target_ops:
666
                for in_var_name in _get_op_input_var_names(op):
667 668
                    previous_op = utils.find_previous_op(op.block, in_var_name)

669 670 671 672
                    if previous_op is not None and (
                        "quantize_dequantize" in previous_op.type
                        or previous_op.type == "moving_average_abs_max_scale"
                    ):
673 674 675
                        scale_name = previous_op.output('OutScale')[0]
                        in_scale = utils.load_variable_data(scope, scale_name)
                        in_scale = utils.fp_numpy_to_naive(in_scale)
676
                        argname, index = _get_input_name_index(op, in_var_name)
677 678 679
                        op._set_attr(
                            argname + str(index) + "_threshold", in_scale
                        )
680
                        op._set_attr("with_quant_attr", True)
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700

        def _gather_output_scale():
            target_ops = []
            for block in program.blocks:
                for op in block.ops:
                    if op.type == "moving_average_abs_max_scale":
                        target_ops.append(op)

            for op in target_ops:
                in_var_name = op.input('X')[0]
                out_var_name = op.output('Out')[0]
                block = op.block
                previous_op = utils.find_previous_op(block, in_var_name)
                next_ops = utils.find_next_ops(block, out_var_name)

                out_scale_name = op.output('OutScale')[0]
                out_scale = utils.load_variable_data(scope, out_scale_name)
                out_scale = utils.fp_numpy_to_naive(out_scale)

                if previous_op.type != "feed":
701
                    res = _get_output_name_index(previous_op, in_var_name)
X
XGZhang 已提交
702 703 704
                    if res is not None:
                        argname, index = res
                        previous_op._set_attr(
705 706
                            argname + str(index) + "_threshold", out_scale
                        )
X
XGZhang 已提交
707
                        previous_op._set_attr("out_threshold", out_scale)
708
                        previous_op._set_attr("with_quant_attr", True)
709 710 711

                for next_op in next_ops:
                    next_op._rename_input(out_var_name, in_var_name)
712 713 714 715 716
                    # If next_op is `fetch` and out_var_name in fetch_targets,
                    # fetch_targets must update to in_var_name when rename input.
                    for i in range(len(fetch_targets)):
                        if fetch_targets[i].name == out_var_name:
                            fetch_targets[i] = block.var(in_var_name)
717 718 719

        _gather_input_scale()
        _gather_output_scale()
C
cc 已提交
720

721
    def _set_skip_quant_attr(self, program):
722
        """
723
        Label the skip quantized ops.
724
        """
725 726 727 728
        for block in program.blocks:
            for op in block.ops:
                if self._is_skip_quant_op(block, op):
                    op._set_attr("skip_quant", True)
729
                    op._set_attr("with_quant_attr", True)
G
guofei 已提交
730 731 732 733 734 735 736

    def _is_skip_quant_op(self, block, in_op):
        """
        The input op should be skipped quantization.
        1. the type of input op should be conv2d, depthwise_conv2d or matmul
        2. the previous ops of the input op are not fake_quantize_dequantize ops
        """
737
        target_op_types = [
738 739 740 741
            "conv2d",
            "depthwise_conv2d",
            "matmul",
            "conv2d_transpose",
742
        ]
G
guofei 已提交
743 744 745
        if in_op.type not in target_op_types:
            return False

746 747 748 749 750 751 752 753 754
        previous_ops = [
            utils.find_previous_op(block, arg_name)
            for arg_name in in_op.input_arg_names
        ]
        return any(
            op is not None
            and op.type not in utils.fake_quantize_dequantize_op_types
            for op in previous_ops
        )