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

[Frontend][PaddlePaddle] Add autopad for conv/pool #9295

Merged
merged 16 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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
159 changes: 126 additions & 33 deletions python/tvm/relay/frontend/paddlepaddle.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# pylint: disable=import-outside-toplevel
"""Paddle: PArallel Distributed Deep LEarning."""

import warnings
import numpy as np

import tvm
Expand All @@ -43,18 +44,56 @@
__all__ = ["from_paddle"]


def _get_pad_size(in_size, dilated_kernel_size, stride_size):
"""Calculate the paddings size for Conv/Pool in SAME padding mode."""
def _autopad(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just use

def autopad(
data,
strides,
kernel_shape,
dilations,
ndim,
pad_type="constant",
deconv=False,
mode="SAME_UPPER",
pad_value=0.0,
):
"""
Perform autopadding with dynamic input shapes
"""
# get attributes as constants
strides = _op.const(np.array(strides), dtype="int64")
dilated_kernel_shape = _op.const(
np.array(
[(kernel - 1) * dilation + 1 for kernel, dilation in zip(kernel_shape, dilations)]
),
dtype="int64",
)
# get input shape
shape = _op.strided_slice(shape_of(data, dtype="int64"), [2], [ndim])
# set up integer constants
zero = _op.const(0, dtype="int64")
one = _op.const(1, dtype="int64")
two = _op.const(2, dtype="int64")
# Calculate total padding
mod = _op.mod(shape, strides)
left = _op.maximum(dilated_kernel_shape - strides, zero)
right = _op.maximum(dilated_kernel_shape - mod, zero)
total_pad = _op.where(_op.equal(mod, zero), left, right)
if deconv:
total_pad = _op.const(np.array(kernel_shape), dtype="int64") - one - total_pad
# split total padding into before and after
pad_before = _op.floor_divide(total_pad, two)
pad_after = total_pad - pad_before
# combine
if "LOWER" in mode:
pad = _op.concatenate(
[_op.reshape(pad_after, [-1, 1]), _op.reshape(pad_before, [-1, 1])], axis=1
)
else:
pad = _op.concatenate(
[_op.reshape(pad_before, [-1, 1]), _op.reshape(pad_after, [-1, 1])], axis=1
)
# pad N and C with zeros
pad = _op.concatenate([_op.const(np.zeros([2, 2], dtype="int64"), dtype="int64"), pad], axis=0)
if isinstance(pad_value, (float, int)):
pad_value = _op.const(pad_value)
return _op.nn.pad(data, fold_constant(pad), pad_value, pad_type)
?

  1. Refactor _autopad in the onnx.py file to tvm/python/tvm/relay/frontend/common.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good advice, I think this function also works for tensorflow and tflite to solve dynamic shape problem.

shape_of and autopad are both removed to common.py

data,
strides,
kernel_shape,
dilations=(1, 1),
pad_type="constant",
pad_value=0.0,
):
"""Perform padding under SAME mode for dynamic and fixed input shapes.
This implementation refers to ONNX frontend.
"""

if stride_size == 1 or in_size % stride_size == 0:
pad = max(dilated_kernel_size - stride_size, 0)
else:
pad = max(dilated_kernel_size - (in_size % stride_size), 0)
# get attributes as constants
strides = _op.const(np.array(strides), dtype="int64")
dilated_kernel_shape = _op.const(
np.array(
[(kernel - 1) * dilation + 1 for kernel, dilation in zip(kernel_shape, dilations)]
),
dtype="int64",
)
# get input shape
ndim = len(infer_shape(data))
shape = _op.strided_slice(shape_of(data, dtype="int64"), [2], [ndim])

# set up integer constants
zero = _op.const(0, dtype="int64")
two = _op.const(2, dtype="int64")

# Calculate total padding
mod = _op.mod(shape, strides)

pad_before = pad // 2
pad_after = pad - pad_before
left = _op.maximum(dilated_kernel_shape - strides, zero)
right = _op.maximum(dilated_kernel_shape - mod, zero)

total_pad = _op.where(_op.equal(mod, zero), left, right)

# split total padding into before and after
pad_before = _op.floor_divide(total_pad, two)
pad_after = total_pad - pad_before

pad = _op.concatenate(
[_op.reshape(pad_before, [-1, 1]), _op.reshape(pad_after, [-1, 1])], axis=1
)

return [pad_before, pad_after]
# pad N and C with zeros
pad = _op.concatenate([_op.const(np.zeros([2, 2], dtype="int64"), dtype="int64"), pad], axis=0)

if isinstance(pad_value, (float, int)):
pad_value = _op.const(pad_value)
return _op.nn.pad(data, fold_constant(pad), pad_value, pad_type)


def _dtype_shape_promotion(inputs):
Expand Down Expand Up @@ -248,24 +287,13 @@ def convert_conv2d(g, op, block):
if padding_algorithm == "VALID":
paddings = [0, 0]
elif padding_algorithm == "SAME":
if strides[0] == 1 and strides[1] == 1:
pad_h = _get_pad_size(0, (k_h - 1) * dilations[0] + 1, strides[0])
pad_w = _get_pad_size(0, (k_w - 1) * dilations[1] + 1, strides[1])
else:
input_shape = shape_of(input_x)
h_w = _op.strided_slice(input_shape, [2], [4])
try:
in_h, in_w = infer_value(h_w, g.get_params()).numpy().tolist()
except Exception as e:
msg = "Dynamic shape is not supported in SAME padding algorithm while stride!=1"
raise tvm.error.OpAttributeInvalid(msg) from e
pad_h = _get_pad_size(in_h, (k_h - 1) * dilations[0] + 1, strides[0])
pad_w = _get_pad_size(in_w, (k_w - 1) * dilations[1] + 1, strides[1])
paddings = [pad_h[0], pad_w[0], pad_h[1], pad_w[1]]
dilations = [1, 1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we mean to override the dilations?

Copy link
Contributor Author

@jiangjiajun jiangjiajun Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a history issue for Paddle framework, while padding==SAME, it will force dliations = 1
Here is the implementation code https://github.com/PaddlePaddle/Paddle/blob/develop/paddle/fluid/operators/conv_op.h#L113

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid confusion, I put a comment on this line of code to explain this problem.

input_x = _autopad(input_x, strides, [k_h, k_w], dilations)
paddings = [0, 0]
elif padding_algorithm == "EXPLICIT":
if len(paddings) == 2:
paddings = [paddings[0], paddings[1], paddings[0], paddings[1]]
if len(paddings) == 4:
elif len(paddings) == 4:
paddings = [paddings[0], paddings[2], paddings[1], paddings[3]]
else:
msg = 'Value {} in attribute "padding" of operator Conv is not "valid."'
Expand Down Expand Up @@ -686,6 +714,39 @@ def convert_mul(g, op, block):
g.add_node(op.output("Out")[0], out)


def convert_padding(g, op, block):
"""Operator converter for padding."""

input_x = g.get_node(op.input("X")[0])
input_padding = op.input("Paddings")
if input_padding:
padding = g.get_node(input_padding[0])
padding = infer_value(padding, g.get_params()).numpy().tolist()
else:
padding = op.attr("paddings")
padding = op.attr("paddings")
value = op.attr("value")
data_format = op.attr("data_format")
mode = op.attr("mode")
assert mode != "circular", "Don't support mod='circular' for PaddlePaddle's padding"
if mode == "replicate":
mode = "edge"

pad_len = len(padding)
new_paddings = [0] * (pad_len + 4)
for i in range(0, pad_len, 2):
index = -1 - i
if data_format[:2] != "NC":
index = -3 - i
new_paddings[index] = padding[i + 1]
new_paddings[index - 1] = padding[i]

new_paddings = [new_paddings[i : i + 2] for i in range(0, len(new_paddings), 2)]

out = _op.nn.pad(input_x, new_paddings, pad_value=value, pad_mode=mode)
g.add_node(op.output("Out")[0], out)


def convert_pool2d(g, op, block):
"""Operator converter for pool2d."""

Expand All @@ -696,17 +757,19 @@ def convert_pool2d(g, op, block):
paddings = op.attr("paddings")
padding_algorithm = op.attr("padding_algorithm")
pooling_type = op.attr("pooling_type")

if global_pooling:
adaptive = True
ksize = [1, 1]

input_x = g.get_node(op.input("X")[0])
in_h, in_w = infer_shape(input_x)[2:]
_, _, in_h, in_w = infer_shape(input_x)

op_map = {
"avg": "avg_pool2d",
"max": "max_pool2d",
}

strides = op.attr("strides")
if isinstance(strides, int):
strides = [strides, strides]
Expand All @@ -718,22 +781,40 @@ def convert_pool2d(g, op, block):
if padding_algorithm == "VALID":
paddings = [0, 0]
elif padding_algorithm == "SAME":
pad_h = _get_pad_size(in_h, ksize[0], strides[0])
pad_w = _get_pad_size(in_w, ksize[1], strides[1])
paddings = [pad_h[0], pad_w[0], pad_h[1], pad_w[1]]
input_x = _autopad(input_x, strides, ksize)
paddings = [0, 0]
elif padding_algorithm == "EXPLICIT":
if len(paddings) == 2:
paddings = [paddings[0], paddings[1], paddings[0], paddings[1]]
if len(paddings) == 4:
elif len(paddings) == 4:
paddings = [paddings[0], paddings[2], paddings[1], paddings[3]]
else:
msg = 'Value {} in attribute "padding" of operator Pool2d is not "valid."'
raise tvm.error.OpAttributeInvalid(msg.format(padding_algorithm))

# handle with special case
# while kernel size less than input size
# shrink kernel size to input size
if not isinstance(in_h, _op.Expr) and in_h < ksize[0]:
ksize[0] = in_h
if not isinstance(in_w, _op.Expr) and in_w < ksize[1]:
ksize[1] = in_w

if not adaptive:
out = getattr(_op.nn, op_map[pooling_type])(
input_x, pool_size=ksize, strides=strides, padding=paddings, ceil_mode=ceil_mode
)
if pooling_type == "avg":
exclusive = op.attr("exclusive")
out = _op.nn.avg_pool2d(
input_x,
pool_size=ksize,
strides=strides,
padding=paddings,
ceil_mode=ceil_mode,
count_include_pad=not exclusive,
)
else:
out = getattr(_op.nn, op_map[pooling_type])(
input_x, pool_size=ksize, strides=strides, padding=paddings, ceil_mode=ceil_mode
)
else:
out = getattr(_op.nn, "adaptive_" + op_map[pooling_type])(input_x, output_size=ksize)
g.add_node(op.output("Out")[0], out)
Expand Down Expand Up @@ -854,6 +935,17 @@ def convert_softmax(g, op, block):
g.add_node(op.output("Out")[0], out)


def convert_squeeze(g, op, block):
"""Operator converter for squeeze2."""

x = g.get_node(op.input("X")[0])
axes = op.attr("axes")
if not axes:
axes = None
x = _op.squeeze(x, axis=axes)
g.add_node(op.output("Out")[0], x)


def convert_unsqueeze(g, op, block):
"""Operator converter for unsqueeze."""

Expand Down Expand Up @@ -904,13 +996,15 @@ def convert_unsqueeze(g, op, block):
"matmul": convert_matmul,
"matmul_v2": convert_matmul,
"mul": convert_mul,
"pad3d": convert_padding,
"pool2d": convert_pool2d,
"relu": convert_unary_op,
"reshape2": convert_reshape,
"scale": convert_scale,
"shape": convert_shape,
"slice": convert_slice,
"softmax": convert_softmax,
"squeeze2": convert_squeeze,
"tanh": convert_unary_op,
"unsqueeze2": convert_unsqueeze,
}
Expand Down Expand Up @@ -1062,7 +1156,6 @@ def from_translated_layer(self, layer, shape_dict):

def from_paddle(program_or_layer, shape_dict=None, scope=None):
"""Convert a PaddlePaddle model into an equivalent Relay Function.

PaddlePaddle Program/TranslatedLayer represent the computation graph of PaddlePaddle model,
and PaddlePaddle scope stores all the weights of PaddlePaddle model.

Expand Down
Loading