blob: 94bf19bba11655bc3e21e617f90b9b1cab35460f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
/*!
* \file np_elemwise_binary_op.h
* \brief Function definition of elemwise and broadcast operators
*/
#ifndef MXNET_OPERATOR_NUMPY_NP_ELEMWISE_BROADCAST_OP_H_
#define MXNET_OPERATOR_NUMPY_NP_ELEMWISE_BROADCAST_OP_H_
#include <algorithm>
#include <utility>
#include <vector>
#include <string>
#include "../tensor/elemwise_binary_broadcast_op.h"
#include "../tensor/elemwise_binary_scalar_op.h"
namespace mxnet {
namespace op {
inline void PrintErrorMessage(const std::string& op_name, const int dtype1, const int dtype2) {
LOG(FATAL) << "Operator " << op_name << " does not support combination of "
<< mshadow::dtype_string(dtype1) << " with " << mshadow::dtype_string(dtype2)
<< " yet...";
}
template <typename xpu, typename OP>
void MixedAllRealBinaryElemwiseCompute(const std::string& op_name,
const OpContext& ctx,
const TBlob& lhs,
const TBlob& rhs,
const TBlob& out,
const OpReqType req) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(lhs.type_flag_, out.type_flag_);
Stream<xpu>* s = ctx.get_stream<xpu>();
MSHADOW_REAL_TYPE_SWITCH(out.type_flag_, DType, {
const size_t size = (ElemwiseBinaryOp::minthree(out.Size(), lhs.Size(), rhs.Size()) +
DataType<DType>::kLanes - 1) /
DataType<DType>::kLanes;
if (size == 0)
return;
switch (lhs.type_flag_) {
case mshadow::kFloat32: {
if (rhs.type_flag_ == mshadow::kFloat16) {
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<float>(), rhs.dptr<mshadow::half::half_t>(), lhs.dptr<float>());
});
} else if (rhs.type_flag_ == mshadow::kBfloat16) {
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<float>(), rhs.dptr<mshadow::bfloat::bf16_t>(), lhs.dptr<float>());
});
} else {
PrintErrorMessage(op_name, lhs.type_flag_, rhs.type_flag_);
}
break;
}
case mshadow::kFloat64: {
if (rhs.type_flag_ == mshadow::kFloat16) {
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<double>(), rhs.dptr<mshadow::half::half_t>(), lhs.dptr<double>());
});
} else if (rhs.type_flag_ == mshadow::kFloat32) {
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<double>(), rhs.dptr<float>(), lhs.dptr<double>());
});
} else if (rhs.type_flag_ == mshadow::kBfloat16) {
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(s,
size,
out.dptr<double>(),
rhs.dptr<mshadow::bfloat::bf16_t>(),
lhs.dptr<double>());
});
} else {
PrintErrorMessage(op_name, lhs.type_flag_, rhs.type_flag_);
}
break;
}
default: {
PrintErrorMessage(op_name, lhs.type_flag_, rhs.type_flag_);
break;
}
}
});
}
template <typename xpu, typename OP>
void MixedIntRealBinaryElemwiseCompute(const OpContext& ctx,
const TBlob& lhs,
const TBlob& rhs,
const TBlob& out,
const OpReqType req) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(lhs.type_flag_, out.type_flag_);
Stream<xpu>* s = ctx.get_stream<xpu>();
MSHADOW_REAL_TYPE_SWITCH(out.type_flag_, FType, {
const size_t size = (ElemwiseBinaryOp::minthree(out.Size(), lhs.Size(), rhs.Size()) +
DataType<FType>::kLanes - 1) /
DataType<FType>::kLanes;
if (size == 0)
return;
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(rhs.type_flag_, IType, {
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<FType>(), rhs.dptr<IType>(), lhs.dptr<FType>());
});
});
});
}
template <typename xpu, typename OP>
void MixedIntBinaryElemwiseCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const TBlob& lhs,
const TBlob& rhs,
const TBlob& out,
const OpReqType req) {
using namespace mshadow;
using namespace mxnet_op;
Stream<xpu>* s = ctx.get_stream<xpu>();
TBlob temp_tblob;
if (lhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT(lhs.type_flag_, LType, {
Tensor<xpu, 1, LType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, LType>(Shape1(rhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob});
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
MXNET_INT_TYPE_SWITCH_EXT(out.type_flag_, DType, {
const size_t size = (ElemwiseBinaryOp::minthree(out.Size(), lhs.Size(), temp_tblob.Size()) +
DataType<DType>::kLanes - 1) /
DataType<DType>::kLanes;
if (size != 0) {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<DType>(), lhs.dptr<DType>(), temp_tblob.dptr<DType>());
}
});
});
} else if (rhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT(rhs.type_flag_, RType, {
Tensor<xpu, 1, RType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, RType>(Shape1(lhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob});
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
MXNET_INT_TYPE_SWITCH_EXT(out.type_flag_, DType, {
const size_t size = (ElemwiseBinaryOp::minthree(out.Size(), temp_tblob.Size(), rhs.Size()) +
DataType<DType>::kLanes - 1) /
DataType<DType>::kLanes;
if (size != 0) {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<DType>(), temp_tblob.dptr<DType>(), rhs.dptr<DType>());
}
});
});
} else {
TBlob temp_tblob_l;
TBlob temp_tblob_r;
MXNET_INT_TYPE_SWITCH_EXT(out.type_flag_, OType, {
Tensor<xpu, 1, OType> workspace =
ctx.requested[0].get_space_typed<xpu, 1, OType>(Shape1(lhs.Size() + rhs.Size()), s);
TBlob temp_tblob = TBlob(workspace);
temp_tblob_l = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_),
lhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
temp_tblob_r = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_) + lhs.Size() + 1,
rhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob_l});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob_r});
MXNET_ASSIGN_REQ_SWITCH(req, Req, {
MXNET_INT_TYPE_SWITCH_EXT(out.type_flag_, DType, {
const size_t size =
(ElemwiseBinaryOp::minthree(out.Size(), temp_tblob_l.Size(), temp_tblob_r.Size()) +
DataType<DType>::kLanes - 1) /
DataType<DType>::kLanes;
if (size != 0) {
Kernel<mxnet_op::op_with_req<OP, Req>, xpu>::Launch(
s, size, out.dptr<DType>(), temp_tblob_l.dptr<DType>(), temp_tblob_r.dptr<DType>());
}
});
});
}
}
template <typename xpu, typename OP, typename LOP, typename ROP>
void MixedBinaryElemwiseCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
if ((common::is_float(lhs.type_flag_)) && (common::is_float(rhs.type_flag_))) {
if (lhs.type_flag_ == out.type_flag_) {
MixedAllRealBinaryElemwiseCompute<xpu, ROP>(attrs.op->name, ctx, lhs, rhs, out, req[0]);
} else {
MixedAllRealBinaryElemwiseCompute<xpu, LOP>(attrs.op->name, ctx, rhs, lhs, out, req[0]);
}
} else if (common::is_float(lhs.type_flag_) || common::is_float(rhs.type_flag_)) {
if (lhs.type_flag_ == out.type_flag_) {
MixedIntRealBinaryElemwiseCompute<xpu, ROP>(ctx, lhs, rhs, out, req[0]);
} else {
MixedIntRealBinaryElemwiseCompute<xpu, LOP>(ctx, rhs, lhs, out, req[0]);
}
} else {
MixedIntBinaryElemwiseCompute<xpu, OP>(attrs, ctx, lhs, rhs, out, req[0]);
}
}
template <typename xpu, typename OP>
void MixedAllRealBinaryBroadcastCompute(const std::string& op_name,
const OpContext& ctx,
const TBlob& lhs,
const TBlob& rhs,
const TBlob& out,
const OpReqType req,
const int ndim,
const mxnet::TShape& new_oshape,
const mxnet::TShape& new_lshape,
const mxnet::TShape& new_rshape) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(lhs.type_flag_, out.type_flag_);
Stream<xpu>* s = ctx.get_stream<xpu>();
BROADCAST_NDIM_SWITCH(ndim, NDim, {
mshadow::Shape<NDim> oshape = new_oshape.get<NDim>();
mshadow::Shape<NDim> lstride = mxnet_op::calc_stride(new_lshape.get<NDim>());
mshadow::Shape<NDim> rstride = mxnet_op::calc_stride(new_rshape.get<NDim>());
switch (lhs.type_flag_) {
case mshadow::kFloat32: {
if (rhs.type_flag_ == mshadow::kFloat16) {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, OP>, xpu>::template LaunchEx(
s,
new_oshape.Size(),
req,
rstride,
lstride,
oshape,
rhs.dptr<mshadow::half::half_t>(),
lhs.dptr<float>(),
out.dptr<float>());
} else if (rhs.type_flag_ == mshadow::kBfloat16) {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, OP>, xpu>::template LaunchEx(
s,
new_oshape.Size(),
req,
rstride,
lstride,
oshape,
rhs.dptr<mshadow::bfloat::bf16_t>(),
lhs.dptr<float>(),
out.dptr<float>());
} else {
PrintErrorMessage(op_name, lhs.type_flag_, rhs.type_flag_);
}
break;
}
case mshadow::kFloat64: {
if (rhs.type_flag_ == mshadow::kFloat16) {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, OP>, xpu>::template LaunchEx(
s,
new_oshape.Size(),
req,
rstride,
lstride,
oshape,
rhs.dptr<mshadow::half::half_t>(),
lhs.dptr<double>(),
out.dptr<double>());
} else if (rhs.type_flag_ == mshadow::kBfloat16) {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, OP>, xpu>::template LaunchEx(
s,
new_oshape.Size(),
req,
rstride,
lstride,
oshape,
rhs.dptr<mshadow::bfloat::bf16_t>(),
lhs.dptr<double>(),
out.dptr<double>());
} else if (rhs.type_flag_ == mshadow::kFloat32) {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, OP>, xpu>::template LaunchEx(
s,
new_oshape.Size(),
req,
rstride,
lstride,
oshape,
rhs.dptr<float>(),
lhs.dptr<double>(),
out.dptr<double>());
} else {
PrintErrorMessage(op_name, lhs.type_flag_, rhs.type_flag_);
}
break;
}
default: {
PrintErrorMessage(op_name, lhs.type_flag_, rhs.type_flag_);
break;
}
}
});
}
template <typename xpu, typename OP, typename LOP, typename ROP>
void MixedBinaryBroadcastCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
mxnet::TShape new_lshape, new_rshape, new_oshape;
int ndim = BinaryBroadcastShapeCompact(
lhs.shape_, rhs.shape_, out.shape_, &new_lshape, &new_rshape, &new_oshape);
if (!ndim) {
MixedBinaryElemwiseCompute<xpu, OP, LOP, ROP>(attrs, ctx, inputs, req, outputs);
} else {
mshadow::Stream<xpu>* s = ctx.get_stream<xpu>();
if ((common::is_float(lhs.type_flag_)) && (common::is_float(rhs.type_flag_))) {
if (lhs.type_flag_ == out.type_flag_) {
MixedAllRealBinaryBroadcastCompute<xpu, ROP>(
attrs.op->name, ctx, lhs, rhs, out, req[0], ndim, new_oshape, new_lshape, new_rshape);
} else {
MixedAllRealBinaryBroadcastCompute<xpu, LOP>(
attrs.op->name, ctx, rhs, lhs, out, req[0], ndim, new_oshape, new_rshape, new_lshape);
}
} else if (common::is_float(lhs.type_flag_) || common::is_float(rhs.type_flag_)) {
CHECK(lhs.type_flag_ == out.type_flag_ || rhs.type_flag_ == out.type_flag_)
<< "One of the input type should be the same as the output";
BROADCAST_NDIM_SWITCH(ndim, NDim, {
mshadow::Shape<NDim> oshape = new_oshape.get<NDim>();
mshadow::Shape<NDim> lstride = mxnet_op::calc_stride(new_lshape.get<NDim>());
mshadow::Shape<NDim> rstride = mxnet_op::calc_stride(new_rshape.get<NDim>());
if (lhs.type_flag_ == out.type_flag_) {
MSHADOW_REAL_TYPE_SWITCH(out.type_flag_, LType, {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(rhs.type_flag_, RType, {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, ROP>,
xpu>::template LaunchEx(s,
new_oshape.Size(),
req[0],
rstride,
lstride,
oshape,
rhs.dptr<RType>(),
lhs.dptr<LType>(),
out.dptr<LType>());
});
});
} else {
MSHADOW_REAL_TYPE_SWITCH(out.type_flag_, RType, {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(lhs.type_flag_, LType, {
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, LOP>,
xpu>::template LaunchEx(s,
new_oshape.Size(),
req[0],
lstride,
rstride,
oshape,
lhs.dptr<LType>(),
rhs.dptr<RType>(),
out.dptr<RType>());
});
});
}
});
} else if (!common::is_float(lhs.type_flag_) && !common::is_float(rhs.type_flag_)) {
TBlob temp_tblob;
if (lhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(lhs.type_flag_, LType, {
Tensor<xpu, 1, LType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, LType>(Shape1(rhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastCompute<xpu, OP>(
attrs, ctx, {lhs, temp_tblob.reshape(rhs.shape_)}, req, outputs);
} else if (rhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(rhs.type_flag_, RType, {
Tensor<xpu, 1, RType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, RType>(Shape1(lhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastCompute<xpu, OP>(
attrs, ctx, {temp_tblob.reshape(lhs.shape_), rhs}, req, outputs);
} else {
TBlob temp_tblob_l;
TBlob temp_tblob_r;
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(out.type_flag_, OType, {
Tensor<xpu, 1, OType> workspace =
ctx.requested[0].get_space_typed<xpu, 1, OType>(Shape1(lhs.Size() + rhs.Size()), s);
TBlob temp_tblob = TBlob(workspace);
temp_tblob_l = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_),
lhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
temp_tblob_r = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_) + lhs.Size() + 1,
rhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob_l});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob_r});
BinaryBroadcastCompute<xpu, OP>(attrs, ctx, {temp_tblob_l, temp_tblob_r}, req, outputs);
}
} else {
PrintErrorMessage(attrs.op->name, lhs.type_flag_, rhs.type_flag_);
}
}
}
template <typename xpu, typename OP, typename LOP, typename ROP>
void NumpyBinaryBroadcastCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
if ((out.shape_.Size() == 0U) || (req[0] == kNullOp))
return;
if (lhs.type_flag_ == rhs.type_flag_) {
BinaryBroadcastCompute<xpu, OP>(attrs, ctx, inputs, req, outputs);
return;
}
MixedBinaryBroadcastCompute<xpu, OP, LOP, ROP>(attrs, ctx, inputs, req, outputs);
}
template <typename xpu, typename OP, typename LOP, typename ROP>
void NumpyBinaryBroadcastComputeWithBool(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
if ((out.shape_.Size() == 0U) || (req[0] == kNullOp))
return;
if (lhs.type_flag_ == rhs.type_flag_) {
BinaryBroadcastComputeWithBool<xpu, OP>(attrs, ctx, inputs, req, outputs);
return;
}
if (!common::is_float(lhs.type_flag_) && !common::is_float(rhs.type_flag_)) {
Stream<xpu>* s = ctx.get_stream<xpu>();
TBlob temp_tblob;
if (lhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(lhs.type_flag_, LType, {
Tensor<xpu, 1, LType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, LType>(Shape1(rhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastCompute<xpu, OP>(
attrs, ctx, {lhs, temp_tblob.reshape(rhs.shape_)}, req, outputs);
} else if (rhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(rhs.type_flag_, RType, {
Tensor<xpu, 1, RType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, RType>(Shape1(lhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastCompute<xpu, OP>(
attrs, ctx, {temp_tblob.reshape(lhs.shape_), rhs}, req, outputs);
} else {
TBlob temp_tblob_l;
TBlob temp_tblob_r;
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(out.type_flag_, OType, {
Tensor<xpu, 1, OType> workspace =
ctx.requested[0].get_space_typed<xpu, 1, OType>(Shape1(lhs.Size() + rhs.Size()), s);
TBlob temp_tblob = TBlob(workspace);
temp_tblob_l = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_),
lhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
temp_tblob_r = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_) + lhs.Size() + 1,
rhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob_l});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob_r});
BinaryBroadcastCompute<xpu, OP>(attrs, ctx, {temp_tblob_l, temp_tblob_r}, req, outputs);
}
return;
}
MixedBinaryBroadcastCompute<xpu, OP, LOP, ROP>(attrs, ctx, inputs, req, outputs);
}
template <typename xpu, typename OP>
void NumpyBinaryBroadcastIntComputeWithBool(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
if ((out.shape_.Size() == 0U) || (req[0] == kNullOp))
return;
if (lhs.type_flag_ == rhs.type_flag_) {
BinaryBroadcastIntComputeWithBool<xpu, OP>(attrs, ctx, inputs, req, outputs);
return;
}
Stream<xpu>* s = ctx.get_stream<xpu>();
TBlob temp_tblob;
if (lhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(lhs.type_flag_, LType, {
Tensor<xpu, 1, LType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, LType>(Shape1(rhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastIntComputeWithBool<xpu, OP>(
attrs, ctx, {lhs, temp_tblob.reshape(rhs.shape_)}, req, outputs);
} else if (rhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(rhs.type_flag_, RType, {
Tensor<xpu, 1, RType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, RType>(Shape1(lhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastIntComputeWithBool<xpu, OP>(
attrs, ctx, {temp_tblob.reshape(lhs.shape_), rhs}, req, outputs);
} else {
TBlob temp_tblob_l;
TBlob temp_tblob_r;
MXNET_INT_TYPE_SWITCH_EXT_WITH_BOOL(out.type_flag_, OType, {
Tensor<xpu, 1, OType> workspace =
ctx.requested[0].get_space_typed<xpu, 1, OType>(Shape1(lhs.Size() + rhs.Size()), s);
TBlob temp_tblob = TBlob(workspace);
temp_tblob_l = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_),
lhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
temp_tblob_r = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_) + lhs.Size() + 1,
rhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob_l});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob_r});
BinaryBroadcastIntComputeWithBool<xpu, OP>(
attrs, ctx, {temp_tblob_l, temp_tblob_r}, req, outputs);
}
return;
}
template <typename xpu, typename OP>
void NumpyBinaryBroadcastIntCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
if ((out.shape_.Size() == 0U) || (req[0] == kNullOp))
return;
if (lhs.type_flag_ == rhs.type_flag_) {
BinaryBroadcastIntCompute<xpu, OP>(attrs, ctx, inputs, req, outputs);
return;
}
Stream<xpu>* s = ctx.get_stream<xpu>();
TBlob temp_tblob;
if (lhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT(lhs.type_flag_, LType, {
Tensor<xpu, 1, LType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, LType>(Shape1(rhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastIntCompute<xpu, OP>(
attrs, ctx, {lhs, temp_tblob.reshape(rhs.shape_)}, req, outputs);
} else if (rhs.type_flag_ == out.type_flag_) {
MXNET_INT_TYPE_SWITCH_EXT(rhs.type_flag_, RType, {
Tensor<xpu, 1, RType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, RType>(Shape1(lhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastIntCompute<xpu, OP>(
attrs, ctx, {temp_tblob.reshape(lhs.shape_), rhs}, req, outputs);
} else {
TBlob temp_tblob_l;
TBlob temp_tblob_r;
MXNET_INT_TYPE_SWITCH_EXT(out.type_flag_, OType, {
Tensor<xpu, 1, OType> workspace =
ctx.requested[0].get_space_typed<xpu, 1, OType>(Shape1(lhs.Size() + rhs.Size()), s);
TBlob temp_tblob = TBlob(workspace);
temp_tblob_l = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_),
lhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
temp_tblob_r = TBlob(reinterpret_cast<OType*>(temp_tblob.dptr_) + lhs.Size() + 1,
rhs.shape_,
temp_tblob.dev_mask(),
temp_tblob.dev_id());
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob_l});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob_r});
BinaryBroadcastIntCompute<xpu, OP>(attrs, ctx, {temp_tblob_l, temp_tblob_r}, req, outputs);
}
return;
}
inline bool NumpyBinaryMixedFloatingType(const nnvm::NodeAttrs& attrs,
std::vector<int>* in_attrs,
std::vector<int>* out_attrs) {
CHECK_EQ(in_attrs->size(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
const int ltype = in_attrs->at(0);
const int rtype = in_attrs->at(1);
if (ltype != -1 && rtype != -1 && (ltype != rtype)) {
// Only when both input types are known and not the same, we enter the mixed-precision mode
TYPE_ASSIGN_CHECK(*out_attrs, 0, common::type_promotion(ltype, rtype));
} else {
TYPE_ASSIGN_CHECK(*out_attrs, 0, in_attrs->at(0));
TYPE_ASSIGN_CHECK(*out_attrs, 0, in_attrs->at(1));
TYPE_ASSIGN_CHECK(*in_attrs, 0, out_attrs->at(0));
TYPE_ASSIGN_CHECK(*in_attrs, 1, out_attrs->at(0));
}
// check if it is float16, float32 or float64. If not, raise error.
CHECK(common::is_float(in_attrs->at(0))) << "Do not support `int` as input.\n";
return out_attrs->at(0) != -1;
}
template <typename xpu, typename OP>
void NumpyBinaryMixedFloatingCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& lhs = inputs[0];
const TBlob& rhs = inputs[1];
const TBlob& out = outputs[0];
if ((out.shape_.Size() == 0U) || (req[0] == kNullOp))
return;
if (lhs.type_flag_ == rhs.type_flag_) {
BinaryBroadcastCompute<xpu, OP>(attrs, ctx, inputs, req, outputs);
return;
}
Stream<xpu>* s = ctx.get_stream<xpu>();
TBlob temp_tblob;
if (lhs.type_flag_ == out.type_flag_) {
MSHADOW_REAL_TYPE_SWITCH(lhs.type_flag_, LType, {
Tensor<xpu, 1, LType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, LType>(Shape1(rhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {rhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastCompute<xpu, OP>(
attrs, ctx, {lhs, temp_tblob.reshape(rhs.shape_)}, req, outputs);
} else {
MSHADOW_REAL_TYPE_SWITCH(rhs.type_flag_, RType, {
Tensor<xpu, 1, RType> temp_tensor =
ctx.requested[0].get_space_typed<xpu, 1, RType>(Shape1(lhs.Size()), s);
temp_tblob = TBlob(temp_tensor);
});
CastCompute<xpu>(attrs, ctx, {lhs}, {kWriteTo}, {temp_tblob});
BinaryBroadcastCompute<xpu, OP>(
attrs, ctx, {temp_tblob.reshape(lhs.shape_), rhs}, req, outputs);
}
return;
}
template <typename xpu, typename LOP, typename ROP>
void NumpyBinaryBackwardUseIn(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace mshadow;
using namespace mxnet_op;
CHECK_EQ(inputs.size(), 3U);
CHECK_EQ(outputs.size(), 2U);
const TBlob& lhs = inputs[1];
const TBlob& rhs = inputs[2];
if (lhs.type_flag_ == rhs.type_flag_) {
BinaryBroadcastBackwardUseIn<xpu, LOP, ROP>(attrs, ctx, inputs, req, outputs);
return;
}
const TBlob& ograd = inputs[0];
const TBlob& lgrad = outputs[0];
const TBlob& rgrad = outputs[1];
if (common::is_float(lhs.type_flag_) || common::is_float(rhs.type_flag_)) {
// If any of the inputs is a float, it's the same type as the output
// So 2 of the 3 tensors have the same data type
Stream<xpu>* s = ctx.get_stream<xpu>();
mxnet::TShape new_lshape, new_rshape, new_oshape;
using namespace broadcast;
const bool need_bc =
BinaryBroadcastShapeCompact(
lgrad.shape_, rgrad.shape_, ograd.shape_, &new_lshape, &new_rshape, &new_oshape) != 0;
// Prepare all the temporary memory
size_t workspace_size_l = 0, workspace_size_r = 0;
TBlob temp_tblob; // The TBlob for casted input data
TBlob temp_igrad; // The TBlob for casted grad results
size_t tensor_size = (lgrad.type_flag_ != ograd.type_flag_) ? lgrad.Size() : rgrad.Size();
Tensor<xpu, 1, char> workspace;
MSHADOW_TYPE_SWITCH(ograd.type_flag_, OType, {
if (need_bc) {
workspace_size_l =
ReduceWorkspaceSize(s, new_lshape, req[0], new_oshape, new_lshape, new_rshape);
workspace_size_r =
ReduceWorkspaceSize(s, new_rshape, req[1], new_oshape, new_lshape, new_rshape);
}
size_t workspace_size = std::max(workspace_size_l, workspace_size_r);
size_t cast_tensor_size = tensor_size * sizeof(OType);
// Allocate the temporary memories now
Tensor<xpu, 1, char> temp_space = ctx.requested[0].get_space_typed<xpu, 1, char>(
Shape1(workspace_size + cast_tensor_size * 2), s);
// Tensor for temp_tblob
Tensor<xpu, 1, OType> temp_tblob_tensor(
reinterpret_cast<OType*>(temp_space.dptr_), Shape1(tensor_size), s);
// Tensor for temp_igrad
Tensor<xpu, 1, OType> temp_igrad_tensor(
reinterpret_cast<OType*>(temp_space.dptr_) + tensor_size, Shape1(tensor_size), s);
temp_tblob = TBlob(temp_tblob_tensor)
.reshape(((lgrad.type_flag_ != ograd.type_flag_) ? lhs.shape_ : rhs.shape_));
temp_igrad = TBlob(temp_igrad_tensor)
.reshape(((lgrad.type_flag_ != ograd.type_flag_) ? lhs.shape_ : rhs.shape_));
if (temp_igrad.Size() != 0) {
Kernel<set_zero, xpu>::Launch(s, temp_igrad.Size(), temp_igrad.dptr<OType>());
}
workspace =
Tensor<xpu, 1, char>(temp_space.dptr_ + 2 * cast_tensor_size, Shape1(workspace_size), s);
});
// Cast the input that does not have consistent type to temp_tblob
CastCompute<xpu>(attrs,
ctx,
{((lgrad.type_flag_ != ograd.type_flag_) ? lhs : rhs)},
{kWriteTo},
{temp_tblob});
if (!need_bc) {
if (lhs.type_flag_ != ograd.type_flag_) {
ElemwiseBinaryOp::BackwardUseIn<xpu, LOP, ROP>(
attrs, ctx, {ograd, temp_tblob, rhs}, {kWriteTo, req[1]}, {temp_igrad, rgrad});
} else {
ElemwiseBinaryOp::BackwardUseIn<xpu, LOP, ROP>(
attrs, ctx, {ograd, lhs, temp_tblob}, {req[0], kWriteTo}, {lgrad, temp_igrad});
}
} else {
if (lhs.type_flag_ != ograd.type_flag_) {
MSHADOW_TYPE_SWITCH(ograd.type_flag_, DType, {
BROADCAST_NDIM_SWITCH(new_oshape.ndim(), NDim, {
BinaryBroadcastBackwardUseInImplWithWorkspace<xpu, NDim, DType, LOP, ROP>(
ctx,
{ograd, temp_tblob, rhs},
{kWriteTo, req[1]},
{temp_igrad, rgrad},
workspace,
new_lshape,
new_rshape,
new_oshape);
});
});
} else {
MSHADOW_TYPE_SWITCH(ograd.type_flag_, DType, {
BROADCAST_NDIM_SWITCH(new_oshape.ndim(), NDim, {
BinaryBroadcastBackwardUseInImplWithWorkspace<xpu, NDim, DType, LOP, ROP>(
ctx,
{ograd, lhs, temp_tblob},
{req[0], kWriteTo},
{lgrad, temp_igrad},
workspace,
new_lshape,
new_rshape,
new_oshape);
});
});
}
}
// If both inputs are floating numbers, cast the igrad to the input that has
// the different data type
if (common::is_float(lhs.type_flag_) && common::is_float(rhs.type_flag_)) {
if (lhs.type_flag_ != ograd.type_flag_) {
CastCompute<xpu>(attrs, ctx, {temp_igrad}, {req[0]}, {lgrad});
} else {
CastCompute<xpu>(attrs, ctx, {temp_igrad}, {req[1]}, {rgrad});
}
}
} else {
// Case where both inputs are integer types, should not even do
// backward computation for this case.
PrintErrorMessage(attrs.op->name, lhs.type_flag_, rhs.type_flag_);
}
}
#if MXNET_USE_ONEDNN == 1
inline bool NumpyBinaryBroadcastStorageType(const nnvm::NodeAttrs& attrs,
const int dev_mask,
DispatchMode* dispatch_mode,
std::vector<int>* in_attrs,
std::vector<int>* out_attrs) {
CHECK_EQ(in_attrs->size(), 2);
CHECK_EQ(out_attrs->size(), 1);
return DNNLStorageType(attrs, dev_mask, true, dispatch_mode, in_attrs, out_attrs);
}
void NumpyDivideBroadcastComputeCPU(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs);
template <typename OP>
void NumpyBinaryOperatorComputeExCPU(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<mxnet::NDArray>& inputs,
const std::vector<OpReqType>& req,
const std::vector<mxnet::NDArray>& outputs) {
if (SupportDNNLBinary(inputs, outputs)) {
const dnnl::algorithm alg = DNNLAlgorithm<OP>::value;
DNNLRun(DNNLBinaryOpForward<alg>, attrs, ctx, inputs, req, outputs);
return;
}
using namespace op::mshadow_op;
std::vector<mxnet::TBlob> in_data = {inputs[0].data(), inputs[1].data()};
std::vector<mxnet::TBlob> out_data = {outputs[0].data()};
if (std::is_same<OP, plus>::value) {
NumpyBinaryBroadcastComputeWithBool<cpu, OP, mixed_plus, mixed_plus>(
attrs, ctx, in_data, req, out_data);
} else if (std::is_same<OP, minus>::value) {
NumpyBinaryBroadcastCompute<cpu, OP, mixed_minus, mixed_rminus>(
attrs, ctx, in_data, req, out_data);
} else if (std::is_same<OP, mul>::value) {
NumpyBinaryBroadcastComputeWithBool<cpu, OP, mixed_mul, mixed_mul>(
attrs, ctx, in_data, req, out_data);
} else if (std::is_same<OP, div>::value) {
NumpyDivideBroadcastComputeCPU(attrs, ctx, in_data, req, out_data);
}
}
#endif // MXNET_USE_ONEDNN
#define MXNET_OPERATOR_REGISTER_NP_BINARY_SCALAR(name) \
NNVM_REGISTER_OP(name) \
.set_num_inputs(1) \
.set_num_outputs(1) \
.set_attr_parser(ParamParser<NumpyBinaryScalarParam>) \
.set_attr<mxnet::FInferShape>("FInferShape", ElemwiseShape<1, 1>) \
.set_attr<nnvm::FInferType>("FInferType", NumpyBinaryScalarType) \
.set_attr<FResourceRequest>( \
"FResourceRequest", \
[](const NodeAttrs& attrs) { \
return std::vector<ResourceRequest>{ResourceRequest::kTempSpace}; \
}) \
.add_argument("data", "NDArray-or-Symbol", "source input") \
.add_arguments(NumpyBinaryScalarParam::__FIELDS__())
inline bool NumpyBinaryMixedPrecisionType(const nnvm::NodeAttrs& attrs,
std::vector<int>* in_attrs,
std::vector<int>* out_attrs) {
CHECK_EQ(in_attrs->size(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
const int ltype = in_attrs->at(0);
const int rtype = in_attrs->at(1);
if (ltype != -1 && rtype != -1 && (ltype != rtype)) {
// Only when both input types are known and not the same, we enter the mixed-precision mode
TYPE_ASSIGN_CHECK(*out_attrs, 0, common::type_promotion(ltype, rtype));
} else {
return ElemwiseType<2, 1>(attrs, in_attrs, out_attrs);
}
return true;
}
#define MXNET_OPERATOR_REGISTER_NP_BINARY_MIXED_PRECISION(name) \
NNVM_REGISTER_OP(name) \
.set_num_inputs(2) \
.set_num_outputs(1) \
.set_attr<nnvm::FListInputNames>("FListInputNames", \
[](const NodeAttrs& attrs) { \
return std::vector<std::string>{"lhs", "rhs"}; \
}) \
.set_attr<nnvm::FListOutputNames>( \
"FListOutputNames", \
[](const NodeAttrs& attrs) { return std::vector<std::string>{"output"}; }) \
.set_attr<mxnet::FInferShape>("FInferShape", BinaryBroadcastShape) \
.set_attr<nnvm::FInferType>("FInferType", NumpyBinaryMixedPrecisionType) \
.set_attr<nnvm::FInplaceOption>("FInplaceOption", \
[](const NodeAttrs& attrs) { \
return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}}; \
}) \
.set_attr<FResourceRequest>( \
"FResourceRequest", \
[](const NodeAttrs& attrs) { \
return std::vector<ResourceRequest>{ResourceRequest::kTempSpace}; \
}) \
.add_argument("lhs", "NDArray-or-Symbol", "First input to the function") \
.add_argument("rhs", "NDArray-or-Symbol", "Second input to the function")
inline bool NumpyBinaryMixedIntPrecisionTypeWithBool(const nnvm::NodeAttrs& attrs,
std::vector<int>* in_attrs,
std::vector<int>* out_attrs) {
CHECK_EQ(in_attrs->size(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
const int ltype = in_attrs->at(0);
const int rtype = in_attrs->at(1);
CHECK(common::is_int(ltype) || ltype == mshadow::kBool)
<< "1st input only supports integer types or bool types.";
CHECK(common::is_int(rtype) || rtype == mshadow::kBool)
<< "2nd input only supports integer types or bool types.";
if (ltype != -1 && rtype != -1 && (ltype != rtype)) {
// Only when both input types are known and not the same, we enter the mixed-precision mode
TYPE_ASSIGN_CHECK(*out_attrs, 0, common::type_promotion(ltype, rtype));
} else {
return ElemwiseType<2, 1>(attrs, in_attrs, out_attrs);
}
return true;
}
#define MXNET_OPERATOR_REGISTER_NP_BINARY_MIXED_INT_PRECISION_WITH_BOOL(name) \
NNVM_REGISTER_OP(name) \
.set_num_inputs(2) \
.set_num_outputs(1) \
.set_attr<nnvm::FListInputNames>("FListInputNames", \
[](const NodeAttrs& attrs) { \
return std::vector<std::string>{"lhs", "rhs"}; \
}) \
.set_attr<mxnet::FInferShape>("FInferShape", BinaryBroadcastShape) \
.set_attr<nnvm::FInferType>("FInferType", NumpyBinaryMixedIntPrecisionTypeWithBool) \
.set_attr<nnvm::FInplaceOption>("FInplaceOption", \
[](const NodeAttrs& attrs) { \
return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}}; \
}) \
.set_attr<FResourceRequest>( \
"FResourceRequest", \
[](const NodeAttrs& attrs) { \
return std::vector<ResourceRequest>{ResourceRequest::kTempSpace}; \
}) \
.add_argument("lhs", "NDArray-or-Symbol", "First input to the function") \
.add_argument("rhs", "NDArray-or-Symbol", "Second input to the function")
inline bool NumpyBinaryMixedIntPrecisionType(const nnvm::NodeAttrs& attrs,
std::vector<int>* in_attrs,
std::vector<int>* out_attrs) {
CHECK_EQ(in_attrs->size(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
const int ltype = in_attrs->at(0);
const int rtype = in_attrs->at(1);
CHECK(common::is_int(ltype)) << "1st input only supports integer types.";
CHECK(common::is_int(rtype)) << "2nd input only supports integer types.";
if (ltype != -1 && rtype != -1 && (ltype != rtype)) {
// Only when both input types are known and not the same, we enter the mixed-precision mode
TYPE_ASSIGN_CHECK(*out_attrs, 0, common::type_promotion(ltype, rtype));
} else {
return ElemwiseType<2, 1>(attrs, in_attrs, out_attrs);
}
return true;
}
#define MXNET_OPERATOR_REGISTER_NP_BINARY_MIXED_INT_PRECISION(name) \
NNVM_REGISTER_OP(name) \
.set_num_inputs(2) \
.set_num_outputs(1) \
.set_attr<nnvm::FListInputNames>("FListInputNames", \
[](const NodeAttrs& attrs) { \
return std::vector<std::string>{"lhs", "rhs"}; \
}) \
.set_attr<mxnet::FInferShape>("FInferShape", BinaryBroadcastShape) \
.set_attr<nnvm::FInferType>("FInferType", NumpyBinaryMixedIntPrecisionType) \
.set_attr<nnvm::FInplaceOption>("FInplaceOption", \
[](const NodeAttrs& attrs) { \
return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}}; \
}) \
.set_attr<FResourceRequest>( \
"FResourceRequest", \
[](const NodeAttrs& attrs) { \
return std::vector<ResourceRequest>{ResourceRequest::kTempSpace}; \
}) \
.add_argument("lhs", "NDArray-or-Symbol", "First input to the function") \
.add_argument("rhs", "NDArray-or-Symbol", "Second input to the function")
} // namespace op
} // namespace mxnet
#endif // MXNET_OPERATOR_NUMPY_NP_ELEMWISE_BROADCAST_OP_H_