Commit 0caefe7d authored by Jayaram Bobba's avatar Jayaram Bobba Committed by Robert Kimball

Initial implementation of implicit broadcasting for eltwise ops (#2936)

* Initial implementation of implicit broadcasting for eltwise ops. Only Add supported

* Addressed PR feedback

* cleanup

* Rename Bcast to Broadcast

* Autobroadcasting support for rest of elementwise ops

* Serializer support for autobroadcast

* Added missing autob serialization for Minimum

* Added execution unit tests and more op types to implicit broadcast elimination

* Addressed PR feedback

* Fixes windows build issue

* RVO optimization per PR feedback
parent 4971bdf1
......@@ -354,6 +354,8 @@ set (SRC
pass/algebraic_simplification.cpp
pass/algebraic_simplification.hpp
pass/assign_layout.hpp
pass/implicit_broadcast_elimination.hpp
pass/implicit_broadcast_elimination.cpp
pass/batch_fusion.hpp
pass/batch_fusion.cpp
pass/common_function_collection.cpp
......
......@@ -14,6 +14,7 @@
// limitations under the License.
//*****************************************************************************
#include <algorithm>
#include <iostream>
#include <limits>
#include <sstream>
......@@ -103,3 +104,32 @@ bool Dimension::merge(Dimension& dst, const Dimension d1, const Dimension d2)
return true;
}
}
bool Dimension::broadcast_merge(Dimension& dst, const Dimension d1, const Dimension d2)
{
if (d1.is_dynamic() && d2.is_dynamic())
{
dst = d1;
return true;
}
else if (d1.is_dynamic() || d2.is_dynamic())
{
// One static. Set dst to static size if >1
auto ds = d1.is_dynamic() ? size_t(d2) : size_t(d1);
dst = (ds > 1) ? ds : Dimension::dynamic();
return true;
}
else
{
// Static sizes. Both match or one of them is 1.
if (size_t(d1) == size_t(d2) || size_t(d1) == 1 || size_t(d2) == 1)
{
dst = std::max(size_t(d1), size_t(d2));
return true;
}
else
{
return false;
}
}
}
......@@ -91,6 +91,10 @@ namespace ngraph
/// returns `false`.
static bool merge(Dimension& dst, const Dimension d1, const Dimension d2);
/// \brief Try to merge two Dimension objects together with implicit broadcasting
/// of unit-sized dimension to non unit-sized dimension
static bool broadcast_merge(Dimension& dst, const Dimension d1, const Dimension d2);
/// \brief Check whether this dimension is capable of being merged with the argument
/// dimension.
/// \param d The dimension to compare this dimension with.
......
......@@ -471,7 +471,8 @@ const NodeVector& ngraph::check_single_output_args(const NodeVector& args)
return args;
}
std::tuple<element::Type, PartialShape> Node::validate_and_infer_elementwise_args()
std::tuple<element::Type, PartialShape>
Node::validate_and_infer_elementwise_args(const op::AutoBroadcastSpec& autob)
{
element::Type element_type = get_input_element_type(0);
PartialShape pshape = get_input_partial_shape(0);
......@@ -485,18 +486,32 @@ std::tuple<element::Type, PartialShape> Node::validate_and_infer_elementwise_arg
element::Type::merge(element_type, element_type, get_input_element_type(i)),
"Argument element types are inconsistent.");
NODE_VALIDATION_CHECK(this,
PartialShape::merge_into(pshape, get_input_partial_shape(i)),
"Argument shapes are inconsistent.");
if (autob.m_type == op::AutoBroadcastType::NONE)
{
NODE_VALIDATION_CHECK(this,
PartialShape::merge_into(pshape, get_input_partial_shape(i)),
"Argument shapes are inconsistent.");
}
else if (autob.m_type == op::AutoBroadcastType::NUMPY)
{
NODE_VALIDATION_CHECK(
this,
PartialShape::broadcast_merge_into(pshape, get_input_partial_shape(i), autob),
"Argument shapes are inconsistent.");
}
else
{
NODE_VALIDATION_CHECK(this, false, "Unsupported auto broadcast specification");
}
}
}
return std::make_tuple(element_type, pshape);
}
void Node::validate_and_infer_elementwise_arithmetic()
void Node::validate_and_infer_elementwise_arithmetic(const op::AutoBroadcastSpec& autob)
{
auto args_et_pshape = validate_and_infer_elementwise_args();
auto args_et_pshape = validate_and_infer_elementwise_args(autob);
element::Type& args_et = std::get<0>(args_et_pshape);
PartialShape& args_pshape = std::get<1>(args_et_pshape);
......@@ -509,9 +524,9 @@ void Node::validate_and_infer_elementwise_arithmetic()
set_output_type(0, args_et, args_pshape);
}
void Node::validate_and_infer_elementwise_logical()
void Node::validate_and_infer_elementwise_logical(const op::AutoBroadcastSpec& autob)
{
auto args_et_pshape = validate_and_infer_elementwise_args();
auto args_et_pshape = validate_and_infer_elementwise_args(autob);
element::Type& args_et = std::get<0>(args_et_pshape);
PartialShape& args_pshape = std::get<1>(args_et_pshape);
......
......@@ -35,6 +35,7 @@
#include "ngraph/descriptor/input.hpp"
#include "ngraph/descriptor/output.hpp"
#include "ngraph/descriptor/tensor.hpp"
#include "ngraph/op/util/attr_types.hpp"
#include "ngraph/placement.hpp"
#include "ngraph/strides.hpp"
......@@ -100,9 +101,12 @@ namespace ngraph
// Called in constructors during transition
void constructor_validate_and_infer_types();
std::tuple<element::Type, PartialShape> validate_and_infer_elementwise_args();
void validate_and_infer_elementwise_arithmetic();
void validate_and_infer_elementwise_logical();
std::tuple<element::Type, PartialShape> validate_and_infer_elementwise_args(
const op::AutoBroadcastSpec& autob = op::AutoBroadcastSpec());
void validate_and_infer_elementwise_arithmetic(
const op::AutoBroadcastSpec& autob = op::AutoBroadcastSpec());
void validate_and_infer_elementwise_logical(
const op::AutoBroadcastSpec& autob = op::AutoBroadcastSpec());
Node(const std::string& node_type, const NodeVector& arguments, size_t output_size = 1);
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::Add::Add(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Add", arg0, arg1)
op::Add::Add(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Add", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,11 +30,16 @@ op::Add::Add(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Add::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Add>(new_args.at(0), new_args.at(1));
return make_shared<Add>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Add::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -35,10 +35,13 @@ namespace ngraph
/// `[d0, ...]`
/// \param arg1 Node that produces the second input tensor.<br>
/// `[d0, ...]`
/// \param autob Auto broadcast specification
///
/// Output `[d0, ...]`
///
Add(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
Add(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::And::And(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseLogical("And", arg0, arg1)
op::And::And(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseLogical("And", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::And::And(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::And::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<And>(new_args.at(0), new_args.at(1));
return make_shared<And>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -35,10 +35,13 @@ namespace ngraph
/// `[d0, ...]`
/// \param arg1 Node that produces the second input tensor.<br>
/// `[d0, ...]`
/// \param autob Auto broadcast specification
///
/// Output `[d0, ...]`
///
And(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
And(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -21,8 +21,10 @@
using namespace std;
using namespace ngraph;
op::Divide::Divide(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Divide", arg0, arg1)
op::Divide::Divide(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Divide", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -30,11 +32,16 @@ op::Divide::Divide(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Divide::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Divide>(new_args.at(0), new_args.at(1));
return make_shared<Divide>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Divide::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Divide(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Divide(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::Equal::Equal(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseComparison("Equal", arg0, arg1)
op::Equal::Equal(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseComparison("Equal", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::Equal::Equal(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Equal::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Equal>(new_args.at(0), new_args.at(1));
return make_shared<Equal>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -30,6 +30,7 @@ namespace ngraph
/// | ------ | --------------------------------- | ------------------------------------------------------ |
/// | `arg0` | \f$E[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of any shape and element type. |
/// | `arg1` | \f$E[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of the same shape and element type as `arg0`. |
/// | `autob`| AutoBroadcastSpec | Auto broadcast specification. |
///
/// ## Output
///
......@@ -43,7 +44,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Equal(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Equal(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::Greater::Greater(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseComparison("Greater", arg0, arg1)
op::Greater::Greater(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseComparison("Greater", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::Greater::Greater(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Greater::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Greater>(new_args.at(0), new_args.at(1));
return make_shared<Greater>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Greater(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Greater(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::GreaterEq::GreaterEq(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseComparison("GreaterEq", arg0, arg1)
op::GreaterEq::GreaterEq(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseComparison("GreaterEq", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::GreaterEq::GreaterEq(const shared_ptr<Node>& arg0, const shared_ptr<Node>& a
shared_ptr<Node> op::GreaterEq::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<GreaterEq>(new_args.at(0), new_args.at(1));
return make_shared<GreaterEq>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
GreaterEq(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
GreaterEq(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::Less::Less(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseComparison("Less", arg0, arg1)
op::Less::Less(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseComparison("Less", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::Less::Less(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Less::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Less>(new_args.at(0), new_args.at(1));
return make_shared<Less>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Less(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Less(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::LessEq::LessEq(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseComparison("LessEq", arg0, arg1)
op::LessEq::LessEq(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseComparison("LessEq", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::LessEq::LessEq(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::LessEq::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<LessEq>(new_args.at(0), new_args.at(1));
return make_shared<LessEq>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
LessEq(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
LessEq(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -25,8 +25,10 @@
using namespace std;
using namespace ngraph;
op::Maximum::Maximum(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Maximum", arg0, arg1)
op::Maximum::Maximum(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Maximum", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -34,11 +36,16 @@ op::Maximum::Maximum(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Maximum::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Maximum>(new_args.at(0), new_args.at(1));
return make_shared<Maximum>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Maximum::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Maximum(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Maximum(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -25,8 +25,10 @@
using namespace std;
using namespace ngraph;
op::Minimum::Minimum(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Minimum", arg0, arg1)
op::Minimum::Minimum(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Minimum", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -34,11 +36,16 @@ op::Minimum::Minimum(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Minimum::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Minimum>(new_args.at(0), new_args.at(1));
return make_shared<Minimum>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Minimum::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Minimum(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Minimum(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::Multiply::Multiply(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Multiply", arg0, arg1)
op::Multiply::Multiply(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Multiply", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,11 +30,16 @@ op::Multiply::Multiply(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg
shared_ptr<Node> op::Multiply::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Multiply>(new_args.at(0), new_args.at(1));
return make_shared<Multiply>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Multiply::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Multiply(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Multiply(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::NotEqual::NotEqual(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseComparison("NotEqual", arg0, arg1)
op::NotEqual::NotEqual(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseComparison("NotEqual", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::NotEqual::NotEqual(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg
shared_ptr<Node> op::NotEqual::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<NotEqual>(new_args.at(0), new_args.at(1));
return make_shared<NotEqual>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
NotEqual(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
NotEqual(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -19,8 +19,10 @@
using namespace std;
using namespace ngraph;
op::Or::Or(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseLogical("Or", arg0, arg1)
op::Or::Or(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseLogical("Or", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -28,5 +30,5 @@ op::Or::Or(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Or::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Or>(new_args.at(0), new_args.at(1));
return make_shared<Or>(new_args.at(0), new_args.at(1), this->get_autob());
}
......@@ -35,10 +35,13 @@ namespace ngraph
/// `[d0, ...]`
/// \param arg1 Node that produces the second input tensor.<br>
/// `[d0, ...]`
/// \param autob Auto broadcast specification
///
/// Output `[d0, ...]`
///
Or(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
Or(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -22,8 +22,10 @@
using namespace std;
using namespace ngraph;
op::Power::Power(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Power", arg0, arg1)
op::Power::Power(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Power", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -31,11 +33,16 @@ op::Power::Power(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
shared_ptr<Node> op::Power::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Power>(new_args.at(0), new_args.at(1));
return make_shared<Power>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Power::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -43,7 +43,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Power(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Power(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -20,8 +20,10 @@
using namespace std;
using namespace ngraph;
op::Subtract::Subtract(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg1)
: BinaryElementwiseArithmetic("Subtract", arg0, arg1)
op::Subtract::Subtract(const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: BinaryElementwiseArithmetic("Subtract", arg0, arg1, autob)
{
constructor_validate_and_infer_types();
}
......@@ -29,11 +31,16 @@ op::Subtract::Subtract(const shared_ptr<Node>& arg0, const shared_ptr<Node>& arg
shared_ptr<Node> op::Subtract::copy_with_new_args(const NodeVector& new_args) const
{
check_new_args_count(this, new_args);
return make_shared<Subtract>(new_args.at(0), new_args.at(1));
return make_shared<Subtract>(new_args.at(0), new_args.at(1), this->get_autob());
}
void op::Subtract::generate_adjoints(autodiff::Adjoints& adjoints, const NodeVector& deltas)
{
if (get_autob().m_type != op::AutoBroadcastType::NONE)
{
throw ngraph_error("Autodiff not supported with auto broadcasting");
}
auto delta = deltas.at(0);
auto x = get_argument(0);
......
......@@ -30,7 +30,10 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
Subtract(const std::shared_ptr<Node>& arg0, const std::shared_ptr<Node>& arg1);
/// \param autob Auto broadcast specification
Subtract(const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
virtual std::shared_ptr<Node>
copy_with_new_args(const NodeVector& new_args) const override;
......
......@@ -16,6 +16,8 @@
#pragma once
#include <cstddef>
namespace ngraph
{
namespace op
......@@ -49,5 +51,56 @@ namespace ngraph
AUTO = SAME_UPPER,
NOTSET = EXPLICIT
};
/// \brief Specifies the algorithm to use for implicit broadcasting of a tensor
/// to align with another tensor
///
/// NONE - No implicit broadcasting of tensor
/// NUMPY - Numpy-style implicit broadcasting
/// (https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html)
/// Right-align dimensions of the two tensors, with missing dimensions
/// treated as size 1 dimensions. After alignment, for each dimension,
/// their sizes should either match or one of them should be of size 1.
/// Size 1 dimension will be implicitly broadcast to match the other
/// size.
///
/// E.g.,
/// A: Shape(2, 1, 6)
/// B: Shape( 3, 1)
/// Result: Shape(2, 3, 6)
///
/// A: Shape(2, 1, 6)
/// B: Shape( 3, 1)
/// Result: Shape(2, 3, 6)
///
/// TODO: Add more implicit broadcast modes used by frameworks
enum class AutoBroadcastType
{
NONE = 0,
NUMPY
};
/// \brief Implicit broadcast specification
struct AutoBroadcastSpec
{
AutoBroadcastSpec()
: m_type(AutoBroadcastType::NONE)
, m_axis(0)
{
}
AutoBroadcastSpec(AutoBroadcastType type)
: m_type(type)
, m_axis(0)
{
}
AutoBroadcastSpec(AutoBroadcastType type, size_t axis)
: m_type(type)
, m_axis(axis)
{
}
AutoBroadcastType m_type; // Implicit broadcasting algorithm
size_t m_axis; // Axis to start alignment on
};
}
}
......@@ -22,12 +22,14 @@ using namespace ngraph;
op::util::BinaryElementwiseArithmetic::BinaryElementwiseArithmetic(
const std::string& node_type,
const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1)
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: Op(node_type, check_single_output_args({arg0, arg1}))
, m_autob(autob)
{
}
void op::util::BinaryElementwiseArithmetic::validate_and_infer_types()
{
validate_and_infer_elementwise_arithmetic();
validate_and_infer_elementwise_arithmetic(m_autob);
}
......@@ -17,6 +17,7 @@
#pragma once
#include "ngraph/op/op.hpp"
#include "ngraph/op/util/attr_types.hpp"
namespace ngraph
{
......@@ -25,8 +26,8 @@ namespace ngraph
namespace util
{
/// \brief Abstract base class for elementwise binary arithmetic operations, i.e., operations where the same
/// scalar binary arithmetic operation is applied to each corresponding pair of elements in two same-shaped
/// input tensors.
/// scalar binary arithmetic operation is applied to each corresponding pair of elements in the two
/// input tensors. Implicit broadcast of input tensors is supported through one of the AutoBroadcast modes
///
/// For example, if the underlying arithmetic operation (determined by the subclass) is \f$\mathit{op}(x,y)\f$, the input tensors
/// \f$[[x_0,y_0],[z_0,w_0]]\f$ and \f$[[x_1,y_1],[z_1,w_1]]\f$ will be mapped to \f$[[\mathit{op}(x_0,x_1),\mathit{op}(y_0,y_1)],[\mathit{op}(z_0,z_1),\mathit{op}(w_0,w_1)]]\f$.
......@@ -36,13 +37,14 @@ namespace ngraph
/// | | Type | Description |
/// | ------ | --------------------------------- | ------------------------------------------------------------------------ |
/// | `arg0` | \f$N[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of any shape. The element type \f$N\f$ may be any numeric type. |
/// | `arg1` | \f$N[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of the same shape and element type as `arg0`. |
/// | `arg1` | \f$N[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of the same element type as `arg0`. |
/// | `autob`| AutoBroadcastSpec | Auto broadcast specification. |
///
/// ## Output
///
/// | Type | Description |
/// | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
/// | \f$N[d_1,\dots,d_n]\f$ | The tensor \f$T\f$, where \f$T[i_1,\dots,i_n] = \mathit{op}(\texttt{arg0}[i_1,\dots,i_n],\texttt{arg1}[i_1,\dots,i_n])\f$. This will always have the same shape and element type as the input tensors. |
/// | \f$N[d_1,\dots,d_n]\f$ | The tensor \f$T\f$, where \f$T[i_1,\dots,i_n] = \mathit{op}(\texttt{arg0}[i_1,\dots,i_n],\texttt{arg1}[i_1,\dots,i_n])\f$. This will always have the same shape and element type as the input tensors (after auto broadcasting). |
class BinaryElementwiseArithmetic : public Op
{
public:
......@@ -50,11 +52,17 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
/// \param autob AutoBroadcast mode.
BinaryElementwiseArithmetic(const std::string& node_type,
const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1);
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
void validate_and_infer_types() override;
const AutoBroadcastSpec& get_autob() const { return m_autob; }
private:
AutoBroadcastSpec m_autob;
};
}
}
......
......@@ -21,14 +21,16 @@ using namespace ngraph;
op::util::BinaryElementwiseComparison::BinaryElementwiseComparison(const string& node_type,
const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1)
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: Op(node_type, check_single_output_args({arg0, arg1}))
, m_autob(autob)
{
}
void op::util::BinaryElementwiseComparison::validate_and_infer_types()
{
auto args_et_pshape = validate_and_infer_elementwise_args();
auto args_et_pshape = validate_and_infer_elementwise_args(m_autob);
PartialShape& args_pshape = std::get<1>(args_et_pshape);
set_output_type(0, element::boolean, args_pshape);
......
......@@ -17,6 +17,7 @@
#pragma once
#include "ngraph/op/op.hpp"
#include "ngraph/op/util/attr_types.hpp"
namespace ngraph
{
......@@ -25,8 +26,8 @@ namespace ngraph
namespace util
{
/// \brief Abstract base class for elementwise binary comparison operations, i.e., operations where the same
/// scalar binary comparison operation is applied to each corresponding pair of elements in two same-shaped
/// input tensors.
/// scalar binary comparison operation is applied to each corresponding pair of elements in two
/// input tensors. Implicit broadcast of input tensors is supported through one of the AutoBroadcast modes
///
/// For example, if the underlying comparison operation (determined by the subclass) is \f$\mathit{op}(x,y)\f$, the input tensors
/// \f$[[x_0,y_0],[z_0,w_0]]\f$ and \f$[[x_1,y_1],[z_1,w_1]]\f$ will be mapped to \f$[[\mathit{op}(x_0,x_1),\mathit{op}(y_0,y_1)],[\mathit{op}(z_0,z_1),\mathit{op}(w_0,w_1)]]\f$.
......@@ -37,6 +38,7 @@ namespace ngraph
/// | ------ | --------------------------------- | ------------------------------------------------------ |
/// | `arg0` | \f$E[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of any shape and element type. |
/// | `arg1` | \f$E[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of the same shape and element type as `arg0`. |
/// | `autob`| AutoBroadcastSpec | Auto broadcast specification. |
///
/// ## Output
///
......@@ -50,11 +52,17 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
/// \param autob AutoBroadcast mode.
BinaryElementwiseComparison(const std::string& node_type,
const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1);
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
void validate_and_infer_types() override;
const AutoBroadcastSpec& get_autob() const { return m_autob; }
private:
AutoBroadcastSpec m_autob;
};
}
}
......
......@@ -21,12 +21,14 @@ using namespace ngraph;
op::util::BinaryElementwiseLogical::BinaryElementwiseLogical(const string& node_type,
const shared_ptr<Node>& arg0,
const shared_ptr<Node>& arg1)
const shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob)
: Op(node_type, check_single_output_args({arg0, arg1}))
, m_autob(autob)
{
}
void op::util::BinaryElementwiseLogical::validate_and_infer_types()
{
validate_and_infer_elementwise_logical();
validate_and_infer_elementwise_logical(m_autob);
}
......@@ -25,7 +25,8 @@ namespace ngraph
namespace util
{
/// \brief Abstract base class for elementwise binary logical operations, i.e., operations where the same
/// scalar binary logical operation is applied to each corresponding pair of elements in two same-shaped
/// scalar binary logical operation is applied to each corresponding pair of elements in two
/// boolean input tensors. Implicit broadcast of input tensors is supported through one of the AutoBroadcast modes
/// boolean input tensors.
///
/// For example, if the underlying operation (determined by the subclass) is \f$\mathit{op}(x,y)\f$, the input tensors
......@@ -37,6 +38,7 @@ namespace ngraph
/// | ------ | --------------------------------------------- | ------------------------------------------------------ |
/// | `arg0` | \f$\texttt{bool}[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of any shape, with element type `bool`. |
/// | `arg1` | \f$\texttt{bool}[d_1,\dots,d_n]~(n \geq 0)\f$ | A tensor of the same shape and element type as `arg0`. |
/// | `autob`| AutoBroadcastSpec | Auto broadcast specification. |
///
/// ## Output
///
......@@ -50,11 +52,17 @@ namespace ngraph
///
/// \param arg0 Node that produces the first input tensor.
/// \param arg1 Node that produces the second input tensor.
/// \param autob AutoBroadcast mode.
BinaryElementwiseLogical(const std::string& node_type,
const std::shared_ptr<Node>& arg0,
const std::shared_ptr<Node>& arg1);
const std::shared_ptr<Node>& arg1,
const AutoBroadcastSpec& autob = AutoBroadcastSpec());
void validate_and_infer_types() override;
const AutoBroadcastSpec& get_autob() const { return m_autob; }
private:
AutoBroadcastSpec m_autob;
};
}
}
......
......@@ -18,6 +18,7 @@
#include <iostream>
#include <vector>
#include "ngraph/check.hpp"
#include "ngraph/partial_shape.hpp"
using namespace ngraph;
......@@ -244,3 +245,33 @@ bool PartialShape::merge_into(PartialShape& dst, const PartialShape& src)
return success;
}
}
bool PartialShape::broadcast_merge_into(PartialShape& dst,
const PartialShape& src,
const op::AutoBroadcastSpec& autob)
{
NGRAPH_CHECK(autob.m_type == op::AutoBroadcastType::NUMPY, "Unsupported auto broadcast type");
if (dst.rank().is_dynamic() || src.rank().is_dynamic())
{
dst = PartialShape::dynamic();
return true;
}
else
{
// Ranks are both static.
auto dst_rank = size_t(dst.rank());
auto src_rank = size_t(src.rank());
auto new_rank = std::max(dst_rank, src_rank);
std::vector<Dimension> dims(new_rank);
bool success = true;
for (size_t i = 0; i < new_rank; i++)
{
auto dsti = i < (new_rank - dst_rank) ? Dimension(1) : dst[i - (new_rank - dst_rank)];
auto srci = i < (new_rank - src_rank) ? Dimension(1) : src[i - (new_rank - src_rank)];
success &= Dimension::broadcast_merge(dims[i], dsti, srci);
}
dst = PartialShape(dims);
return success;
}
}
......@@ -19,6 +19,7 @@
#include <stddef.h>
#include "ngraph/dimension.hpp"
#include "ngraph/op/util/attr_types.hpp"
#include "ngraph/rank.hpp"
#include "ngraph/shape.hpp"
......@@ -204,6 +205,11 @@ namespace ngraph
/// unspecified changes to `dst`.
static bool merge_into(PartialShape& dst, const PartialShape& src);
/// \brief Try to merge one shape into another along with implicit broadcasting
static bool broadcast_merge_into(PartialShape& dst,
const PartialShape& src,
const op::AutoBroadcastSpec& autob);
private:
// Private constructor for PartialShape::dynamic().
PartialShape(bool rank_is_static, std::vector<Dimension> dimensions)
......
//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// 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.
//*****************************************************************************
#include "ngraph/pass/implicit_broadcast_elimination.hpp"
#include "ngraph/graph_util.hpp"
#include "ngraph/op/util/binary_elementwise_arithmetic.hpp"
#include "ngraph/op/util/binary_elementwise_comparison.hpp"
#include "ngraph/op/util/binary_elementwise_logical.hpp"
using namespace std;
using namespace ngraph;
template <typename optype>
static bool broadcast_and_replace(std::shared_ptr<ngraph::Node>& node)
{
if (auto op = std::dynamic_pointer_cast<optype>(node))
{
if (op->get_autob().m_type != op::AutoBroadcastType::NONE)
{
auto new_args = pass::explicit_broadcast<optype>(op);
for (size_t i = 0; i < new_args.size(); i++)
{
op->input(i).replace_source_output(new_args[i]->output(0));
}
return true;
};
}
return false;
}
bool ngraph::pass::ImplicitBroadcastElimination::run_on_node(std::shared_ptr<ngraph::Node> node)
{
return broadcast_and_replace<op::util::BinaryElementwiseArithmetic>(node) ||
broadcast_and_replace<op::util::BinaryElementwiseComparison>(node) ||
broadcast_and_replace<op::util::BinaryElementwiseLogical>(node);
}
//*****************************************************************************
// Copyright 2017-2019 Intel Corporation
//
// 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.
//*****************************************************************************
#pragma once
#include "ngraph/op/util/broadcasting.hpp"
#include "ngraph/pass/pass.hpp"
namespace ngraph
{
namespace pass
{
template <typename T>
NodeVector static explicit_broadcast(std::shared_ptr<T>& node)
{
NodeVector rc;
if (node->get_autob().m_type == op::AutoBroadcastType::NONE)
{
rc = node->get_arguments();
}
else if (node->get_autob().m_type == op::AutoBroadcastType::NUMPY)
{
rc = op::numpy_style_broadcast(node->get_arguments());
}
else
{
throw ngraph_error("Unsupported implicit broadcast type");
}
return rc;
}
class ImplicitBroadcastElimination : public NodePass
{
public:
bool run_on_node(std::shared_ptr<ngraph::Node> node) override;
};
}
}
......@@ -136,6 +136,7 @@
#include "ngraph/pass/dump_sorted.hpp"
#include "ngraph/pass/fused_op_decomposition.hpp"
#include "ngraph/pass/get_output_element_elimination.hpp"
#include "ngraph/pass/implicit_broadcast_elimination.hpp"
#include "ngraph/pass/like_replacement.hpp"
#include "ngraph/pass/liveness.hpp"
#include "ngraph/pass/manager.hpp"
......@@ -1181,6 +1182,7 @@ void runtime::cpu::CPU_ExternalFunction::register_common_passes(
REGISTER_KNOBBED_PASS(LikeReplacement, true, ngraph::pass);
REGISTER_KNOBBED_PASS_WITH_ARGS(FusedOpDecomposition, true, ngraph::pass, is_supported);
REGISTER_KNOBBED_PASS(ImplicitBroadcastElimination, true, ngraph::pass);
REGISTER_KNOBBED_PASS(NopElimination, true, ngraph::pass);
REGISTER_KNOBBED_PASS(ZeroDimTensorElimination, true, ngraph::pass);
REGISTER_KNOBBED_PASS(LSTMFusion, true, runtime::cpu::pass);
......
......@@ -35,6 +35,7 @@
#include "ngraph/pass/algebraic_simplification.hpp"
#include "ngraph/pass/fused_op_decomposition.hpp"
#include "ngraph/pass/get_output_element_elimination.hpp"
#include "ngraph/pass/implicit_broadcast_elimination.hpp"
#include "ngraph/pass/like_replacement.hpp"
#include "ngraph/runtime/gpu/gpu_backend.hpp"
......@@ -172,6 +173,7 @@ void runtime::gpu::GPUCompiledFunction::compile()
pass_manager.register_pass<runtime::gpu::pass::BatchNormCache>();
pass_manager.register_pass<ngraph::pass::LikeReplacement>();
pass_manager.register_pass<ngraph::pass::FusedOpDecomposition>();
pass_manager.register_pass<ngraph::pass::ImplicitBroadcastElimination>();
pass_manager.register_pass<runtime::gpu::pass::GPULayout>(this);
pass_manager.register_pass<ngraph::pass::AssignLayout<descriptor::layout::DenseTensorLayout>>();
pass_manager.register_pass<ngraph::pass::GetOutputElementElimination>();
......
......@@ -47,6 +47,7 @@
#include "ngraph/pass/cse.hpp"
#include "ngraph/pass/fused_op_decomposition.hpp"
#include "ngraph/pass/get_output_element_elimination.hpp"
#include "ngraph/pass/implicit_broadcast_elimination.hpp"
#include "ngraph/pass/manager.hpp"
#include "ngraph/pass/nop_elimination.hpp"
#include "ngraph/pass/reshape_elimination.hpp"
......@@ -427,6 +428,7 @@ shared_ptr<runtime::Executable>
{
pass_manager.register_pass<ngraph::pass::FusedOpDecomposition>(
IntelGPUBackend::is_supported_impl);
pass_manager.register_pass<ngraph::pass::ImplicitBroadcastElimination>();
}
if (m_disable_backend_optimizations < 1)
......
......@@ -24,6 +24,7 @@
#include "ngraph/pass/assign_layout.hpp"
#include "ngraph/pass/core_fusion.hpp"
#include "ngraph/pass/fused_op_decomposition.hpp"
#include "ngraph/pass/implicit_broadcast_elimination.hpp"
#include "ngraph/pass/like_replacement.hpp"
#include "ngraph/pass/liveness.hpp"
#include "ngraph/pass/manager.hpp"
......@@ -46,6 +47,7 @@ runtime::interpreter::INTExecutable::INTExecutable(const shared_ptr<Function>& f
pass::Manager pass_manager;
pass_manager.register_pass<pass::LikeReplacement>();
pass_manager.register_pass<pass::FusedOpDecomposition>();
pass_manager.register_pass<pass::ImplicitBroadcastElimination>();
pass_manager.register_pass<pass::AssignLayout<DenseTensorLayout>>();
pass_manager.register_pass<pass::Liveness>();
pass_manager.run_passes(m_function);
......
This diff is collapsed.
......@@ -458,3 +458,68 @@ NGRAPH_TEST(${BACKEND_NAME}, subtract_overload)
handle->call_with_validate({result}, {a, b});
EXPECT_TRUE(test::all_close_f((vector<float>{1, 2, 4, 8}), read_vector<float>(result)));
}
template <typename optype, typename itype, typename otype>
void check_auto_bcast(const std::vector<std::vector<itype>>& inputs,
const std::vector<otype> output)
{
auto iet = element::from<itype>();
auto oet = element::from<otype>();
if (std::is_same<itype, char>::value)
{
iet = element::boolean;
}
if (std::is_same<otype, char>::value)
{
oet = element::boolean;
}
auto A = make_shared<op::Parameter>(iet, Shape{2, 3});
auto B = make_shared<op::Parameter>(iet, Shape{3});
auto f = make_shared<Function>(make_shared<optype>(A, B, op::AutoBroadcastType::NUMPY),
ParameterVector{A, B});
auto backend = runtime::Backend::create("${BACKEND_NAME}");
// Create some tensors for input/output
shared_ptr<runtime::Tensor> a = backend->create_tensor(iet, Shape{2, 3});
shared_ptr<runtime::Tensor> b = backend->create_tensor(iet, Shape{3});
shared_ptr<runtime::Tensor> result = backend->create_tensor(oet, Shape{2, 3});
copy_data(a, inputs[0]);
copy_data(b, inputs[1]);
auto handle = backend->compile(f);
handle->call_with_validate({result}, {a, b});
EXPECT_TRUE(test::all_close(read_vector<otype>(result), output));
}
NGRAPH_TEST(${BACKEND_NAME}, auto_bcast_binary_elementwise)
{
check_auto_bcast<op::Add, float, float>({{1, 2, 3, 4, 5, 6}, {5, 6, 7}}, {6, 8, 10, 9, 11, 13});
check_auto_bcast<op::Subtract, float, float>({{1, 2, 3, 4, 5, 6}, {5, 6, 7}},
{-4.f, -4.f, -4.f, -1.f, -1.f, -1.f});
check_auto_bcast<op::Multiply, float, float>({{1, 2, 3, 4, 5, 6}, {5, 6, 7}},
{5, 12, 21, 20, 30, 42});
check_auto_bcast<op::Divide, float, float>({{4, 5, 6, 7, 8, 9}, {1, 2, 3}},
{4, 2.5f, 2, 7, 4, 3});
check_auto_bcast<op::Maximum, float, float>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}},
{1, 5, 8, 4, 5, 8});
check_auto_bcast<op::Minimum, float, float>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}},
{1, 2, 3, 1, 5, 6});
check_auto_bcast<op::Power, float, float>({{1, 2, 3, 4, 5, 6}, {1, 2, 3}},
{1, 4, 27, 4, 25, 216});
check_auto_bcast<op::And, char, char>({{1, 0, 1, 0, 0, 1}, {1, 0, 1}}, {1, 0, 1, 0, 0, 1});
check_auto_bcast<op::Or, char, char>({{1, 0, 1, 0, 1, 1}, {1, 0, 0}}, {1, 0, 1, 1, 1, 1});
check_auto_bcast<op::Equal, uint8_t, char>({{1, 0, 1, 0, 1, 1}, {1, 0, 0}}, {1, 1, 0, 0, 0, 0});
check_auto_bcast<op::Greater, float, char>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}}, {0, 0, 0, 1, 0, 0});
check_auto_bcast<op::GreaterEq, float, char>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}},
{1, 0, 0, 1, 1, 0});
check_auto_bcast<op::Less, uint8_t, char>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}}, {0, 1, 1, 0, 0, 1});
check_auto_bcast<op::LessEq, uint8_t, char>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}},
{1, 1, 1, 0, 1, 1});
check_auto_bcast<op::NotEqual, uint8_t, char>({{1, 2, 3, 4, 5, 6}, {1, 5, 8}},
{0, 1, 1, 1, 0, 1});
}
......@@ -485,6 +485,47 @@ TEST(partial_shape, partial_shape_merge_both_static_different_rank)
ASSERT_FALSE(PartialShape::merge_into(s1, s2));
}
TEST(partial_shape, partial_shape_broadcast_merge_into_fails)
{
PartialShape s1{2, Dimension::dynamic(), 3, 4};
ASSERT_FALSE(
PartialShape::broadcast_merge_into(s1, PartialShape{3}, op::AutoBroadcastType::NUMPY));
ASSERT_FALSE(
PartialShape::broadcast_merge_into(s1, PartialShape{4, 4}, op::AutoBroadcastType::NUMPY));
ASSERT_FALSE(PartialShape::broadcast_merge_into(
s1, PartialShape{2, 5, 3, 3, 4}, op::AutoBroadcastType::NUMPY));
}
TEST(partial_shape, partial_shape_broadcast_merge_into_dynamic_rank)
{
PartialShape s1{PartialShape::dynamic()};
ASSERT_TRUE(PartialShape::broadcast_merge_into(
s1, PartialShape{3, 2, 4}, op::AutoBroadcastType::NUMPY));
ASSERT_TRUE(s1.same_scheme(PartialShape::dynamic()));
PartialShape s2{2, Dimension::dynamic()};
ASSERT_TRUE(PartialShape::broadcast_merge_into(
s2, PartialShape::dynamic(), op::AutoBroadcastType::NUMPY));
ASSERT_TRUE(s2.same_scheme(PartialShape::dynamic()));
}
TEST(partial_shape, partial_shape_broadcast_merge_into)
{
PartialShape s1{5, Dimension::dynamic(), 3, 4};
const PartialShape s2{3, 4};
ASSERT_TRUE(PartialShape::broadcast_merge_into(s1, s2, op::AutoBroadcastType::NUMPY));
ASSERT_TRUE(s1.same_scheme(PartialShape{5, Dimension::dynamic(), 3, 4}));
PartialShape s3{Dimension::dynamic()};
ASSERT_TRUE(PartialShape::broadcast_merge_into(s3, s2, op::AutoBroadcastType::NUMPY));
ASSERT_TRUE(s3.same_scheme(PartialShape{3, 4}));
PartialShape s4{2, 4, 1, 5};
ASSERT_TRUE(PartialShape::broadcast_merge_into(
s4, PartialShape{2, 1, 3, 5}, op::AutoBroadcastType::NUMPY));
ASSERT_TRUE(s4.same_scheme(PartialShape{2, 4, 3, 5}));
}
TEST(partial_shape, dim_pluseq_left_dynamic)
{
Dimension d1{Dimension::dynamic()};
......
......@@ -2484,6 +2484,41 @@ TEST(type_prop, or_bad_arguments)
});
}
template <typename T>
void test_binary_eltwise_numpy(const element::Type& et, const op::AutoBroadcastSpec& autob)
{
auto param1 = make_shared<op::Parameter>(et, Shape{1, 3, 6});
auto param2 = make_shared<op::Parameter>(et, Shape{3, 1});
auto param3 = make_shared<op::Parameter>(et, Shape{2, 3, 6});
auto param4 = make_shared<op::Parameter>(et, Shape{6});
EXPECT_EQ(make_shared<T>(param1, param2, autob)->get_shape(), (Shape{1, 3, 6}));
EXPECT_EQ(make_shared<T>(param1, param3, autob)->get_shape(), (Shape{2, 3, 6}));
EXPECT_EQ(make_shared<T>(param4, param3, autob)->get_shape(), (Shape{2, 3, 6}));
auto pp1 = make_shared<op::Parameter>(et, PartialShape{1, Dimension::dynamic(), 6});
auto pp2 = make_shared<op::Parameter>(et, PartialShape{3, 1});
EXPECT_EQ(make_shared<T>(pp1, pp2, autob)->get_shape(), (Shape{1, 3, 6}));
}
TEST(type_prop, eltwise_auto_bcast)
{
test_binary_eltwise_numpy<op::Add>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::And>(element::boolean, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Divide>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Equal>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Greater>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::GreaterEq>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Less>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::LessEq>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Maximum>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Minimum>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Multiply>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::NotEqual>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Or>(element::boolean, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Power>(element::f32, op::AutoBroadcastType::NUMPY);
test_binary_eltwise_numpy<op::Subtract>(element::f32, op::AutoBroadcastType::NUMPY);
}
TEST(type_prop, embedding_lookup_non_matrix_weights)
{
auto tv0_2_4_param_0 = make_shared<op::Parameter>(element::boolean, Shape{2, 4});
......
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