Commit edc5d6ba authored by Tomasz Socha's avatar Tomasz Socha Committed by Jayaram Bobba

[SPEC] Add new v1::GroupConvolution and v1::GroupConvolutionBackpropData operators. (#3956)

* [SPEC] Add new v1::GroupConvolution and v1::GroupConvolutionBackpropData ops.

* Sort downgrade passes

* Add downgrade pass fro GroupConvolution

* WIP I

* Fix GroupConvolution validate and infer types

* Add upgrade pass for GroupConvolution

* Remove unnecesary get_static_groups() method

* Disable pass for cases when groups are in filters

* Review Fix I

* Move ops to fused/group_conv.hpp

* Use Op instead of FusedOp again

* Move v1::GroupConvolution and v1::GroupConvolutionBackpropData to FusedOp but temporarily disable decomposition
parent fbbc6ad0
......@@ -18,13 +18,16 @@
#include <memory>
#include <vector>
#include "ngraph/builder/reshape.hpp"
#include "ngraph/frontend/onnx_import/exceptions.hpp"
#include "ngraph/frontend/onnx_import/op/conv.hpp"
#include "ngraph/frontend/onnx_import/utils/convpool.hpp"
#include "ngraph/op/add.hpp"
#include "ngraph/op/broadcast.hpp"
#include "ngraph/op/concat.hpp"
#include "ngraph/op/constant.hpp"
#include "ngraph/op/convolution.hpp"
#include "ngraph/op/fused/group_conv.hpp"
#include "ngraph/op/slice.hpp"
#include "ngraph/op/util/attr_types.hpp"
#include "ngraph/op/util/broadcasting.hpp"
......@@ -51,48 +54,21 @@ namespace ngraph
{
if (groups > 1)
{
// Split one convolution op to N ops where N is the number of groups
// and concat results after computation.
// reference:
// https://github.com/NervanaSystems/ngraph-mxnet/blob/fdd692/src/ngraph/ngraph_emitter.cc#L822-L856
std::size_t n_data_channels{data->get_shape().at(1)};
std::size_t n_filters_channels{filters->get_shape().at(0)};
std::size_t data_group_size{n_data_channels / groups};
std::size_t filters_group_size{n_filters_channels / groups};
NodeVector convolution_nodes;
// initial bounds for splice
std::vector<std::size_t> data_lower_bounds(data->get_shape().size());
std::vector<std::size_t> data_upper_bounds{data->get_shape()};
std::vector<std::size_t> filters_lower_bounds(
filters->get_shape().size());
std::vector<std::size_t> filters_upper_bounds{filters->get_shape()};
for (int64_t group{0}; group < groups; ++group)
{
// slice data
data_lower_bounds[1] = group * data_group_size;
data_upper_bounds[1] = (group + 1) * data_group_size;
auto sliced_data = std::make_shared<ngraph::op::Slice>(
data, data_lower_bounds, data_upper_bounds);
// slice filters
filters_lower_bounds[0] = group * filters_group_size;
filters_upper_bounds[0] = (group + 1) * filters_group_size;
auto sliced_filters = std::make_shared<ngraph::op::Slice>(
filters, filters_lower_bounds, filters_upper_bounds);
convolution_nodes.push_back(
std::make_shared<ngraph::op::v1::Convolution>(sliced_data,
sliced_filters,
strides,
padding_below,
padding_above,
dilations,
auto_pad));
}
std::size_t concatenation_axis = 1;
return std::make_shared<ngraph::op::Concat>(convolution_nodes,
concatenation_axis);
auto filters_shape = filters->get_shape();
filters_shape.at(0) = filters_shape.at(0) / groups;
filters_shape.insert(filters_shape.begin(), groups);
auto reshaped_filters =
ngraph::builder::reshape(filters, filters_shape);
return std::make_shared<ngraph::op::v1::GroupConvolution>(
data,
reshaped_filters,
strides,
padding_below,
padding_above,
dilations,
auto_pad);
}
else
{
......
......@@ -28,17 +28,323 @@
using namespace std;
using namespace ngraph;
constexpr NodeTypeInfo op::GroupConvolution::type_info;
op::GroupConvolution::GroupConvolution(const Output<Node>& data_batch,
const Output<Node>& filters,
const Strides& window_movement_strides,
const Strides& window_dilation_strides,
const CoordinateDiff& padding_below,
const CoordinateDiff& padding_above,
const Strides& data_dilation_strides,
const size_t groups,
const PadType& pad_type)
constexpr NodeTypeInfo op::v1::GroupConvolution::type_info;
shared_ptr<Node> op::v1::GroupConvolution::get_default_value() const
{
return op::Constant::create(get_element_type(), get_shape(), {0});
}
op::v1::GroupConvolution::GroupConvolution(const Output<Node>& data_batch,
const Output<Node>& filters,
const Strides& strides,
const CoordinateDiff& pads_begin,
const CoordinateDiff& pads_end,
const Strides& dilations,
const PadType& auto_pad)
: FusedOp({data_batch, filters})
, m_strides(strides)
, m_dilations(dilations)
, m_pads_begin(pads_begin)
, m_pads_end(pads_end)
, m_auto_pad(auto_pad)
{
constructor_validate_and_infer_types();
}
void op::v1::GroupConvolution::validate_and_infer_types()
{
const PartialShape& data_batch_pshape = get_input_partial_shape(0);
element::Type data_batch_et = get_input_element_type(0);
const PartialShape& filters_pshape = get_input_partial_shape(1);
element::Type filters_et = get_input_element_type(1);
PartialShape result_shape{PartialShape::dynamic()};
// we need to adjust filters_shape to reuse helpers for normal convolution
if (filters_pshape.is_static() && data_batch_pshape.is_static())
{
auto filters_shape = filters_pshape.to_shape();
auto groups = filters_shape[0];
filters_shape[1] *= groups;
filters_shape.erase(filters_shape.begin());
auto data_batch_shape = data_batch_pshape.to_shape();
data_batch_shape[1] /= groups;
if (m_strides.size() == 0)
{
m_strides = conv_default_strides(this, data_batch_shape, filters_shape);
}
if (m_dilations.size() == 0)
{
m_dilations = conv_default_strides(this, data_batch_shape, filters_shape);
}
if (m_pads_begin.size() == 0)
{
m_pads_begin = conv_default_padding(this, data_batch_shape, filters_shape);
}
if (m_pads_end.size() == 0)
{
m_pads_end = conv_default_padding(this, data_batch_shape, filters_shape);
}
if (m_auto_pad == PadType::SAME_UPPER || m_auto_pad == PadType::SAME_LOWER)
{
m_pads_begin.clear();
m_pads_end.clear();
filters_shape.erase(filters_shape.begin(), filters_shape.begin() + 2); // Remove {O,I}
infer_auto_padding(data_batch_shape,
filters_shape,
m_strides,
m_dilations,
m_auto_pad,
m_pads_end,
m_pads_begin);
}
result_shape =
infer_convolution_forward(this,
data_batch_shape,
Strides(m_strides.size(), 1), // dummy data dilations
m_pads_begin,
m_pads_end,
filters_shape,
m_strides,
m_dilations);
}
element::Type result_et;
NODE_VALIDATION_CHECK(
this,
element::Type::merge(result_et, data_batch_et, filters_et),
"Element types for data batch and filters do not match (data batch element type: ",
data_batch_et,
", filters element type: ",
filters_et,
").");
set_output_type(0, result_et, result_shape);
}
shared_ptr<Node> op::v1::GroupConvolution::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<v1::GroupConvolution>(new_args.at(0),
new_args.at(1),
m_strides,
m_pads_begin,
m_pads_end,
m_dilations,
m_auto_pad);
}
void op::v1::GroupConvolution::generate_adjoints(autodiff::Adjoints& adjoints,
const NodeVector& deltas)
{
ngraph_error("Not Yet Implemented");
}
constexpr NodeTypeInfo op::v1::GroupConvolutionBackpropData::type_info;
op::v1::GroupConvolutionBackpropData::GroupConvolutionBackpropData(
const Output<Node>& data,
const Output<Node>& filters,
const Output<Node>& output_shape,
const Strides& strides,
const CoordinateDiff& pads_begin,
const CoordinateDiff& pads_end,
const Strides& dilations,
const PadType& auto_pad,
const CoordinateDiff& output_padding)
: FusedOp({data, filters, output_shape})
, m_strides(strides)
, m_dilations(dilations)
, m_pads_begin(pads_begin)
, m_pads_end(pads_end)
, m_auto_pad(auto_pad)
, m_output_padding(output_padding)
{
constructor_validate_and_infer_types();
}
op::v1::GroupConvolutionBackpropData::GroupConvolutionBackpropData(
const Output<Node>& data,
const Output<Node>& filters,
const Strides& strides,
const CoordinateDiff& pads_begin,
const CoordinateDiff& pads_end,
const Strides& dilations,
const PadType& auto_pad,
const CoordinateDiff& output_padding)
: FusedOp({data, filters})
, m_strides(strides)
, m_dilations(dilations)
, m_pads_begin(pads_begin)
, m_pads_end(pads_end)
, m_auto_pad(auto_pad)
, m_output_padding(output_padding)
{
constructor_validate_and_infer_types();
}
const PartialShape op::v1::GroupConvolutionBackpropData::get_output_shape() const
{
PartialShape shape{PartialShape::dynamic()};
bool is_output_shape_present = get_inputs().size() == 3;
if (is_output_shape_present)
{
if (auto const_op = as_type<op::Constant>(input_value(2).get_node()))
{
shape = const_op->get_shape_val();
}
}
return shape;
}
void op::v1::GroupConvolutionBackpropData::set_output_shape(const Shape& shape)
{
this->input(2).replace_source_output(
op::Constant::create(element::i64, Shape{shape.size()}, shape)->output(0));
}
void op::v1::GroupConvolutionBackpropData::validate_and_infer_types()
{
auto data_pshape = get_input_partial_shape(0);
element::Type delta_et = get_input_element_type(0);
const PartialShape& filters_pshape = get_input_partial_shape(1);
element::Type filters_et = get_input_element_type(1);
bool is_output_shape_present = get_inputs().size() == 3;
PartialShape output_pshape = get_output_shape();
element::Type result_et;
NODE_VALIDATION_CHECK(
this,
element::Type::merge(result_et, delta_et, filters_et),
"Element types for data batch and filters do not match (data batch element type: ",
delta_et,
", filters element type: ",
filters_et,
").");
if (m_auto_pad == PadType::SAME_UPPER || m_auto_pad == PadType::SAME_LOWER)
{
NODE_VALIDATION_CHECK(this,
is_output_shape_present,
"Selected Pad type: ",
m_auto_pad,
"requires an output_shape input which is missing.");
if (output_pshape.is_static() && filters_pshape.is_static())
{
m_pads_begin.clear();
m_pads_end.clear();
auto filter_shape = filters_pshape.to_shape();
filter_shape.erase(filter_shape.begin(), filter_shape.begin() + 3); // Remove {G,O,I}
infer_auto_padding(output_pshape.to_shape(),
filter_shape,
m_strides,
m_dilations,
m_auto_pad,
m_pads_end,
m_pads_begin);
}
}
PartialShape result_shape;
if (is_output_shape_present)
{
set_input_is_relevant_to_shape(2);
if (output_pshape.is_static() && data_pshape.is_static())
{
auto data_shape = data_pshape.to_shape();
auto output_shape = output_pshape.to_shape();
output_shape.insert(output_shape.begin(), data_shape.begin(), data_shape.begin() + 1);
output_pshape = output_shape;
}
}
else
{
if (filters_pshape.is_static() && data_pshape.is_static())
{
auto filters_shape = filters_pshape.to_shape();
filters_shape.erase(filters_shape.begin(),
filters_shape.begin() + 3); // remove {G, O, I}
auto data_shape = data_pshape.to_shape();
data_shape.erase(data_shape.begin(), data_shape.begin() + 2); // remove {N, C}
Shape output_shape;
auto data_spatial_rank = data_shape.size();
auto output_padding = get_output_padding();
if (output_padding.size() == 0)
{
output_padding.insert(output_padding.begin(), data_spatial_rank, 0);
}
for (size_t i = 0; i < data_spatial_rank; ++i)
{
size_t tmp = m_strides[i] * (data_shape[i] - 1) +
((filters_shape[i] - 1) * m_dilations[i] + 1) - m_pads_begin[i] -
m_pads_end[i] + output_padding[i];
output_shape.push_back(tmp);
output_pshape = output_shape;
}
output_shape.insert(output_shape.begin(), data_shape.begin(), data_shape.begin() + 1);
}
}
set_input_is_relevant_to_shape(0);
set_input_is_relevant_to_shape(1);
set_output_type(0, result_et, output_pshape);
}
void op::v1::GroupConvolutionBackpropData::generate_adjoints(autodiff::Adjoints& adjoints,
const NodeVector& deltas)
{
ngraph_error("Not Yet Implemented");
}
shared_ptr<Node>
op::v1::GroupConvolutionBackpropData::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
if (new_args.size() == 3)
{
return make_shared<v1::GroupConvolutionBackpropData>(new_args.at(0),
new_args.at(1),
new_args.at(2),
m_strides,
m_pads_begin,
m_pads_end,
m_dilations,
m_auto_pad,
m_output_padding);
}
else
{
return make_shared<v1::GroupConvolutionBackpropData>(new_args.at(0),
new_args.at(1),
m_strides,
m_pads_begin,
m_pads_end,
m_dilations,
m_auto_pad,
m_output_padding);
}
}
constexpr NodeTypeInfo op::v0::GroupConvolution::type_info;
op::v0::GroupConvolution::GroupConvolution(const Output<Node>& data_batch,
const Output<Node>& filters,
const Strides& window_movement_strides,
const Strides& window_dilation_strides,
const CoordinateDiff& padding_below,
const CoordinateDiff& padding_above,
const Strides& data_dilation_strides,
const size_t groups,
const PadType& pad_type)
: FusedOp({data_batch, filters})
, m_window_movement_strides(window_movement_strides)
, m_window_dilation_strides(window_dilation_strides)
......@@ -47,32 +353,33 @@ op::GroupConvolution::GroupConvolution(const Output<Node>& data_batch,
, m_data_dilation_strides(data_dilation_strides)
, m_groups(groups)
, m_pad_type(pad_type)
, m_groups_in_filters(false)
{
constructor_validate_and_infer_types();
}
op::GroupConvolution::GroupConvolution(const Output<Node>& data_batch,
const Output<Node>& filters,
const Strides& window_movement_strides,
const Strides& window_dilation_strides,
const CoordinateDiff& padding_below,
const CoordinateDiff& padding_above,
const Strides& data_dilation_strides,
const PadType& pad_type)
op::v0::GroupConvolution::GroupConvolution(const Output<Node>& data_batch,
const Output<Node>& filters,
const Strides& window_movement_strides,
const Strides& window_dilation_strides,
const CoordinateDiff& padding_below,
const CoordinateDiff& padding_above,
const Strides& data_dilation_strides,
const PadType& pad_type)
: FusedOp({data_batch, filters})
, m_window_movement_strides(window_movement_strides)
, m_window_dilation_strides(window_dilation_strides)
, m_padding_below(padding_below)
, m_padding_above(padding_above)
, m_data_dilation_strides(data_dilation_strides)
, m_groups(filters.get_partial_shape().rank().is_dynamic() ? Dimension::dynamic()
: filters.get_partial_shape()[0])
, m_groups(0)
, m_pad_type(pad_type)
, m_groups_in_filters(true)
{
constructor_validate_and_infer_types();
}
void op::GroupConvolution::pre_validate_and_infer_types()
void op::v0::GroupConvolution::pre_validate_and_infer_types()
{
auto data_shape = get_input_partial_shape(0);
auto filters_shape = get_input_partial_shape(1);
......@@ -80,7 +387,7 @@ void op::GroupConvolution::pre_validate_and_infer_types()
if (data_shape.is_static() && filters_shape.is_static())
{
// Update groups
if (has_groups_in_filters_shape())
if (m_groups_in_filters)
{
m_groups = static_cast<size_t>(get_input_partial_shape(1)[0]);
}
......@@ -96,7 +403,7 @@ void op::GroupConvolution::pre_validate_and_infer_types()
// Input Filters
NODE_VALIDATION_CHECK(this,
(filters_shape.to_shape()[has_groups_in_filters_shape() ? 2 : 1] *
(filters_shape.to_shape()[m_groups_in_filters ? 2 : 1] *
get_groups()) == data_shape.to_shape()[1],
"Incorrect number of channels per filter");
}
......@@ -106,7 +413,7 @@ void op::GroupConvolution::pre_validate_and_infer_types()
}
}
void op::GroupConvolution::post_validate_and_infer_types()
void op::v0::GroupConvolution::post_validate_and_infer_types()
{
auto data_shape = get_input_partial_shape(0);
auto filters_shape = get_input_partial_shape(1);
......@@ -129,12 +436,12 @@ void op::GroupConvolution::post_validate_and_infer_types()
}
}
Shape op::GroupConvolution::get_weights_dimensions() const
Shape op::v0::GroupConvolution::get_weights_dimensions() const
{
auto data_shape = get_input_shape(0);
auto weights_shape = get_input_shape(1);
// check if weights already includes groups
if (has_groups_in_filters_shape())
if (m_groups_in_filters)
{
return weights_shape;
}
......@@ -152,25 +459,36 @@ Shape op::GroupConvolution::get_weights_dimensions() const
return weights_shape_groups;
}
shared_ptr<Node> op::GroupConvolution::copy_with_new_args(const NodeVector& new_args) const
shared_ptr<Node> op::v0::GroupConvolution::copy_with_new_args(const NodeVector& new_args) const
{
if (new_args.size() != 2)
check_new_args_count(this, new_args);
if (m_groups_in_filters)
{
throw ngraph_error("Incorrect number of new arguments");
return make_shared<op::v0::GroupConvolution>(new_args.at(0),
new_args.at(1),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_data_dilation_strides(),
get_pad_type());
}
else
{
return make_shared<op::v0::GroupConvolution>(new_args.at(0),
new_args.at(1),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_data_dilation_strides(),
get_groups(),
get_pad_type());
}
return make_shared<op::GroupConvolution>(new_args.at(0),
new_args.at(1),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_data_dilation_strides(),
get_groups(),
get_pad_type());
}
NodeVector op::GroupConvolution::decompose_op() const
NodeVector op::v0::GroupConvolution::decompose_op() const
{
auto data = input_value(0);
auto filters = input_value(1);
......@@ -188,7 +506,7 @@ NodeVector op::GroupConvolution::decompose_op() const
for (std::size_t group{0}; group < get_groups(); ++group)
{
auto sliced_filter = sliced_filters[group];
if (has_groups_in_filters_shape())
if (m_groups_in_filters)
{
// Remove group dimmension after slicing
sliced_filter = builder::reshape(
......@@ -209,22 +527,15 @@ NodeVector op::GroupConvolution::decompose_op() const
return {std::make_shared<ngraph::op::Concat>(convolution_nodes, concatenation_axis)};
}
void op::GroupConvolution::generate_adjoints(autodiff::Adjoints& /* adjoints */,
const NodeVector& /* deltas */)
void op::v0::GroupConvolution::generate_adjoints(autodiff::Adjoints& /* adjoints */,
const NodeVector& /* deltas */)
{
throw ngraph_error("NYI");
}
bool ngraph::op::GroupConvolution::has_groups_in_filters_shape() const
{
// If filters_rank is (data_rank + 1), then filters are divided by groups on first
// dim.
return ((get_input_shape(0).size() + 1) == get_input_shape(1).size());
}
constexpr NodeTypeInfo op::GroupConvolutionBackpropData::type_info;
constexpr NodeTypeInfo op::v0::GroupConvolutionBackpropData::type_info;
op::GroupConvolutionBackpropData::GroupConvolutionBackpropData(
op::v0::GroupConvolutionBackpropData::GroupConvolutionBackpropData(
const Output<Node>& data_batch,
const Output<Node>& filters,
const Output<Node>& output_delta,
......@@ -243,7 +554,7 @@ op::GroupConvolutionBackpropData::GroupConvolutionBackpropData(
constructor_validate_and_infer_types();
}
void op::GroupConvolutionBackpropData::pre_validate_and_infer_types()
void op::v0::GroupConvolutionBackpropData::pre_validate_and_infer_types()
{
element::Type data_element_type = get_input_element_type(0);
PartialShape data_pshape = get_input_partial_shape(0);
......@@ -263,24 +574,24 @@ void op::GroupConvolutionBackpropData::pre_validate_and_infer_types()
}
shared_ptr<Node>
op::GroupConvolutionBackpropData::copy_with_new_args(const NodeVector& new_args) const
op::v0::GroupConvolutionBackpropData::copy_with_new_args(const NodeVector& new_args) const
{
if (new_args.size() != 3)
{
throw ngraph_error("Incorrect number of new arguments");
}
return make_shared<op::GroupConvolutionBackpropData>(new_args.at(0),
new_args.at(1),
new_args.at(2),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_groups());
return make_shared<op::v0::GroupConvolutionBackpropData>(new_args.at(0),
new_args.at(1),
new_args.at(2),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_groups());
}
NodeVector op::GroupConvolutionBackpropData::decompose_op() const
NodeVector op::v0::GroupConvolutionBackpropData::decompose_op() const
{
auto data_batch = input_value(0);
auto filters = input_value(1);
......@@ -333,9 +644,9 @@ NodeVector op::GroupConvolutionBackpropData::decompose_op() const
return {std::make_shared<ngraph::op::Concat>(sliced_inputs, concatenation_axis)};
}
constexpr NodeTypeInfo op::GroupConvolutionBackpropFilters::type_info;
constexpr NodeTypeInfo op::v0::GroupConvolutionBackpropFilters::type_info;
op::GroupConvolutionBackpropFilters::GroupConvolutionBackpropFilters(
op::v0::GroupConvolutionBackpropFilters::GroupConvolutionBackpropFilters(
const Output<Node>& data_batch,
const Output<Node>& filters,
const Output<Node>& output_delta,
......@@ -354,7 +665,7 @@ op::GroupConvolutionBackpropFilters::GroupConvolutionBackpropFilters(
constructor_validate_and_infer_types();
}
void op::GroupConvolutionBackpropFilters::pre_validate_and_infer_types()
void op::v0::GroupConvolutionBackpropFilters::pre_validate_and_infer_types()
{
element::Type filters_element_type = get_input_element_type(1);
PartialShape data_pshape = get_input_partial_shape(0);
......@@ -374,24 +685,24 @@ void op::GroupConvolutionBackpropFilters::pre_validate_and_infer_types()
}
shared_ptr<Node>
op::GroupConvolutionBackpropFilters::copy_with_new_args(const NodeVector& new_args) const
op::v0::GroupConvolutionBackpropFilters::copy_with_new_args(const NodeVector& new_args) const
{
if (new_args.size() != 3)
{
throw ngraph_error("Incorrect number of new arguments");
}
return make_shared<op::GroupConvolutionBackpropFilters>(new_args.at(0),
new_args.at(1),
new_args.at(2),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_groups());
return make_shared<op::v0::GroupConvolutionBackpropFilters>(new_args.at(0),
new_args.at(1),
new_args.at(2),
get_window_movement_strides(),
get_window_dilation_strides(),
get_padding_below(),
get_padding_above(),
get_groups());
}
NodeVector op::GroupConvolutionBackpropFilters::decompose_op() const
NodeVector op::v0::GroupConvolutionBackpropFilters::decompose_op() const
{
auto data_batch = input_value(0);
auto filters = input_value(1);
......
......@@ -25,6 +25,175 @@ namespace ngraph
{
namespace op
{
namespace v1
{
/// \brief Batched convolution operation, with optional window dilation and stride.
class NGRAPH_API GroupConvolution : public op::util::FusedOp
{
public:
static constexpr NodeTypeInfo type_info{"GroupConvolution", 1};
const NodeTypeInfo& get_type_info() const override { return type_info; }
/// \brief Constructs a batched convolution operation.
GroupConvolution() = default;
/// \brief Constructs a batched convolution operation.
///
/// \param data_batch The node producing the input data batch tensor.<br>
/// `[N, C_IN, D1, ... Df]`
/// \param filters The node producing the filters tensor.<br>
/// `[C_OUT, C_IN, F1, ... Ff]`
/// \param strides The strides.<br>
/// `[f]`
/// \param dilations The dilations.<br>
/// `[f]`
/// \param pads_begin The beginning of padding shape.<br>
/// `[f]`
/// \param pads_end The end of padding shape.<br>
/// `[f]`
/// \param auto_pad The pad type for automatically computing padding sizes.<br>
/// `[f]`
///
/// Output `[N, C_OUT, R1, ... Rf]`
///
GroupConvolution(const Output<Node>& data_batch,
const Output<Node>& filters,
const Strides& strides,
const CoordinateDiff& pads_begin,
const CoordinateDiff& pads_end,
const Strides& dilations,
const PadType& auto_pad = PadType::EXPLICIT);
// TODO - Remove supports_decompose and validate_and_infer_type once op supports
// decomposition
bool supports_decompose() const override { return false; }
void validate_and_infer_types() override;
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
void generate_adjoints(autodiff::Adjoints& adjoints,
const NodeVector& deltas) override;
/// \return The strides.
const Strides& get_strides() const { return m_strides; }
void set_strides(const Strides& strides) { m_strides = strides; }
/// \return The dilations.
const Strides& get_dilations() const { return m_dilations; }
void set_dilations(const Strides& dilations) { m_dilations = dilations; }
/// \return The padding-below sizes (possibly negative).
const CoordinateDiff& get_pads_begin() const { return m_pads_begin; }
void set_pads_begin(const CoordinateDiff& pads_begin) { m_pads_begin = pads_begin; }
/// \return The padding-above sizes (possibly negative).
const CoordinateDiff& get_pads_end() const { return m_pads_end; }
void set_adding_above(const CoordinateDiff& pads_end) { m_pads_end = pads_end; }
/// \return The pad type for convolution.
const PadType& get_auto_pad() const { return m_auto_pad; }
void set_auto_pad(const PadType& auto_pad) { m_auto_pad = auto_pad; }
/// \return The default value for Convolution.
virtual std::shared_ptr<Node> get_default_value() const override;
protected:
Strides m_strides;
Strides m_dilations;
CoordinateDiff m_pads_begin;
CoordinateDiff m_pads_end;
PadType m_auto_pad;
};
/// \brief Data batch backprop for batched convolution operation.
class NGRAPH_API GroupConvolutionBackpropData : public op::util::FusedOp
{
public:
static constexpr NodeTypeInfo type_info{"GroupConvolutionBackpropData", 1};
const NodeTypeInfo& get_type_info() const override { return type_info; }
/// \brief Constructs a batched-convolution data batch-backprop operation.
GroupConvolutionBackpropData() = default;
// clang-format off
/// \brief Constructs a batched-convolution data batch-backprop operation.
///
/// \param data The node producing data from forward-prop.
/// \param filter The node producing the filter from forward-prop.
/// \param output_shape The shape of the data batch from forward-prop.
/// \param strides The strides from forward-prop.
/// \param pads_begin The padding-below sizes from forward-prop.
/// \param pads_end The padding-above sizes from forward-prop.
/// \param dilations The dilations from forward-prop.
/// \param auto_pad The pad type for automatically computing padding sizes.
/// \param output_padding The output padding adds additional amount of paddings per each spatial axis in the output tensor.
// clang-format on
GroupConvolutionBackpropData(const Output<Node>& data,
const Output<Node>& filter,
const Output<Node>& output_shape,
const Strides& strides,
const CoordinateDiff& pads_begin,
const CoordinateDiff& pads_end,
const Strides& dilations,
const PadType& auto_pad = PadType::EXPLICIT,
const CoordinateDiff& output_padding = {});
// clang-format off
/// \brief Constructs a batched-convolution data batch-backprop operation.
///
/// \param data The node producing data from forward-prop.
/// \param filter The node producing the filter from forward-prop.
/// \param strides The strides from forward-prop.
/// \param pads_begin The padding-below sizes from forward-prop.
/// \param pads_end The padding-above sizes from forward-prop.
/// \param dilations The dilations from forward-prop.
/// \param auto_pad The pad type for automatically computing padding sizes.
/// \param output_padding The output padding adds additional amount of paddings per each spatial axis in the output tensor.
// clang-format on
GroupConvolutionBackpropData(const Output<Node>& data,
const Output<Node>& filter,
const Strides& strides,
const CoordinateDiff& pads_begin,
const CoordinateDiff& pads_end,
const Strides& dilations,
const PadType& auto_pad = PadType::EXPLICIT,
const CoordinateDiff& output_padding = {});
// TODO - Remove supports_decompose and validate_and_infer_type once op supports
// decomposition
bool supports_decompose() const override { return false; }
void validate_and_infer_types() override;
void generate_adjoints(autodiff::Adjoints& adjoints,
const NodeVector& deltas) override;
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
/// \return The data batch shape.
const PartialShape get_output_shape() const;
void set_output_shape(const Shape& output_shape);
/// \return The strides from the forward prop.
const Strides& get_strides() const { return m_strides; }
void set_strides(const Strides& strides) { m_strides = strides; }
/// \return The dilations from the forward prop.
const Strides& get_dilations() const { return m_dilations; }
void set_dilations(const Strides& dilations) { m_dilations = dilations; }
/// \return The padding-below sizes (possibly negative) from the forward prop.
const CoordinateDiff& get_pads_begin() const { return m_pads_begin; }
void set_pads_begin(const CoordinateDiff& pads_begin) { m_pads_begin = pads_begin; }
/// \return The padding-above sizes (possibly negative) from the forward prop.
const CoordinateDiff& get_pads_end() const { return m_pads_end; }
void set_pads_end(const CoordinateDiff& pads_end) { m_pads_end = pads_end; }
/// \return The auto pad.
const PadType& get_auto_pad() const { return m_auto_pad; }
void set_auto_pad(const PadType& auto_pad) { m_auto_pad = auto_pad; }
/// \return The output padding.
const CoordinateDiff& get_output_padding() const { return m_output_padding; }
void set_output_padding(const CoordinateDiff& output_padding)
{
m_output_padding = output_padding;
}
protected:
Strides m_strides;
Strides m_dilations;
CoordinateDiff m_pads_begin;
CoordinateDiff m_pads_end;
PadType m_auto_pad;
CoordinateDiff m_output_padding;
};
} // namespace v1
namespace v0
{
/// \brief Group Convolution
......@@ -80,6 +249,7 @@ namespace ngraph
virtual void generate_adjoints(autodiff::Adjoints& adjoints,
const NodeVector& deltas) override;
bool has_groups_in_filters() const { return m_groups_in_filters; }
protected:
Strides m_window_movement_strides;
Strides m_window_dilation_strides;
......@@ -90,7 +260,7 @@ namespace ngraph
PadType m_pad_type{PadType::NOTSET};
private:
bool has_groups_in_filters_shape() const;
bool m_groups_in_filters;
};
/// \brief Group Convolution data batch backprop
......@@ -181,5 +351,5 @@ namespace ngraph
using v0::GroupConvolution;
using v0::GroupConvolutionBackpropData;
using v0::GroupConvolutionBackpropFilters;
}
}
} // namespace op
} // namespace ngraph
......@@ -115,7 +115,9 @@ NGRAPH_OP(Greater, ngraph::op::v1, 1)
NGRAPH_OP(GreaterEq, ngraph::op::v0, 0)
NGRAPH_OP(GreaterEqual, ngraph::op::v1, 1)
NGRAPH_OP(GroupConvolution, ngraph::op::v0, 0)
NGRAPH_OP(GroupConvolution, ngraph::op::v1, 1)
NGRAPH_OP(GroupConvolutionBackpropData, ngraph::op::v0, 0)
NGRAPH_OP(GroupConvolutionBackpropData, ngraph::op::v1, 1)
NGRAPH_OP(GroupConvolutionBackpropFilters, ngraph::op::v0, 0)
NGRAPH_OP(GroupConvolutionTranspose, ngraph::op::v0, 0)
NGRAPH_OP(HardSigmoid, ngraph::op::v0, 0)
......
......@@ -86,7 +86,8 @@ NGRAPH_OP(Gather, ngraph::op::v1)
NGRAPH_OP(GatherTree, ngraph::op::v1)
NGRAPH_OP(Greater, ngraph::op::v1)
NGRAPH_OP(GreaterEqual, ngraph::op::v1)
NGRAPH_OP(GroupConvolution, ngraph::op::v0)
NGRAPH_OP(GroupConvolution, ngraph::op::v1)
NGRAPH_OP(GroupConvolutionBackpropData, ngraph::op::v1)
NGRAPH_OP(GRN, ngraph::op::v0)
NGRAPH_OP(HardSigmoid, ngraph::op::v0)
NGRAPH_OP(Interpolate, ngraph::op::v0)
......
......@@ -19,6 +19,7 @@
#include <functional>
#include <numeric>
#include "ngraph/builder/reshape.hpp"
#include "ngraph/graph_util.hpp"
#include "ngraph/node.hpp"
#include "ngraph/op/util/broadcasting.hpp"
......@@ -117,12 +118,8 @@ namespace
{
const auto data_arg = node->input_value(0);
const auto filters_arg = node->input_value(1);
const PartialShape& data_arg_pshape = node->get_input_partial_shape(0);
NGRAPH_CHECK(data_arg_pshape.rank().is_static(),
"Unable to convert Convolution:v1 to Convolution:v0 if data argument "
"rank is dynamic. Node: ",
*node);
const size_t num_spatial_dims = static_cast<size_t>(data_arg_pshape.rank()) - 2;
const auto strides = node->get_strides();
const size_t num_spatial_dims = strides.size();
auto replacement_node = make_shared<op::v0::Convolution>(data_arg,
filters_arg,
node->get_strides(),
......@@ -140,16 +137,12 @@ namespace
auto output_shape = as_type_ptr<op::Constant>(node->input_value(2).get_node_shared_ptr());
const auto data_arg = node->input(0).get_source_output();
const auto filters_arg = node->input(1).get_source_output();
const PartialShape& delta_arg_pshape = node->get_input_partial_shape(1);
NGRAPH_CHECK(delta_arg_pshape.rank().is_static(),
"Unable to convert ConvolutionBackpropData:v1 to ConvolutionBackpropData:v0 "
"if delta argument rank is dynamic. Node: ",
*node);
const auto strides = node->get_strides();
NGRAPH_CHECK(output_shape,
"Unable to convert ConvolutionBackpropData:v1 to ConvolutionBackpropData:v0 "
"if output_shape is not constant. Node: ",
*node);
const size_t num_spatial_dims = static_cast<size_t>(delta_arg_pshape.rank()) - 2;
const size_t num_spatial_dims = strides.size();
auto output_padding = node->get_output_padding();
......@@ -182,12 +175,8 @@ namespace
->get_shape_val();
const auto data_arg = node->input_value(0);
const auto delta_arg = node->input_value(1);
const PartialShape& data_arg_pshape = node->get_input_partial_shape(0);
NGRAPH_CHECK(data_arg_pshape.rank().is_static(),
"Unable to convert ConvolutionBackpropFilters:v1 to "
"ConvolutionBackpropFilters:v0 if data argument rank is dynamic. Node: ",
*node);
const size_t num_spatial_dims = static_cast<size_t>(data_arg_pshape.rank()) - 2;
const auto strides = node->get_strides();
const size_t num_spatial_dims = strides.size();
auto replacement_node =
make_shared<op::v0::ConvolutionBackpropFilters>(data_arg,
filters_shape,
......@@ -256,6 +245,24 @@ namespace
return true;
}
bool op_cast(shared_ptr<op::v1::GroupConvolution> node)
{
const auto data_arg = node->input_value(0);
const auto filters_arg = node->input_value(1);
const auto strides = node->get_strides();
const size_t num_spatial_dims = strides.size();
auto replacement_node = make_shared<op::GroupConvolution>(data_arg,
filters_arg,
node->get_strides(),
node->get_dilations(),
node->get_pads_begin(),
node->get_pads_end(),
Strides(num_spatial_dims, 1),
node->get_auto_pad());
replace_node(node, replacement_node);
return true;
}
bool op_cast(shared_ptr<op::v1::Less> node)
{
op_cast_binary_elementwise_node<op::v0::Less, op::v1::Less>(node);
......
......@@ -18,6 +18,7 @@
#include <limits>
#include <numeric>
#include "ngraph/builder/reshape.hpp"
#include "ngraph/graph_util.hpp"
#include "ngraph/ops.hpp"
#include "ngraph/pass/opset1_upgrade.hpp"
......@@ -136,11 +137,9 @@ namespace
auto data_dilation_strides = node->get_data_dilation_strides();
auto auto_pad = node->get_pad_type();
bool is_dds_valid = true;
for (auto value : data_dilation_strides)
{
is_dds_valid = is_dds_valid && (value == 1);
}
bool is_dds_valid = all_of(data_dilation_strides.begin(),
data_dilation_strides.end(),
[](size_t value) { return value == 1; });
NGRAPH_CHECK(is_dds_valid,
"Unable to convert Convolution:0 to Convolution:1 with data dilation strides "
......@@ -167,11 +166,9 @@ namespace
auto pads_end = node->get_padding_above_forward();
auto data_dilation_strides = node->get_data_dilation_strides_forward();
bool is_dds_valid = true;
for (auto value : data_dilation_strides)
{
is_dds_valid = is_dds_valid && (value == 1);
}
bool is_dds_valid = all_of(data_dilation_strides.begin(),
data_dilation_strides.end(),
[](size_t value) { return value == 1; });
NGRAPH_CHECK(is_dds_valid,
"Unable to convert ConvolutionBackpropData:0 to ConvolutionBackpropData:1 "
......@@ -271,6 +268,62 @@ namespace
return true;
}
bool op_cast(shared_ptr<op::v0::GroupConvolution> node)
{
auto strides = node->get_window_movement_strides();
auto dilations = node->get_window_dilation_strides();
auto pads_begin = node->get_padding_below();
auto pads_end = node->get_padding_above();
auto data_dilation_strides = node->get_data_dilation_strides();
auto auto_pad = node->get_pad_type();
bool is_dds_valid = all_of(data_dilation_strides.begin(),
data_dilation_strides.end(),
[](size_t value) { return value == 1; });
NGRAPH_CHECK(is_dds_valid,
"Unable to convert GroupConvolution:0 to GroupConvolution:1"
"with data dilation strides other than `1`. Node: ",
*node);
shared_ptr<Node> replacement_node;
if (node->has_groups_in_filters())
{
replacement_node = make_shared<op::v1::GroupConvolution>(node->input_value(0),
node->input_value(1),
strides,
pads_begin,
pads_end,
dilations,
auto_pad);
}
else
{
NGRAPH_CHECK(node->get_input_partial_shape(1).is_static(),
"Unable to convert GroupConvolution:0 to GroupConvolution:1"
"with dynamic filters shape. Node: ",
*node);
auto filters_shape = node->get_input_shape(1);
auto groups = node->get_groups();
filters_shape[0] /= groups;
filters_shape.insert(filters_shape.begin(), groups);
auto reshaped_filters = builder::reshape(node->input_value(1), filters_shape);
replacement_node =
make_shared<op::v1::GroupConvolution>(node->input(0).get_source_output(),
reshaped_filters,
strides,
pads_begin,
pads_end,
dilations,
auto_pad);
}
replace_node(node, replacement_node);
return true;
}
bool op_cast(shared_ptr<op::Less> node)
{
op_cast_binary_elementwise_node<op::v0::Less, op::v1::Less>(node);
......
......@@ -1766,6 +1766,11 @@ void ngraph::runtime::cpu::pass::CPUFusion::construct_groupconv_batchnorm_global
return false;
}
if (conv_m->has_groups_in_filters())
{
return false;
}
// new weights = old weights * gamma / sqrt(variance + epsilon)
// new biases = (-mean) * gamma / sqrt(variance + epsilon) + beta
......
......@@ -1178,6 +1178,51 @@ shared_ptr<Node> JSONDeserializer::deserialize_node(json node_js)
}
break;
}
case OP_TYPEID::GroupConvolution_v1:
{
auto strides = node_js.at("strides").get<vector<size_t>>();
auto dilations = node_js.at("dilations").get<vector<size_t>>();
auto pads_begin = node_js.at("pads_begin").get<vector<std::ptrdiff_t>>();
auto pads_end = node_js.at("pads_end").get<vector<std::ptrdiff_t>>();
op::PadType auto_pad = read_pad_type(node_js);
node = make_shared<op::v1::GroupConvolution>(
args[0], args[1], strides, pads_begin, pads_end, dilations, auto_pad);
break;
}
case OP_TYPEID::GroupConvolutionBackpropData_v1:
{
auto strides = node_js.at("strides").get<vector<size_t>>();
auto dilations = node_js.at("dilations").get<vector<size_t>>();
auto pads_begin = node_js.at("pads_begin").get<vector<std::ptrdiff_t>>();
auto pads_end = node_js.at("pads_end").get<vector<std::ptrdiff_t>>();
auto output_padding = node_js.at("output_padding").get<vector<std::ptrdiff_t>>();
if (args.size() == 3)
{
node = make_shared<op::v1::GroupConvolutionBackpropData>(args[0],
args[1],
args[2],
strides,
pads_begin,
pads_end,
dilations,
read_pad_type(node_js),
output_padding);
}
else
{
node = make_shared<op::v1::GroupConvolutionBackpropData>(args[0],
args[1],
strides,
pads_begin,
pads_end,
dilations,
read_pad_type(node_js),
output_padding);
}
break;
}
case OP_TYPEID::ConvolutionBackpropFilters:
{
auto filters_shape = node_js.at("filters_shape").get<vector<size_t>>();
......@@ -3296,6 +3341,27 @@ json JSONSerializer::serialize_node(const Node& n)
node["output_padding"] = tmp->get_output_padding();
break;
}
case OP_TYPEID::GroupConvolution_v1:
{
auto tmp = static_cast<const op::v1::GroupConvolution*>(&n);
node["strides"] = tmp->get_strides();
node["dilations"] = tmp->get_dilations();
node["pads_begin"] = tmp->get_pads_begin();
node["pads_end"] = tmp->get_pads_end();
node["auto_pad"] = tmp->get_auto_pad();
break;
}
case OP_TYPEID::GroupConvolutionBackpropData_v1:
{
auto tmp = static_cast<const op::v1::GroupConvolutionBackpropData*>(&n);
node["strides"] = tmp->get_strides();
node["dilations"] = tmp->get_dilations();
node["pads_begin"] = tmp->get_pads_begin();
node["pads_end"] = tmp->get_pads_end();
node["auto_pad"] = tmp->get_auto_pad();
node["output_padding"] = tmp->get_output_padding();
break;
}
case OP_TYPEID::ConvolutionBackpropFilters:
{
auto tmp = static_cast<const op::v0::ConvolutionBackpropFilters*>(&n);
......
......@@ -70,7 +70,8 @@ TEST(opset, check_opset1)
CHECK_OPSET(op::v1::Greater, opset1::Greater)
CHECK_OPSET(op::v1::GreaterEqual, opset1::GreaterEqual)
CHECK_OPSET(op::v0::GRN, opset1::GRN)
CHECK_OPSET(op::v0::GroupConvolution, opset1::GroupConvolution)
CHECK_OPSET(op::v1::GroupConvolution, opset1::GroupConvolution)
CHECK_OPSET(op::v1::GroupConvolutionBackpropData, opset1::GroupConvolutionBackpropData)
// CHECK_OPSET(op::v0::GRUCell, opset1::GRUCell)
// TODO CHECK_OPSET(op::v0::GRUSequence, opset1::GRUSequence)
CHECK_OPSET(op::v0::HardSigmoid, opset1::HardSigmoid)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment