-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Changes from 13 commits
5b39c79
74cc942
e0420bd
f181b0a
800b187
a0a33fb
73a21b0
bf3b0a9
30a9922
7c2afce
cd8296a
4ebcdeb
ab3278a
fee1886
9c442a9
211022b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
# pylint: disable=import-outside-toplevel | ||
"""Paddle: PArallel Distributed Deep LEarning.""" | ||
|
||
import warnings | ||
import numpy as np | ||
|
||
import tvm | ||
|
@@ -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( | ||
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): | ||
|
@@ -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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we mean to override the dilations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a history issue for Paddle framework, while padding== There was a problem hiding this comment. Choose a reason for hiding this commentThe 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."' | ||
|
@@ -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.""" | ||
|
||
|
@@ -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] | ||
|
@@ -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) | ||
|
@@ -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.""" | ||
|
||
|
@@ -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, | ||
} | ||
|
@@ -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. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you just use
tvm/python/tvm/relay/frontend/onnx.py
Lines 412 to 472 in d0c6ca5
tvm/python/tvm/relay/frontend/common.py
There was a problem hiding this comment.
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
andautopad
are both removed tocommon.py