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

【PaddlePaddle Hackathon 3】Add Paddle group_norm operator #12329

Merged
merged 6 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions src/core/tests/frontend/paddle/op_fuzzy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ static const std::vector<std::string> models{
std::string("greater_than_float32"),
std::string("greater_than_int32"),
std::string("greater_than_int64"),
std::string("group_norm_1"),
std::string("group_norm_2"),
std::string("group_norm_3"),
std::string("hard_sigmoid"),
std::string("hard_swish"),
std::string("layer_norm"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright (C) 2018-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0

#
# group norm paddle model generator
#
import numpy as np
from save_model import saveModel
import paddle
import sys

data_type = "float32"

def group_norm(name: str, x, groups, epsilon, scale, bias, data_layout):
paddle.enable_static()

with paddle.static.program_guard(paddle.static.Program(), paddle.static.Program()):
node_x = paddle.static.data(name="x", shape=x.shape, dtype=data_type)
if scale is False:
scale_attr = scale
else:
scale_attr = paddle.ParamAttr(name="scale1", initializer=paddle.nn.initializer.Assign(scale))
if bias is False:
bias_attr = bias
else:
bias_attr = paddle.ParamAttr(name="bias1", initializer=paddle.nn.initializer.Assign(bias))

out = paddle.static.nn.group_norm(node_x, groups=groups,
epsilon=epsilon,
param_attr=scale_attr,
bias_attr=bias_attr,
data_layout=data_layout)

cpu = paddle.static.cpu_places(1)
exe = paddle.static.Executor(cpu[0])

exe.run(paddle.static.default_startup_program())

outs = exe.run(feed={"x": x}, fetch_list=[out])

saveModel(name, exe, feedkeys=['x'], fetchlist=[out], inputs=[x], outputs=[outs[0]], target_dir=sys.argv[1])

return outs[0]


def main():
# data layout is NCHW
data = np.random.random((2, 4, 3, 4)).astype(np.float32)
groups = 2
epsilon = 1e-05
scale = np.random.random(4).astype(np.float32)
bias = np.random.random(4).astype(np.float32)
group_norm("group_norm_1", data, groups, epsilon, scale, bias, "NCHW")

# data layout is NHWC
data = np.random.random((2, 4, 3, 4)).astype(np.float32)
groups = 2
epsilon = 1e-05
scale = np.random.random(4).astype(np.float32)
bias = np.random.random(4).astype(np.float32)
group_norm("group_norm_2", data, groups, epsilon, scale, bias, "NHWC")

# scale and bias are None
scale = False
bias = False
group_norm("group_norm_3", data, groups, epsilon, scale, bias, "NHWC")


if __name__ == "__main__":
main()
119 changes: 119 additions & 0 deletions src/frontends/paddle/src/op/group_norm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright (C) 2018-2022 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//

#include "default_opset.hpp"
#include "openvino/frontend/paddle/node_context.hpp"
#include "openvino/frontend/paddle/visibility.hpp"

namespace ov {
namespace frontend {
namespace paddle {
namespace op {

Output<ov::Node> reshape_channel_shaped_node_to_nchw(const Output<ov::Node>& node,
const Output<ov::Node>& expected_rank) {
const auto one_const = default_opset::Constant::create(element::i64, Shape{1}, {1});
const auto two_const = default_opset::Constant::create(element::i64, Shape{1}, {2});
const auto tail_shape_rank = std::make_shared<default_opset::Subtract>(expected_rank, two_const);
const auto tail_shape = std::make_shared<default_opset::Broadcast>(one_const, tail_shape_rank);
const auto C_dim = std::make_shared<default_opset::ShapeOf>(node);
const auto new_shape = std::make_shared<default_opset::Concat>(OutputVector{one_const, C_dim, tail_shape}, 0);
return std::make_shared<default_opset::Reshape>(node, new_shape, false);
}

NamedOutputs group_norm(const NodeContext& node) {
auto data = node.get_input("X");
size_t num_groups = static_cast<size_t>(node.get_attribute<int32_t>("groups"));
auto epsilon = node.get_attribute<float>("epsilon", 1e-5);
auto data_layout = node.get_attribute<std::string>("data_layout", "NCHW");

const auto& pshape = data.get_partial_shape();
PADDLE_OP_CHECK(node, pshape.rank().is_static());
size_t rank_size = pshape.rank().get_length();
PADDLE_OP_CHECK(node, rank_size >= 2, "2-D and above tensors supported only");

if (data_layout == "NHWC") {
Copy link
Contributor

Choose a reason for hiding this comment

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

What if don't we have defined layout in the model?

Copy link
Contributor Author

@Patrick-Star125 Patrick-Star125 Sep 6, 2022

Choose a reason for hiding this comment

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

In line 29, the default value of data_layout is "NCHW", which is same as Paddle doc description.

Copy link
Contributor

Choose a reason for hiding this comment

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

@ilyachur Some Paddle operations have this data_layout attribute, while some are not. For NHWC layout, it is okay to use this layout conversion for the op mapper, I think. We could apply a transformation from model perspective to eliminate the conversions between operations later.

auto values = std::vector<size_t>{0, rank_size - 1};
for (size_t i = 1; i < rank_size - 1; i++) {
values.push_back(i);
}
auto perm1 = default_opset::Constant::create(element::i64, Shape{rank_size}, values);
data = std::make_shared<default_opset::Transpose>(data, perm1);
}
// The process below creates a shape to which we need to reshape the input before normalization.
luo-cheng2021 marked this conversation as resolved.
Show resolved Hide resolved
auto num_groups_const = default_opset::Constant::create(element::i64, Shape{1}, {num_groups});
auto data_shape_node = std::make_shared<default_opset::ShapeOf>(data);
auto shape = std::make_shared<default_opset::ShapeOf>(data);
auto axis_node = default_opset::Constant::create(element::i64, Shape{}, {0});
auto split = std::make_shared<default_opset::Split>(shape, axis_node, rank_size);
auto splits = split->outputs();
ov::OutputVector new_shape{std::make_shared<default_opset::Multiply>(splits[0], num_groups_const),
std::make_shared<default_opset::Divide>(splits[1], num_groups_const)};
for (size_t i = 2; i < rank_size; i++) {
new_shape.push_back(splits[i]);
}
// The 4D shape: [N * num_groups, C // num_groups, H, W] is created
// instead of 5D shape: [N, num_groups, C // num_groups, H, W].
// The reason is the lack of support for 5D MVN input by some plugins.
auto reshaped_ = std::make_shared<default_opset::Concat>(new_shape, 0);
auto data_reshaped = std::make_shared<default_opset::Reshape>(data, reshaped_, true);
const Output<ov::Node> data_reshaped_value = data_reshaped;
PADDLE_OP_CHECK(node, data_reshaped_value.get_partial_shape().rank().is_static());
size_t reshape_rank = data_reshaped_value.get_partial_shape().rank().get_length();
std::vector<size_t> range_value;
for (size_t i = 1; i < reshape_rank; i++)
range_value.push_back(i);
const auto reduction_axes = default_opset::Constant::create(element::i64, {range_value.size()}, range_value);

auto mvn = std::make_shared<default_opset::MVN>(data_reshaped,
reduction_axes,
true,
epsilon,
ov::op::MVNEpsMode::INSIDE_SQRT);
std::shared_ptr<ov::Node> result = std::make_shared<default_opset::Reshape>(mvn, data_shape_node, true);
// The process below reshape the result that become standrd output after normalization.
const auto data_rank = std::make_shared<default_opset::ShapeOf>(data_shape_node);
if (node.has_input("Scale")) {
auto scale = node.get_input("Scale");
const auto& scale_shape = scale.get_partial_shape();
PADDLE_OP_CHECK(node, scale_shape.rank().is_static());
auto scale_rank = scale_shape.rank().get_length();
if (scale_rank == 1) {
result =
std::make_shared<default_opset::Multiply>(result,
op::reshape_channel_shaped_node_to_nchw(scale, data_rank));
} else {
result = std::make_shared<default_opset::Multiply>(result, scale);
}
}

if (node.has_input("Bias")) {
auto bias = node.get_input("Bias");
const auto& bias_shape = bias.get_partial_shape();
PADDLE_OP_CHECK(node, bias_shape.rank().is_static());
auto bias_rank = bias_shape.rank().get_length();
if (bias_rank == 1) {
result =
std::make_shared<default_opset::Add>(result, op::reshape_channel_shaped_node_to_nchw(bias, data_rank));
} else {
result = std::make_shared<default_opset::Add>(result, bias);
}
}

if (data_layout == "NHWC") {
auto values = std::vector<size_t>{0};
for (size_t i = 2; i < rank_size; i++) {
values.push_back(i);
}
values.push_back(1);
auto perm2 = default_opset::Constant::create(element::i64, Shape{rank_size}, values);
result = std::make_shared<default_opset::Transpose>(result, perm2);
}

return node.default_single_output_mapping({result}, {"Y"});
}
} // namespace op
} // namespace paddle
} // namespace frontend
} // namespace ov
2 changes: 2 additions & 0 deletions src/frontends/paddle/src/op_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ OP_CONVERTER(floor);
OP_CONVERTER(gather);
OP_CONVERTER(gelu);
OP_CONVERTER(greater_than);
OP_CONVERTER(group_norm);
OP_CONVERTER(hard_sigmoid);
OP_CONVERTER(hard_swish);
OP_CONVERTER(layer_norm);
Expand Down Expand Up @@ -134,6 +135,7 @@ std::map<std::string, CreatorFunction> get_supported_ops() {
{"gelu", op::gelu},
{"greater_equal", op::elementwise_greater_equal},
{"greater_than", op::greater_than},
{"group_norm", op::group_norm},
{"hard_sigmoid", op::hard_sigmoid},
{"hard_swish", op::hard_swish},
{"layer_norm", op::layer_norm},
Expand Down