Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support fuse layers for ptq #35015

Merged
merged 37 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f4f31f2
support quantization of conv2d_transpose
XGZhang11 Aug 2, 2021
11fbba0
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 5, 2021
ac21a60
fix quantization bugs
XGZhang11 Aug 5, 2021
350048e
Update post_training_quantization.py
XGZhang11 Aug 8, 2021
cdfa3fe
Update quantization_pass.py
XGZhang11 Aug 8, 2021
111387f
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 8, 2021
9cfc38f
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 9, 2021
4b047da
update docs
XGZhang11 Aug 9, 2021
e5ea4eb
add tests for quantized_conv2d_transpose
XGZhang11 Aug 9, 2021
3231853
update codestyle
XGZhang11 Aug 9, 2021
da48df7
update docs
XGZhang11 Aug 9, 2021
7981cb3
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 14, 2021
43976be
update tests and conv2dtranspose layer
XGZhang11 Aug 14, 2021
8ec36b6
update quant tests
XGZhang11 Aug 14, 2021
fa20111
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 16, 2021
fc74ab0
update sampcd_processor for tests
XGZhang11 Aug 16, 2021
ccd1675
update code examples
XGZhang11 Aug 16, 2021
199cf30
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 16, 2021
eb0fa57
Merge branch 'develop' of github.com:XGZhang11/Paddle into fuse_1
XGZhang11 Aug 18, 2021
0250eed
Merge branch 'PaddlePaddle:develop' into develop
XGZhang11 Aug 18, 2021
7819d62
Merge branch 'develop' of github.com:XGZhang11/Paddle into fuse_1
XGZhang11 Aug 18, 2021
ff95292
support fuse for eval
XGZhang11 Aug 19, 2021
2f7cb4b
Merge branch 'PaddlePaddle:develop' into fuse_1
XGZhang11 Aug 19, 2021
4140a48
add tests for fuse
XGZhang11 Aug 19, 2021
b75e4cf
support fuse conv/linear+bn
XGZhang11 Aug 19, 2021
efb1acd
update test
XGZhang11 Aug 20, 2021
8870cc3
update test
XGZhang11 Aug 20, 2021
eca29a6
update test
XGZhang11 Aug 20, 2021
323cc3d
add test
XGZhang11 Aug 20, 2021
7a34ddb
add test
XGZhang11 Aug 20, 2021
97a1666
add test
XGZhang11 Aug 20, 2021
4b9c5dd
add test
XGZhang11 Aug 21, 2021
ba689a7
add test
XGZhang11 Aug 21, 2021
6c74925
add test
XGZhang11 Aug 22, 2021
5d3396a
add test
XGZhang11 Aug 22, 2021
fb67b15
modified according to reviewers' suggestions
XGZhang11 Aug 27, 2021
1c334c0
update test
XGZhang11 Aug 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions python/paddle/fluid/contrib/slim/quantization/imperative/fuse_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import paddle
import paddle.nn as nn
from . import utils


class Identity(nn.Layer):
'''a layer to replace bn or relu layers'''

def __init__(self, *args, **kwargs):
super(Identity, self).__init__()

def forward(self, input):
return input


def fuse_layers(model, layers_to_fuse, inplace=False):
'''
fuse layers in layers_to_fuse

Args:
model(paddle.nn.Layer): The model to be fused.
layers_to_fuse(list): The layers' names to be fused. For
example,"fuse_list = [["conv1", "bn1"], ["conv2", "bn2"]]".
A TypeError would be raised if "fuse" was set as
True but "fuse_list" was None.
Default: None.
inplace(bool): Whether apply fusing to the input model.
Default: False.

Return
fused_model(paddle.nn.Layer): The fused model.
'''
if inplace == False:
model = copy.deepcopy(model)
for layers in layers_to_fuse:
_fuse_layers(model, layers)
return model


def _fuse_layers(model, layers_list):
'''fuse all the layers in layers_list'''
layer_list = []
for layer_name in layers_list:
parent_layer, sub_name = utils.find_parent_layer_and_sub_name(
model, layer_name)
layer_list.append(getattr(parent_layer, sub_name))
new_layers = _fuse_func(layer_list)
for i, item in enumerate(layers_list):
parent_layer, sub_name = utils.find_parent_layer_and_sub_name(model,
item)
setattr(parent_layer, sub_name, new_layers[i])


def _fuse_func(layer_list):
'''choose the fuser method and fuse layers'''
types = tuple(type(m) for m in layer_list)
fusion_method = types_to_fusion_method.get(types, None)
new_layers = [None] * len(layer_list)
fused_layer = fusion_method(*layer_list)
for handle_id, pre_hook_fn in layer_list[0]._forward_pre_hooks.items():
fused_layer.register_forward_pre_hook(pre_hook_fn)
del layer_list[0]._forward_pre_hooks[handle_id]
for handle_id, hook_fn in layer_list[-1]._forward_post_hooks.items():
fused_layer.register_forward_post_hook(hook_fn)
del layer_list[-1]._forward_post_hooks[handle_id]
new_layers[0] = fused_layer
for i in range(1, len(layer_list)):
identity = Identity()
identity.training = layer_list[0].training
new_layers[i] = identity
return new_layers


def _fuse_conv_bn(conv, bn):
'''fuse conv and bn for train or eval'''
assert(conv.training == bn.training),\
"Conv and BN both must be in the same mode (train or eval)."
if conv.training:
assert bn._num_features == conv._out_channels, 'Output channel of Conv2d must match num_features of BatchNorm2d'
raise NotImplementedError
else:
return _fuse_conv_bn_eval(conv, bn)


def _fuse_conv_bn_eval(conv, bn):
'''fuse conv and bn for eval'''
assert (not (conv.training or bn.training)), "Fusion only for eval!"
fused_conv = copy.deepcopy(conv)

fused_weight, fused_bias = _fuse_conv_bn_weights(
fused_conv.weight, fused_conv.bias, bn._mean, bn._variance, bn._epsilon,
bn.weight, bn.bias)
fused_conv.weight.set_value(fused_weight)
if fused_conv.bias is None:
fused_conv.bias = paddle.create_parameter(
shape=[fused_conv._out_channels], is_bias=True, dtype=bn.bias.dtype)
fused_conv.bias.set_value(fused_bias)
return fused_conv


def _fuse_conv_bn_weights(conv_w, conv_b, bn_rm, bn_rv, bn_eps, bn_w, bn_b):
'''fuse weights and bias of conv and bn'''
if conv_b is None:
conv_b = paddle.zeros_like(bn_rm)
if bn_w is None:
bn_w = paddle.ones_like(bn_rm)
if bn_b is None:
bn_b = paddle.zeros_like(bn_rm)
bn_var_rsqrt = paddle.rsqrt(bn_rv + bn_eps)
conv_w = conv_w * \
(bn_w * bn_var_rsqrt).reshape([-1] + [1] * (len(conv_w.shape) - 1))
conv_b = (conv_b - bn_rm) * bn_var_rsqrt * bn_w + bn_b
return conv_w, conv_b


def _fuse_linear_bn(linear, bn):
'''fuse linear and bn'''
assert (linear.training == bn.training),\
"Linear and BN both must be in the same mode (train or eval)."
if linear.training:
assert bn._num_features == linear.weight.shape[
1], 'Output channel of Linear must match num_features of BatchNorm'
raise NotImplementedError
else:
return _fuse_linear_bn_eval(linear, bn)


def _fuse_linear_bn_eval(linear, bn):
'''fuse linear and bn for eval'''
assert (not (linear.training or bn.training)), "Fusion only for eval!"
fused_linear = copy.deepcopy(linear)

fused_weight, fused_bias = _fuse_linear_bn_weights(
fused_linear.weight, fused_linear.bias, bn._mean, bn._variance,
bn._epsilon, bn.weight, bn.bias)
fused_linear.weight.set_value(fused_weight)
if fused_linear.bias is None:
fused_linear.bias = paddle.create_parameter(
shape=[fused_linear.weight.shape[1]],
is_bias=True,
dtype=bn.bias.dtype)
fused_linear.bias.set_value(fused_bias)
return fused_linear


def _fuse_linear_bn_weights(linear_w, linear_b, bn_rm, bn_rv, bn_eps, bn_w,
bn_b):
'''fuse weights and bias of linear and bn'''
if linear_b is None:
linear_b = paddle.zeros_like(bn_rm)
bn_scale = bn_w * paddle.rsqrt(bn_rv + bn_eps)
fused_w = linear_w * bn_scale.unsqueeze(-1)
fused_b = (linear_b - bn_rm) * bn_scale + bn_b
return fused_w, fused_b


types_to_fusion_method = {
(nn.Conv2D, nn.BatchNorm2D): _fuse_conv_bn,
(nn.Linear, nn.BatchNorm1D): _fuse_linear_bn,
}
17 changes: 13 additions & 4 deletions python/paddle/fluid/contrib/slim/quantization/imperative/ptq.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from paddle.fluid.log_helper import get_logger
from paddle.fluid.dygraph.io import INFER_MODEL_SUFFIX, INFER_PARAMS_SUFFIX

from . import fuse_utils
from . import utils
from . import ptq_hooks
from . import ptq_config
Expand Down Expand Up @@ -55,23 +56,31 @@ def __init__(self, quant_config=ptq_config.default_ptq_config):

self._quant_config = quant_config

def quantize(self, model, inplace=False):
def quantize(self, model, inplace=False, fuse=False, fuse_list=None):
"""
Add quant config and hook to the target layer.

Args:
model(paddle.nn.Layer): The model to be quantized.
inplace(bool): Whether apply quantization to the input model.
Default: False.
Returns:
fuse(bool): Whether to fuse layers.
Default: False.
fuse_list(list): The layers' names to be fused. For example,
"fuse_list = [["conv1", "bn1"], ["conv2", "bn2"]]".
A TypeError would be raised if "fuse" was set as
True but "fuse_list" was None.
Default: None.
Return
quantized_model(paddle.nn.Layer): The quantized model.
"""
assert isinstance(model, paddle.nn.Layer), \
"The model must be the instance of paddle.nn.Layer."

if not inplace:
model = copy.deepcopy(model)

if fuse:
model.eval()
model = fuse_utils.fuse_layers(model, fuse_list)
for name, layer in model.named_sublayers():
if PTQRegistry.is_supported_layer(layer) \
and utils.is_leaf_layer(layer) \
Expand Down
60 changes: 60 additions & 0 deletions python/paddle/fluid/contrib/slim/tests/imperative_test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from paddle.fluid.dygraph.container import Sequential
from paddle.nn import ReLU, ReLU6, LeakyReLU, Sigmoid, Softmax, PReLU
from paddle.nn import Linear, Conv2D, Softmax, BatchNorm2D, MaxPool2D
from paddle.nn import BatchNorm1D

from paddle.fluid.log_helper import get_logger

Expand All @@ -43,6 +44,15 @@ def fix_model_dict(model):
return model


def pre_hook(layer, input):
input_return = (input[0] * 2)
return input_return


def post_hook(layer, input, output):
return output * 2


def train_lenet(lenet, reader, optimizer):
loss_list = []
lenet.train()
Expand Down Expand Up @@ -224,3 +234,53 @@ def forward(self, inputs):
x = self.softmax_0(x)

return x


class ImperativeLinearBn(fluid.dygraph.Layer):
def __init__(self):
super(ImperativeLinearBn, self).__init__()

fc_w_attr = paddle.ParamAttr(
name="fc_weight",
initializer=paddle.nn.initializer.Constant(value=0.5))
fc_b_attr = paddle.ParamAttr(
name="fc_bias",
initializer=paddle.nn.initializer.Constant(value=1.0))
bn_w_attr = paddle.ParamAttr(
name="bn_weight",
initializer=paddle.nn.initializer.Constant(value=0.5))

self.linear = Linear(
in_features=10,
out_features=10,
weight_attr=fc_w_attr,
bias_attr=fc_b_attr)
self.bn = BatchNorm1D(10, weight_attr=bn_w_attr)

def forward(self, inputs):
x = self.linear(inputs)
x = self.bn(x)

return x


class ImperativeLinearBn_hook(fluid.dygraph.Layer):
def __init__(self):
super(ImperativeLinearBn_hook, self).__init__()

fc_w_attr = paddle.ParamAttr(
name="linear_weight",
initializer=paddle.nn.initializer.Constant(value=0.5))

self.linear = Linear(
in_features=10, out_features=10, weight_attr=fc_w_attr)
self.bn = BatchNorm1D(10)

forward_pre = self.linear.register_forward_pre_hook(pre_hook)
forward_post = self.bn.register_forward_post_hook(post_hook)

def forward(self, inputs):
x = self.linear(inputs)
x = self.bn(x)

return x
Loading