blob: 8fd2cb4c53f4ce430220ad4a7f54749e3d991041 [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.
*/
/*!
* Copyright (c) 2015 by Contributors
* \file elementwise_binary_broadcast_op.h
* \brief Function definition of elementwise unary operators
*/
#ifndef MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_BROADCAST_OP_H_
#define MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_BROADCAST_OP_H_
#include <mxnet/operator_util.h>
#include <mxnet/op_attr_types.h>
#include <algorithm>
#include <vector>
#include <string>
#include <utility>
#include "../mshadow_op.h"
#include "../elemwise_op_common.h"
#include "./elemwise_binary_op.h"
#include "../operator_common.h"
#include "broadcast_reduce-inl.h"
namespace mxnet {
namespace op {
inline bool BinaryBroadcastShape(const nnvm::NodeAttrs& attrs,
std::vector<TShape> *in_attrs,
std::vector<TShape> *out_attrs) {
CHECK_EQ(in_attrs->size(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
TShape& lhs = (*in_attrs)[0];
TShape& rhs = (*in_attrs)[1];
// avoid pre-mature shape inference.
if (lhs.ndim() == 0 || rhs.ndim() == 0) return false;
if (lhs == rhs) {
SHAPE_ASSIGN_CHECK(*out_attrs, 0, lhs);
return true;
}
TShape out(std::max(lhs.ndim(), rhs.ndim()));
index_t bl = out.ndim() - lhs.ndim();
index_t br = out.ndim() - rhs.ndim();
for (index_t i = 0; i < out.ndim(); ++i) {
index_t l = 1, r = 1;
if (i >= bl) l = lhs[i-bl];
if (i >= br) r = rhs[i-br];
if (l != r) {
if (l == 0 || r == 0) {
out[i] = 0;
} else {
CHECK(l == 1 || r == 1)
<< "operands could not be broadcast together with shapes " << lhs << " " << rhs;
out[i] = std::max(l, r);
}
} else {
out[i] = l;
}
}
SHAPE_ASSIGN_CHECK(*out_attrs, 0, out);
return true;
}
inline bool BinaryBroadcastMulStorageType(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(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
const int lhs_stype = in_attrs->at(0);
const int rhs_stype = in_attrs->at(1);
int& out_stype = out_attrs->at(0);
bool dispatched = false;
if (!dispatched && common::ContainsOnlyStorage(*in_attrs, kDefaultStorage)) {
dispatched = storage_type_assign(&out_stype, kDefaultStorage,
dispatch_mode, DispatchMode::kFCompute);
}
if (!dispatched && lhs_stype == kCSRStorage && rhs_stype == kDefaultStorage) {
dispatched = storage_type_assign(&out_stype, kCSRStorage,
dispatch_mode, DispatchMode::kFComputeEx);
}
if (!dispatched) {
dispatched = dispatch_fallback(out_attrs, dispatch_mode);
}
return dispatched;
}
inline bool BinaryBroadcastAddStorageType(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(), 2U);
CHECK_EQ(out_attrs->size(), 1U);
const int lhs_stype = in_attrs->at(0);
const int rhs_stype = in_attrs->at(1);
int& out_stype = out_attrs->at(0);
bool dispatched = false;
if (!dispatched && common::ContainsOnlyStorage(*in_attrs, kDefaultStorage)) {
dispatched = storage_type_assign(&out_stype, kDefaultStorage,
dispatch_mode, DispatchMode::kFCompute);
}
if (!dispatched && ((lhs_stype == kCSRStorage && rhs_stype == kDefaultStorage) ||
(lhs_stype == kDefaultStorage && rhs_stype == kCSRStorage))) {
dispatched = storage_type_assign(&out_stype, kDefaultStorage,
dispatch_mode, DispatchMode::kFComputeEx);
}
if (!dispatched) {
dispatched = dispatch_fallback(out_attrs, dispatch_mode);
}
return dispatched;
}
#define BROADCAST_NDIM_SWITCH(ndim, NDim, ...) \
if (ndim <= 2) { \
const int NDim = 2; \
{__VA_ARGS__} \
} else if (ndim <= 4) { \
const int NDim = 4; \
{__VA_ARGS__} \
} else if (ndim <= broadcast::MAX_DIM) { \
const int NDim = broadcast::MAX_DIM; \
{__VA_ARGS__} \
} else { \
LOG(FATAL) << "NDim too large "; \
}
inline int BinaryBroadcastShapeCompact(const TShape& lshape, const TShape& rshape,
const TShape& oshape, TShape *new_lshape,
TShape *new_rshape, TShape *new_oshape) {
if (lshape == rshape) return 0;
index_t odim = std::max<index_t>(oshape.ndim(), broadcast::MAX_DIM);
*new_lshape = TShape(odim);
*new_rshape = TShape(odim);
*new_oshape = TShape(odim);
index_t bl = oshape.ndim() - lshape.ndim();
index_t br = oshape.ndim() - rshape.ndim();
index_t j = 0, lprod = 1, rprod = 1, oprod = 1;
for (index_t i = 0; i < oshape.ndim(); ++i) {
index_t l = 1, r = 1, o = oshape[i];
if (i >= bl) l = lshape[i-bl];
if (i >= br) r = rshape[i-br];
if ((lprod != rprod || l != r) &&
lprod*l > 1 && rprod*r > 1) {
(*new_lshape)[j] = lprod;
(*new_rshape)[j] = rprod;
(*new_oshape)[j] = oprod;
lprod = rprod = oprod = 1; ++j;
}
lprod *= l;
rprod *= r;
oprod *= o;
}
if (lprod > 1 || rprod > 1) {
(*new_lshape)[j] = lprod;
(*new_rshape)[j] = rprod;
(*new_oshape)[j] = oprod;
++j;
}
if (j <= broadcast::MAX_DIM) {
BROADCAST_NDIM_SWITCH(j, NDim, {
new_lshape->assign(&(*new_lshape)[0], &(*new_lshape)[NDim]);
new_rshape->assign(&(*new_rshape)[0], &(*new_rshape)[NDim]);
new_oshape->assign(&(*new_oshape)[0], &(*new_oshape)[NDim]);
});
} else {
LOG(FATAL) << "Too many broadcast dimensions with operands " << lshape << " " << rshape;
}
return j;
}
namespace mxnet_op {
template<int ndim, typename DType, typename OP>
struct binary_broadcast_kernel {
/*! \brief Map function for binary_broadcast_kernel */
MSHADOW_XINLINE static void Map(int base, int length, OpReqType req,
const Shape <ndim> &lstride, const Shape <ndim> &rstride,
const Shape <ndim> &oshape, DType *lhs, DType *rhs,
DType *out) {
Shape <ndim> coord = unravel(base, oshape);
auto lidx = static_cast<index_t>(dot(coord, lstride));
auto ridx = static_cast<index_t>(dot(coord, rstride));
KERNEL_ASSIGN(out[base], req, OP::Map(lhs[lidx], rhs[ridx]));
// starts from 1 to avoid extra inc at end of loop
for (int i = 1; i < length; ++i) {
inc(&coord, oshape, &lidx, lstride, &ridx, rstride);
// When tuning, don't actually run the op, since it's not going to be tuned against
// the actual op we'll eventually be using
KERNEL_ASSIGN(out[base + i], req, OP::Map(lhs[lidx], rhs[ridx]));
}
}
/*! \brief Map function for binary_broadcast_kernel */
MSHADOW_XINLINE static void Map(int base, int length, OpReqType req,
const Shape <ndim> &lstride, const Shape <ndim> &rstride,
const Shape <ndim> &oshape, DType lhs, DType *rhs,
DType *out) {
Shape <ndim> coord = unravel(base, oshape);
auto lidx = static_cast<index_t>(dot(coord, lstride));
auto ridx = static_cast<index_t>(dot(coord, rstride));
KERNEL_ASSIGN(out[base], req, OP::Map(lhs, rhs[ridx]));
// starts from 1 to avoid extra inc at end of loop
for (int i = 1; i < length; ++i) {
inc(&coord, oshape, &lidx, lstride, &ridx, rstride);
// When tuning, don't actually run the op, since it's not going to be tuned against
// the actual op we'll eventually be using
KERNEL_ASSIGN(out[base + i], req, OP::Map(lhs, rhs[ridx]));
}
}
};
template<int req, typename OP, bool col_vec>
struct csr_dns_csr_broadcast_kernel {
/*!
* \brief Map function for broadcast between csr and 1D vector
* \param row global thread id/assigned row id
* \param csr_data ptr to data buffer of csr matrix
* \param csr_indices ptr to indices buffer of csr matrix
* \param csr_indptr ptr to indptr buffer of csr matrix
* \param dns ptr to data buffer of the dense vector
* \param out ptr to the data buffer of the result csr matrix
*/
template<typename DType, typename CType, typename RType>
MSHADOW_XINLINE static void Map(int row, const DType *csr_data, const CType *csr_indices,
const RType *csr_indptr, const DType *dns, DType *out) {
const nnvm::dim_t curr_row_i = csr_indptr[row];
const nnvm::dim_t next_row_i = csr_indptr[row + 1];
for (nnvm::dim_t iter = curr_row_i; iter < next_row_i; iter++) {
KERNEL_ASSIGN(out[iter], req, OP::Map(csr_data[iter],
(col_vec)? dns[row] : dns[csr_indices[iter]]));
}
}
/*!
* \brief Map function for broadcast between csr and a scalar
* \param i global thread id
* \param csr_data ptr to data buffer of csr matrix
* \param scalar_ptr ptr to data buffer of the scalar tensor, only the 0-th element is used
* \param out ptr to the data buffer of output csr matrix
* \param nnz number of non-zero elements in input csr matrix
*/
template<typename DType>
MSHADOW_XINLINE static void Map(int i, const DType *csr_data, const DType* scalar_ptr,
DType *out, const nnvm::dim_t nnz) {
const DType scale = scalar_ptr[0];
if (i < nnz) {
KERNEL_ASSIGN(out[i], req, OP::Map(csr_data[i], scale));
}
}
};
template<int req, typename OP, bool reverse = false>
struct csr_dns_map_kernel {
template <typename DType, typename CType, typename RType>
MSHADOW_XINLINE static void Map(int row, const DType *csr_data, const CType *csr_indices,
const RType *csr_indptr, DType *out, const nnvm::dim_t num_rows,
const nnvm::dim_t num_cols) {
if (row < num_rows) {
const nnvm::dim_t curr_row_i = csr_indptr[row];
const nnvm::dim_t next_row_i = csr_indptr[row + 1];
for (nnvm::dim_t iter = curr_row_i; iter < next_row_i; iter++) {
const nnvm::dim_t target = row * num_cols + csr_indices[iter];
KERNEL_ASSIGN(out[target], req,
reverse ? OP::Map(out[target], csr_data[iter]) :
OP::Map(csr_data[iter], out[target]));
}
}
}
};
} // namespace mxnet_op
template<typename xpu, typename OP>
void BinaryBroadcastCompute(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
TShape new_lshape, new_rshape, new_oshape;
int ndim = BinaryBroadcastShapeCompact(inputs[0].shape_, inputs[1].shape_, outputs[0].shape_,
&new_lshape, &new_rshape, &new_oshape);
if (!ndim) {
ElemwiseBinaryOp::Compute<xpu, OP>(attrs, ctx, inputs, req, outputs);
} else {
if (req[0] != kNullOp) {
mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, {
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>());
mxnet_op::Kernel<mxnet_op::binary_broadcast_kernel<NDim, DType, OP>, xpu>::
template LaunchEx(s, new_oshape.Size(), req[0], lstride, rstride, oshape,
inputs[0].dptr<DType>(), inputs[1].dptr<DType>(), outputs[0].dptr<DType>());
});
});
}
}
}
template<typename xpu, typename OP>
void BinaryBroadcastCsrDnsCsrImpl(const OpContext& ctx,
const NDArray& csr,
const NDArray& dns,
const OpReqType req,
const NDArray& output) {
using namespace mshadow;
using namespace mxnet_op;
using namespace csr;
CHECK(req != kAddTo && req != kWriteInplace);
mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
bool col_vec;
if (dns.shape().ndim() == 1) {
col_vec = false;
} else {
col_vec = (dns.shape()[0] == csr.shape()[0])? true : false;
}
if (csr.storage_initialized()) {
const nnvm::dim_t nnz = csr.storage_shape()[0];
const nnvm::dim_t num_rows = output.shape()[0];
output.CheckAndAlloc({Shape1(num_rows + 1), Shape1(nnz)});
MSHADOW_TYPE_SWITCH(output.dtype(), DType, {
MSHADOW_IDX_TYPE_SWITCH(output.aux_type(kIdx), CType, {
MSHADOW_IDX_TYPE_SWITCH(output.aux_type(kIndPtr), RType, {
MXNET_ASSIGN_REQ_SWITCH(req, req_type, {
// broadcast_mul/div between csr and a scalar case
if ((dns.shape().ndim() == 2 && dns.shape()[0] == 1 && dns.shape()[1] == 1) ||
(dns.shape().ndim() == 1 && dns.shape()[0] == 1)) {
Kernel<csr_dns_csr_broadcast_kernel<req_type, OP, false>, xpu>::Launch(
s, nnz, csr.data().dptr<DType>(), dns.data().dptr<DType>(),
output.data().dptr<DType>(), nnz);
} else {
// broadcast_mul/div between csr and column vector
if (col_vec) {
Kernel<csr_dns_csr_broadcast_kernel<req_type, OP, true>, xpu>::Launch(
s, num_rows, csr.data().dptr<DType>(), csr.aux_data(kIdx).dptr<CType>(),
csr.aux_data(kIndPtr).dptr<RType>(), dns.data().dptr<DType>(),
output.data().dptr<DType>());
// broadcast_mul/div between csr and row vector
} else {
Kernel<csr_dns_csr_broadcast_kernel<req_type, OP, false>, xpu>::Launch(
s, num_rows, csr.data().dptr<DType>(), csr.aux_data(kIdx).dptr<CType>(),
csr.aux_data(kIndPtr).dptr<RType>(), dns.data().dptr<DType>(),
output.data().dptr<DType>());
}
}
Copy(output.aux_data(kIdx).FlatTo1D<xpu, CType>(),
csr.aux_data(kIdx).FlatTo1D<xpu, CType>(), s);
Copy(output.aux_data(kIndPtr).FlatTo1D<xpu, RType>(),
csr.aux_data(kIndPtr).FlatTo1D<xpu, RType>(), s);
});
});
});
});
// If input csr is an empty matrix, fill zeros and return
} else {
FillZerosCsrImpl(s, output);
return;
}
}
template<typename xpu, typename OP>
void BinaryBroadcastCsrDnsDnsImpl(const OpContext& ctx,
const NDArray& csr,
const NDArray& dns,
const OpReqType req,
const NDArray& output,
const TShape& new_csrshape,
const TShape& new_dnsshape,
const TShape& new_oshape,
const int ndim,
const bool reverse) {
using namespace mshadow;
using namespace mxnet_op;
using namespace csr;
CHECK(req == kWriteTo) << "Only kWriteTo supported for broadcast(csr, dns) = dns";
const bool legal_op = std::is_same<OP, mshadow_op::plus>::value ||
std::is_same<OP, mshadow_op::minus>::value;
CHECK(legal_op) << "Only add/sub are supported for broadcast(csr, dns) = dns";
CHECK_EQ(csr.shape()[0], output.shape()[0]);
CHECK_EQ(csr.shape()[1], output.shape()[1]);
mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
const nnvm::dim_t num_rows = output.shape()[0];
const nnvm::dim_t num_cols = output.shape()[1];
const TBlob& csr_data = csr.data();
const TBlob& csr_indices = csr.aux_data(kIdx);
const TBlob& csr_indptr = csr.aux_data(kIndPtr);
TBlob dns_data = dns.data();
TBlob out_data = output.data();
MSHADOW_TYPE_SWITCH(output.dtype(), DType, {
BROADCAST_NDIM_SWITCH(ndim, NDim, {
Shape<NDim> oshape = new_oshape.get<NDim>();
Shape<NDim> lstride = calc_stride(new_csrshape.get<NDim>());
Shape<NDim> rstride = calc_stride(new_dnsshape.get<NDim>());
if (reverse && std::is_same<OP, mshadow_op::minus>::value) {
Kernel<binary_broadcast_kernel<NDim, DType, mshadow_op::plus>, xpu>::
template LaunchEx(s, new_oshape.Size(), req, lstride, rstride, oshape,
DType(0), dns_data.dptr<DType>(), out_data.dptr<DType>());
} else {
Kernel<binary_broadcast_kernel<NDim, DType, OP>, xpu>::
template LaunchEx(s, new_oshape.Size(), req, lstride, rstride, oshape,
DType(0), dns_data.dptr<DType>(), out_data.dptr<DType>());
}
});
});
if (csr.storage_initialized()) {
MSHADOW_TYPE_SWITCH(csr.dtype(), DType, {
MSHADOW_IDX_TYPE_SWITCH(csr.aux_type(kIdx), CType, {
MSHADOW_IDX_TYPE_SWITCH(csr.aux_type(kIndPtr), RType, {
MXNET_ASSIGN_REQ_SWITCH(req, req_type, {
if (reverse && std::is_same<OP, mshadow_op::minus>::value) {
Kernel<csr_dns_map_kernel<req_type, mshadow_op::minus, true>, xpu>::Launch(
s, num_rows, csr_data.dptr<DType>(), csr_indices.dptr<CType>(),
csr_indptr.dptr<RType>(), out_data.dptr<DType>(), num_rows, num_cols);
} else {
Kernel<csr_dns_map_kernel<req_type, mshadow_op::plus>, xpu>::Launch(
s, num_rows, csr_data.dptr<DType>(), csr_indices.dptr<CType>(),
csr_indptr.dptr<RType>(), out_data.dptr<DType>(), num_rows, num_cols);
}
});
});
});
});
}
}
template<typename xpu, typename OP>
void BinaryBroadcastComputeSparseEx(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<NDArray>& inputs,
const std::vector<OpReqType>& req,
const std::vector<NDArray>& outputs) {
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
CHECK_EQ(req.size(), 1U);
CHECK_LE(inputs[1].shape().ndim(), 2U)
<< "input dense matrix should have less than or equal to 2 dimensions";
if (req[0] == kNullOp) return;
const NDArray& lhs = inputs[0];
const NDArray& rhs = inputs[1];
const NDArray& out = outputs[0];
const auto lhs_stype = lhs.storage_type();
const auto rhs_stype = rhs.storage_type();
const auto out_stype = out.storage_type();
// If the input is a matrix with the same shape, should be elemwise
if ((rhs.shape().ndim() != 1U) && (rhs.shape()[0] != 1) && (rhs.shape()[1] != 1)) {
if (lhs_stype == kCSRStorage && rhs_stype == kDefaultStorage && out_stype == kCSRStorage) {
const bool supported_op = std::is_same<OP, mshadow_op::mul>::value;
CHECK(supported_op)
<< "Please use elemwise_div for division between csr and dense of the same shape";
ElemwiseBinaryOp::DnsCsrCsrOp<xpu, mshadow_op::mul>(attrs, ctx, rhs, lhs, req[0], out, true);
}
} else {
// broadcast(CSR, Dense(1D)) = CSR
if (lhs_stype == kCSRStorage && rhs_stype == kDefaultStorage && out_stype == kCSRStorage) {
BinaryBroadcastCsrDnsCsrImpl<xpu, OP>(ctx, lhs, rhs, req[0], out);
} else {
LogUnimplementedOp(attrs, ctx, inputs, req, outputs);
}
}
}
template<typename xpu, typename OP>
void BinaryBroadcastComputeDenseEx(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<NDArray>& inputs,
const std::vector<OpReqType>& req,
const std::vector<NDArray>& outputs) {
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
CHECK_EQ(req.size(), 1U);
CHECK_LE(inputs[1].shape().ndim(), 2U)
<< "input dense matrix should have less than or equal to 2 dimensions";
if (req[0] == kNullOp) return;
const NDArray& lhs = inputs[0];
const NDArray& rhs = inputs[1];
const NDArray& out = outputs[0];
const auto lhs_stype = lhs.storage_type();
const auto rhs_stype = rhs.storage_type();
const auto out_stype = out.storage_type();
bool reverse = (lhs_stype == kDefaultStorage);
const NDArray& dns = (reverse) ? lhs : rhs;
const NDArray& csr = (reverse) ? rhs : lhs;
TShape new_csrshape, new_dnsshape, new_oshape;
int ndim = BinaryBroadcastShapeCompact(csr.shape(), dns.shape(), out.shape(),
&new_csrshape, &new_dnsshape, &new_oshape);
if (((lhs_stype == kCSRStorage && rhs_stype == kDefaultStorage) ||
(lhs_stype == kDefaultStorage && rhs_stype == kCSRStorage)) &&
out_stype == kDefaultStorage) {
// If the input is a matrix with the same shape, should be elemwise
if (!ndim) {
mshadow::Stream<xpu>* s = ctx.get_stream<xpu>();
ElemwiseBinaryOp::DnsCsrDnsOp<OP>(s, attrs, ctx, dns, csr, req[0], outputs[0], !reverse);
} else {
// broadcast(CSR, Dense(1D)) = CSR
BinaryBroadcastCsrDnsDnsImpl<xpu, OP>(ctx, csr, dns, req[0], out,
new_csrshape, new_dnsshape, new_oshape,
ndim, reverse);
}
} else {
LogUnimplementedOp(attrs, ctx, inputs, req, outputs);
}
}
template<typename xpu, typename LOP, typename ROP>
inline typename std::enable_if<std::is_same<xpu, cpu>::value, void>::type
BinaryBroadcastBackwardUseNone(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
using namespace broadcast;
TShape new_lshape, new_rshape, new_oshape;
int ndim = BinaryBroadcastShapeCompact(outputs[0].shape_, outputs[1].shape_, inputs[0].shape_,
&new_lshape, &new_rshape, &new_oshape);
if (!ndim) {
ElemwiseBinaryOp::BackwardUseNone<cpu, LOP, ROP>(attrs, ctx, inputs, req, outputs);
} else {
MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, {
Stream<cpu> *s = ctx.get_stream<cpu>();
const TBlob lhs = outputs[0].reshape(new_lshape);
const TBlob rhs = outputs[1].reshape(new_rshape);
const TBlob out = inputs[0].reshape(new_oshape);
BROADCAST_NDIM_SWITCH(ndim, NDim, {
// Request temporary storage
size_t workspace_size = new_oshape.Size();
Tensor<cpu, 1, char> workspace =
ctx.requested[0].get_space_typed<cpu, 1, char>(
Shape1(workspace_size * sizeof(index_t)), s);
ReduceWithExtraMem<red::sum, NDim, DType, LOP>(s, lhs, req[0], workspace, out);
ReduceWithExtraMem<red::sum, NDim, DType, ROP>(s, rhs, req[1], workspace, out);
});
});
}
}
template<typename xpu, typename LOP, typename ROP>
inline typename std::enable_if<std::is_same<xpu, gpu>::value, void>::type
BinaryBroadcastBackwardUseNone(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs);
template<typename xpu, int ndim, typename DType, typename LOP, typename ROP>
inline void BinaryBroadcastBackwardUseInImpl(const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs,
const TShape& new_lshape,
const TShape& new_rshape,
const TShape& new_oshape) {
using namespace mshadow;
using namespace mshadow::expr;
using namespace broadcast;
Stream<xpu> *s = ctx.get_stream<xpu>();
const TBlob lgrad = outputs[0].reshape(new_lshape);
const TBlob rgrad = outputs[1].reshape(new_rshape);
const TBlob ograd = inputs[0].reshape(new_oshape);
const TBlob lhs = inputs[1].reshape(new_lshape);
const TBlob rhs = inputs[2].reshape(new_rshape);
size_t workspace_size_l = ReduceWorkspaceSize<ndim, DType>(
s, lgrad.shape_, req[0], ograd.shape_, lhs.shape_, rhs.shape_);
size_t workspace_size_r = ReduceWorkspaceSize<ndim, DType>(
s, rgrad.shape_, req[1], ograd.shape_, lhs.shape_, rhs.shape_);
size_t workspace_size = std::max(workspace_size_l, workspace_size_r);
Tensor<xpu, 1, char> workspace =
ctx.requested[0].get_space_typed<xpu, 1, char>(Shape1(workspace_size), s);
Reduce<red::sum, ndim, DType, op::mshadow_op::mul, LOP>(s, lgrad, req[0], workspace,
ograd, lhs, rhs);
Reduce<red::sum, ndim, DType, op::mshadow_op::mul, ROP>(s, rgrad, req[1], workspace,
ograd, lhs, rhs);
}
template<typename xpu, typename LOP, typename ROP>
void BinaryBroadcastBackwardUseIn(const nnvm::NodeAttrs& attrs,
const OpContext& ctx,
const std::vector<TBlob>& inputs,
const std::vector<OpReqType>& req,
const std::vector<TBlob>& outputs) {
TShape new_lshape, new_rshape, new_oshape;
const bool need_bc = BinaryBroadcastShapeCompact(outputs[0].shape_,
outputs[1].shape_, inputs[0].shape_,
&new_lshape, &new_rshape, &new_oshape) != 0;
if (!need_bc) {
ElemwiseBinaryOp::BackwardUseIn<xpu, LOP, ROP>(attrs, ctx, inputs, req, outputs);
} else {
MSHADOW_TYPE_SWITCH(outputs[0].type_flag_, DType, {
BROADCAST_NDIM_SWITCH(new_oshape.ndim(), NDim, {
BinaryBroadcastBackwardUseInImpl<xpu, NDim, DType, LOP, ROP>(
ctx, inputs, req, outputs, new_lshape, new_rshape, new_oshape);
});
});
}
}
#define MXNET_OPERATOR_REGISTER_BINARY_BROADCAST(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::FInferShape>("FInferShape", BinaryBroadcastShape) \
.set_attr<nnvm::FInferType>("FInferType", ElemwiseType<2, 1>) \
.set_attr<nnvm::FInplaceOption>("FInplaceOption", \
[](const NodeAttrs& attrs){ \
return std::vector<std::pair<int, int> >{{0, 0}, {1, 0}}; \
}) \
.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
#ifdef __CUDACC__
#include "./elemwise_binary_broadcast_op-inl.cuh"
#endif
#endif // MXNET_OPERATOR_TENSOR_ELEMWISE_BINARY_BROADCAST_OP_H_