Initial implementation of implicit broadcasting for eltwise ops #2936
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just comments since this is [WIP] but it looks great so far
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also agree on bcast
-> broadcast
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff! In the comments are a couple of cosmetic gripes. A few additional things I think we should do (ideally before merging, but I could be persuaded to take a raincheck):
- Expand support to all binops.
- Add a few execution tests for binops with implicit broadcast. (Okay to disable in non-CPU/INTERPRETER BEs for now.)
Will remove WIP once i expand to all binops. We can run execution tests on other backends as well since they will invoke the ImplicitBroadcastElimination pass |
SGTM. I'd actually meant to backspace the comment about updating other BEs, since it occurred to me as I was typing it that the manifest exists. But it might be worth doing in this PR as well. |
{ | ||
// 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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dunno why, but I genuinely love corner cases like this.
src/ngraph/node.cpp
Outdated
@@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass by reference instead of by value
NUMPY | ||
}; | ||
|
||
struct AutoBroadcastSpec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use some doc strings, also on ctors
src/ngraph/op/util/attr_types.hpp
Outdated
{ | ||
} | ||
|
||
AutoBroadcastType type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure I am the only one who thinks this, but these should be m_type and there should be accessor functions.
enum class AutoBroadcastType | ||
{ | ||
NONE = 0, | ||
NUMPY |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a shortcut to just say that this works like numpy broadcast, but it would be nice to actually know the rules without having to go find numpy's documentation to find out how our code works.
src/ngraph/partial_shape.cpp
Outdated
|
||
bool PartialShape::broadcast_merge_into(PartialShape& dst, | ||
const PartialShape& src, | ||
const op::AutoBroadcastSpec autob) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass by reference
{ | ||
if (node->get_autob().type == op::AutoBroadcastType::NONE) | ||
{ | ||
return node->get_arguments(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by having multiple returns you do not allow the compiler to use Return Value Optimization, forcing it to make a copy when returning NodeVector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used on nodes with two arguments. As such copying might not be too bad. Do you have an alternative in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at the top define NodeVector rc;
instead of returns set rc = <whatever>;
then at the bottom return rc;
One return so RVO works. It is a good general rule to follow to only have one return per function if you are returning something non trivial
src/ngraph/serializer.cpp
Outdated
case OP_TYPEID::Add: | ||
{ | ||
auto tmp = dynamic_cast<const op::Add*>(&n); | ||
node["autob"] = write_auto_broadcast(tmp->get_autob()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be better if you only wrote out autob if it is not the default value.
op::And::And(const shared_ptr<Node>& arg0, | ||
const shared_ptr<Node>& arg1, | ||
const AutoBroadcastSpec& autob) | ||
: BinaryElementwiseLogical("And", arg0, arg1, autob) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to pass this to every constructor ? I assume autob mode should be fixed for all nodes during at least on ngraph invocation. Can't we instead implicitly read it from some configuration class/global ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provide a way to change the default constructor of AutoBroadcastSpec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might make sense to have a global that influences the constructors' behavior for the sake of convenience, but I still think we'll need to store autob
and respect that stored value, for things like deserialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or set the attribute after construction and before validation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, is it possible that nodes may have different auto-broadcast modes ? If not, then makes more sense to keep it as a global.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, "possible" is up to us, right? ;)
Consider, for example, a builder (either one that's part of Core and meant to be reused across bridges, or one that's used internally by the bridge) that wants to use autobroadcast internally. How would that interact with the global?
src/ngraph/op/util/attr_types.hpp
Outdated
} | ||
|
||
AutoBroadcastType type; | ||
size_t axis; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is axis ? Couldn't find a use anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used to indicate the start of alignment, I added a comment.
{ | ||
if (node->get_autob().type == op::AutoBroadcastType::NONE) | ||
{ | ||
return node->get_arguments(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at the top define NodeVector rc;
instead of returns set rc = <whatever>;
then at the bottom return rc;
One return so RVO works. It is a good general rule to follow to only have one return per function if you are returning something non trivial
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like passing that ab all over the place, but this will do until we can work something better out like a graph context or something.
op::And::And(const shared_ptr<Node>& arg0, | ||
const shared_ptr<Node>& arg1, | ||
const AutoBroadcastSpec& autob) | ||
: BinaryElementwiseLogical("And", arg0, arg1, autob) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or set the attribute after construction and before validation.
TODO
Will be supported through reductions along broadcast axes in a future PR