blob: 8f1b4f9f3a307f9eafee36de534a56ea5f9d5856 [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_matmul_op-inl.h
* \brief Function definition of matrix numpy-compatible matmul operator
*/
#ifndef MXNET_OPERATOR_NUMPY_NP_MATMUL_OP_INL_H_
#define MXNET_OPERATOR_NUMPY_NP_MATMUL_OP_INL_H_
#include <mxnet/operator_util.h>
#include <vector>
#include <algorithm>
#include <memory>
#include "../mxnet_op.h"
#include "np_tensordot_op-inl.h"
#include "np_dot-inl.h"
namespace mxnet {
namespace op {
inline bool MatmulNeedBroadcast(const mxnet::TShape& ashape,
const mxnet::TShape& bshape) {
bool need_bcast = false;
for (int i = ashape.ndim() - 3, j = bshape.ndim() - 3;
i >= 0 || j >=0; --i, --j) {
if (i >= 0 && j >= 0) {
need_bcast |= (ashape[i] != bshape[j]);
} else if (i >= 0) {
need_bcast |= (ashape[i] != 1);
} else {
need_bcast |= (bshape[j] != 1);
}
}
return need_bcast;
}
/*!
* \brief Get mshadow::Shape from mxnet::TShape.
* \note fill ndim = 1 into extra ndims if outshape.ndim > input.ndim
* \example i 0 1 2 3 4 (shape.ndim() = 5)
shape 2 3 - - - (N = 2)
k_shape 1 1 2 3 (ndim = 4)
* \tparam ndim - ndim of outshape.
* \param shape - inshape.
* \param N - the count of valid inshape ndims.
*/
template<int ndim>
mshadow::Shape<ndim> GetKernelShape(const mxnet::TShape& shape, size_t N) {
mshadow::Shape<ndim>k_shape;
for (int i = shape.ndim() - 1, j = N - 1; i >= 0 || j >= 0 ; --i, --j) {
if (i >= 0) {
k_shape[j] = shape[i];
} else {
k_shape[j] = 1;
}
}
return k_shape;
}
/*!
* \brief Broadcast in_shape to broadcast_shape in [dimstart, dimend].
Make sure that before use this function:
If in_shape[i] != broadcast_shape[i], in_shape[i] == 1.
* \param N - ndim of both in_shape and broadcast_shape.
* \param dimstart start dimension
* \param dimend end dimension
*/
template<int ndim>
mshadow::Shape<ndim> GetBroadcastKernelShape(mshadow::Shape<ndim> in_shape,
mshadow::Shape<ndim> broadcast_shape,
int dimstart, int dimend) {
CHECK_GE(dimstart, 0) << "dimstart must be >= 0, while received " << dimstart;
CHECK_LT(dimend, ndim) << "dimend must be < " << ndim
<< ", while received " << dimend;
mshadow::Shape<ndim>out_shape(in_shape);
for (int i = dimstart; i < dimend; ++i) {
out_shape[i] = std::max(in_shape[i], broadcast_shape[i]);
}
return out_shape;
}
struct SumByShape {
/*!
* \brief squash input into output by addition
* \example input.flatten.shape = (10, ),
output.flatten.shape = (2, ),
in_size = 10, out_size = 2, then,
output[0] = sum(input[0, 2, 4, 6, 8])
output[1] = sum(input[1, 3, 5, 7, 9])
* \note in_size >= out_size
*/
template<typename DType>
MSHADOW_XINLINE static void Map(int i, DType* output, DType* input,
size_t in_size, size_t out_size,
const int req){
// i is the global position in flattened output
size_t pos = static_cast<size_t>(i);
DType temp = 0;
while (pos < in_size) {
temp += input[pos];
pos += out_size;
}
KERNEL_ASSIGN(output[i], req, temp);
}
};
template<typename xpu, typename DType>
inline void MatmulImpl(const OpContext& ctx,
const TBlob& input_a, const TBlob& input_b,
const OpReqType& req, const TBlob& output,
Tensor<xpu, 1, char> temp_mem,
const size_t ndim, const size_t batch_size,
const size_t bc_size_a, const size_t bc_size_b,
const mxnet::TShape& a_shape,
const mxnet::TShape& b_shape,
const mxnet::TShape& out_shape,
const bool TA, const bool TB) {
using namespace mshadow;
using namespace mxnet_op;
mshadow::Tensor<xpu, 1, DType*> workspace;
mshadow::Tensor<xpu, 3, DType> ans, mlhs, mrhs;
mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
bool isCPU = std::is_same<xpu, cpu>::value;
// Is true if either a or b requires broadcast or not
if (MatmulNeedBroadcast(a_shape, b_shape)) {
// e.g. a.shape = (2, 3, 1, 4, 2)
// b.shape = (5, 2, 4)
// c = matmul(a, b), need to broadcast a and b
// c.shape = (2, 3, 5, 4, 4)
mshadow::Shape<MXNET_SPECIAL_MAX_NDIM> k_a_shape =
GetKernelShape<MXNET_SPECIAL_MAX_NDIM>(a_shape, ndim);
mshadow::Shape<MXNET_SPECIAL_MAX_NDIM> k_b_shape =
GetKernelShape<MXNET_SPECIAL_MAX_NDIM>(b_shape, ndim);
mshadow::Shape<MXNET_SPECIAL_MAX_NDIM> k_out_shape =
GetKernelShape<MXNET_SPECIAL_MAX_NDIM>(out_shape, ndim);
const mshadow::Shape<MXNET_SPECIAL_MAX_NDIM> k_a_shape_bc =
GetBroadcastKernelShape<MXNET_SPECIAL_MAX_NDIM>(k_a_shape, k_out_shape, 0, ndim - 2);
const mshadow::Shape<MXNET_SPECIAL_MAX_NDIM> k_b_shape_bc =
GetBroadcastKernelShape<MXNET_SPECIAL_MAX_NDIM>(k_b_shape, k_out_shape, 0, ndim - 2);
DType* bc_a_ptr = reinterpret_cast<DType*>(temp_mem.dptr_);
DType* bc_b_ptr = bc_a_ptr + bc_size_a;
MSHADOW_TYPE_SWITCH_WITH_BOOL(input_a.type_flag_, IType, {
MSHADOW_TYPE_SWITCH_WITH_BOOL(input_b.type_flag_, OType, {
struct ShapeAndStride aux_data_a, aux_data_b;
PrepareAUXData(&aux_data_a, k_a_shape, k_a_shape_bc, ndim);
PrepareAUXData(&aux_data_b, k_b_shape, k_b_shape_bc, ndim);
if (isCPU) {
if (!aux_data_a.shape_changed) {
Kernel<direct_copy<mshadow_op::identity>, xpu>::Launch(
s, bc_size_a, input_a.dptr<IType>(), bc_a_ptr, OpReqType::kWriteTo);
Kernel<broadcast_kernel_cpu<mshadow_op::identity>, xpu>::Launch(
s, input_b.Size(), input_b.dptr<IType>(), bc_b_ptr,
aux_data_b, OpReqType::kWriteTo, ndim);
} else if (!aux_data_b.shape_changed) {
Kernel<direct_copy<mshadow_op::identity>, xpu>::Launch(
s, bc_size_b, input_b.dptr<IType>(), bc_b_ptr, OpReqType::kWriteTo);
Kernel<broadcast_kernel_cpu<mshadow_op::identity>, xpu>::Launch(
s, input_a.Size(), input_a.dptr<IType>(), bc_a_ptr,
aux_data_a, OpReqType::kWriteTo, ndim);
} else {
Kernel<broadcast_kernel_cpu<mshadow_op::identity>, xpu>::Launch(
s, input_a.Size(), input_a.dptr<IType>(), bc_a_ptr,
aux_data_a, OpReqType::kWriteTo, ndim);
Kernel<broadcast_kernel_cpu<mshadow_op::identity>, xpu>::Launch(
s, input_b.Size(), input_b.dptr<IType>(), bc_b_ptr,
aux_data_b, OpReqType::kWriteTo, ndim);
}
} else {
Kernel<broadcast_kernel_gpu<mshadow_op::identity>, xpu>::Launch(
s, bc_size_a, input_a.dptr<IType>(), bc_a_ptr,
aux_data_a, OpReqType::kWriteTo, ndim);
Kernel<broadcast_kernel_gpu<mshadow_op::identity>, xpu>::Launch(
s, bc_size_b, input_b.dptr<IType>(), bc_b_ptr,
aux_data_b, OpReqType::kWriteTo, ndim);
}
});
});
ans = mshadow::Tensor<xpu, 3, DType>(output.dptr<DType>(),
Shape3(batch_size, k_out_shape[ndim - 2], k_out_shape[ndim - 1]), s);
mlhs = mshadow::Tensor<xpu, 3, DType>(bc_a_ptr,
Shape3(batch_size, k_a_shape_bc[ndim - 2], k_a_shape_bc[ndim - 1]), s);
mrhs = mshadow::Tensor<xpu, 3, DType>(bc_b_ptr,
Shape3(batch_size, k_b_shape_bc[ndim - 2], k_b_shape_bc[ndim - 1]), s);
DType** workspace_ptr = reinterpret_cast<DType**>(bc_b_ptr + bc_size_b);
workspace = mshadow::Tensor<xpu, 1, DType*>(workspace_ptr, Shape1(3 * ans.size(0)), s);
} else {
ans = output.get_with_shape<xpu, 3, DType>(
Shape3(batch_size, out_shape[ndim - 2], out_shape[ndim - 1]), s);
mlhs = input_a.get_with_shape<xpu, 3, DType>(
Shape3(batch_size, (a_shape.ndim() == 1) ? 1 : a_shape[a_shape.ndim() - 2],
a_shape[a_shape.ndim() - 1]), s);
mrhs = input_b.get_with_shape<xpu, 3, DType>(
Shape3(batch_size, b_shape[b_shape.ndim() - 2], b_shape[b_shape.ndim() - 1]), s);
workspace = ctx.requested[0].get_space_typed<xpu, 1, DType*>
(mshadow::Shape1(3 * ans.size(0)), s);
}
if (TA && TB) {
mshadow::BatchGEMM<true, true>(ans, mlhs, mrhs, (DType)1.0f,
(kAddTo == req) ? (DType)1.0f : (DType)0.0f,
workspace);
} else if (TA && !TB) {
mshadow::BatchGEMM<true, false>(ans, mlhs, mrhs, (DType)1.0f,
(kAddTo == req) ? (DType)1.0f : (DType)0.0f,
workspace);
} else if (!TA && TB) {
mshadow::BatchGEMM<false, true>(ans, mlhs, mrhs, (DType)1.0f,
(kAddTo == req) ? (DType)1.0f : (DType)0.0f,
workspace);
} else {
mshadow::BatchGEMM<false, false>(ans, mlhs, mrhs, (DType)1.0f,
(kAddTo == req) ? (DType)1.0f : (DType)0.0f,
workspace);
}
}
template<typename xpu>
void NumpyMatmulForward(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;
if (req[0] == kNullOp && req[1] == kNullOp) return;
CHECK_EQ(inputs.size(), 2U);
CHECK_EQ(outputs.size(), 1U);
const TBlob& a = inputs[0];
const TBlob& b = inputs[1];
const TBlob& out = outputs[0];
mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
CHECK_NE(a.shape_.ndim(), 0)
<< "Multiplication by scalars is not allowed.\n";
CHECK_NE(b.shape_.ndim(), 0)
<< "Multiplication by scalars is not allowed.\n";
CHECK_EQ(out.type_flag_, a.type_flag_)
<< "Mstmul function only support input/output with the same type";
CHECK_EQ(out.type_flag_, b.type_flag_)
<< "Matmul function only support input/output with the same type";
CHECK(out.type_flag_ == kFloat32 || out.type_flag_ == kFloat64 ||
(out.type_flag_ == kFloat16 && ctx.run_ctx.ctx.dev_mask() == mshadow::gpu::kDevMask))
<< "Matmul only supports float32/float64 for CPU, and float16/float32/float64 for GPU";
if ((a.shape_.Size() == 0) || (b.shape_.Size() == 0)) {
MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, {
Kernel<mxnet_op::set_zero, xpu>::Launch(
s, outputs[0].shape_.Size(), outputs[0].dptr<DType>());
});
return;
}
mxnet::TShape a_shape = (a.shape_.ndim() == 1) ? Shape2(1, a.shape_.Size()) : a.shape_;
mxnet::TShape b_shape = (b.shape_.ndim() == 1) ? Shape2(b.shape_.Size(), 1) : b.shape_;
mxnet::TShape out_shape = out.shape_;
size_t ndim = out_shape.ndim();
if ((a.shape_.ndim() == 1) && (b.shape_.ndim() == 1)) {
ndim = 2;
std::vector<size_t> newshape({1, 1});
out_shape.assign(newshape.begin(), newshape.end());
} else if ((a.shape_.ndim() == 1) && (b.shape_.ndim() != 1)) {
ndim = out_shape.ndim() + 1;
std::vector<size_t> newshape(ndim);
for (size_t i = 0; i < ndim - 2; ++i) {
newshape[i] = out_shape[i];
}
newshape[ndim - 2] = 1;
newshape[ndim - 1] = out_shape[ndim - 2];
out_shape.assign(newshape.begin(), newshape.end());
} else if ((a.shape_.ndim() != 1) && (b.shape_.ndim() == 1)) {
ndim = out_shape.ndim() + 1;
std::vector<size_t> newshape(ndim);
for (size_t i = 0; i < ndim - 1; ++i) {
newshape[i] = out_shape[i];
}
newshape[ndim - 1] = 1;
out_shape.assign(newshape.begin(), newshape.end());
}
MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, {
size_t batch_size = out_shape.ProdShape(0, ndim - 2);
size_t bc_size_a = batch_size * a_shape[a_shape.ndim() - 2] * a_shape[a_shape.ndim() - 1];
size_t bc_size_b = batch_size * b_shape[b_shape.ndim() - 2] * b_shape[b_shape.ndim() - 1];
size_t temp_mem_size = (bc_size_a + bc_size_b) * sizeof(DType) +
3 * batch_size * sizeof(DType*);
Tensor<xpu, 1, char> temp_mem =
ctx.requested[0].get_space_typed<xpu, 1, char>(Shape1(temp_mem_size), s);
MatmulImpl<xpu, DType>(ctx, inputs[0], inputs[1], req[0], outputs[0], temp_mem,
ndim, batch_size, bc_size_a, bc_size_b,
a_shape, b_shape, out_shape, false, false);
});
}
template<typename xpu>
void NumpyMatmulBackward(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;
if (req[0] == kNullOp && req[1] == kNullOp) return;
CHECK_EQ(inputs.size(), 3U);
CHECK_EQ(outputs.size(), 2U);
const TBlob& ograd = inputs[0];
const TBlob& a = inputs[1];
const TBlob& b = inputs[2];
const TBlob& grad_a = outputs[0];
const TBlob& grad_b = outputs[1];
mshadow::Stream<xpu> *s = ctx.get_stream<xpu>();
CHECK(grad_a.type_flag_ == kFloat32 || grad_a.type_flag_ == kFloat64 ||
(grad_a.type_flag_ == kFloat16 && ctx.run_ctx.ctx.dev_mask() == mshadow::gpu::kDevMask))
<< "Matmul only supports float32/float64 for CPU, and float16/float32/float64 for GPU";
CHECK(grad_b.type_flag_ == kFloat32 || grad_b.type_flag_ == kFloat64 ||
(grad_b.type_flag_ == kFloat16 && ctx.run_ctx.ctx.dev_mask() == mshadow::gpu::kDevMask))
<< "Matmul only supports float32/float64 for CPU, and float16/float32/float64 for GPU";
// ograd.shape_.Size() == 0 if and only if a.shape_.Size() == 0 && b.shape_.Size() == 0
if (a.shape_.Size() == 0 && b.shape_.Size() == 0) return;
if (a.shape_.Size() == 0) { // a.shape_.Size() == 0 && b.shape_.Size() != 0
if (req[1] == kWriteTo) {
MSHADOW_REAL_TYPE_SWITCH(outputs[1].type_flag_, DType, {
Kernel<mxnet_op::set_zero, xpu>::Launch(s, grad_b.shape_.Size(), grad_b.dptr<DType>());
});
}
return;
}
if (b.shape_.Size() == 0) { // b.shape_.Size() == 0 && a.shape_.Size() != 0
if (req[0] == kWriteTo) {
MSHADOW_REAL_TYPE_SWITCH(outputs[0].type_flag_, DType, {
Kernel<mxnet_op::set_zero, xpu>::Launch(s, grad_a.shape_.Size(), grad_a.dptr<DType>());
});
}
return;
}
mxnet::TShape a_shape = (a.shape_.ndim() == 1) ? Shape2(1, a.shape_.Size()) : a.shape_;
mxnet::TShape b_shape = (b.shape_.ndim() == 1) ? Shape2(b.shape_.Size(), 1) : b.shape_;
mxnet::TShape out_shape = ograd.shape_;
size_t ndim = out_shape.ndim();
if ((a.shape_.ndim() == 1) && (b.shape_.ndim() == 1)) {
// e.g. a.shape = (x), b.shape = (x)
// c = matmul(a, b)
// a.shape -> (1, x), b.shape -> (x, 1)
// newshape = (1, 1) -> c.shape = ()
ndim = 2;
std::vector<size_t> newshape({1, 1});
out_shape.assign(newshape.begin(), newshape.end());
} else if ((a.shape_.ndim() == 1) && (b.shape_.ndim() != 1)) {
// e.g. a.shape = (x), b.shape = (..., x, y)
// c = matmul(a, b)
// a.shape -> (1, x)
// newshape = (..., 1, y) -> c.shape = (..., y)
ndim = out_shape.ndim() + 1;
std::vector<size_t> newshape(ndim);
for (size_t i = 0; i < ndim - 2; ++i) {
newshape[i] = out_shape[i];
}
newshape[ndim - 2] = 1;
newshape[ndim - 1] = out_shape[out_shape.ndim() - 1];
out_shape.assign(newshape.begin(), newshape.end());
} else if ((a.shape_.ndim() != 1) && (b.shape_.ndim() == 1)) {
// e.g. a.shape = (..., x, y), b.shape = (y)
// c = matmul(a, b)
// b.shape -> (y, 1)
// newshape = (..., y, 1) -> c.shape = (..., y)
ndim = out_shape.ndim() + 1;
std::vector<size_t> newshape(ndim);
for (size_t i = 0; i < ndim - 1; ++i) {
newshape[i] = out_shape[i];
}
newshape[ndim - 1] = 1;
out_shape.assign(newshape.begin(), newshape.end());
}
std::vector<size_t> vec_grad_a_shape(ndim, -1);
std::vector<size_t> vec_grad_b_shape(ndim, -1);
for (unsigned int i = 0; i < ndim - 2; ++i) {
vec_grad_a_shape[i] = out_shape[i];
vec_grad_b_shape[i] = out_shape[i];
}
vec_grad_a_shape[ndim - 2] = a_shape[a_shape.ndim() - 2];
vec_grad_a_shape[ndim - 1] = a_shape[a_shape.ndim() - 1];
mxnet::TShape grad_a_shape = mxnet::TShape(vec_grad_a_shape.begin(), vec_grad_a_shape.end());
vec_grad_b_shape[ndim - 2] = b_shape[b_shape.ndim() - 2];
vec_grad_b_shape[ndim - 1] = b_shape[b_shape.ndim() - 1];
mxnet::TShape grad_b_shape = mxnet::TShape(vec_grad_b_shape.begin(), vec_grad_b_shape.end());
MSHADOW_REAL_TYPE_SWITCH(ograd.type_flag_, DType, {
size_t batch_size =
out_shape.ProdShape(0, ndim - 2);
size_t bc_size_a =
batch_size * a_shape[a_shape.ndim() - 2] * a_shape[a_shape.ndim() - 1];
size_t bc_size_b =
batch_size * b_shape[b_shape.ndim() - 2] * b_shape[b_shape.ndim() - 1];
size_t bc_size_out =
batch_size * out_shape[out_shape.ndim() - 2] * out_shape[out_shape.ndim() - 1];
size_t temp_mem_size_grada = (bc_size_out + bc_size_b) * sizeof(DType) +
3 * batch_size * sizeof(DType*);
size_t temp_mem_size_gradb = (bc_size_a + bc_size_out) * sizeof(DType) +
3 * batch_size * sizeof(DType*);
size_t temp_size_grada = bc_size_a * sizeof(DType);
size_t temp_size_gradb = bc_size_b * sizeof(DType);
size_t temp_mem_size = temp_mem_size_grada + temp_mem_size_gradb +
temp_size_grada + temp_size_gradb;
Tensor<xpu, 1, char> temp_mem =
ctx.requested[0].get_space_typed<xpu, 1, char>(Shape1(temp_mem_size), s);
Tensor<xpu, 1, char> workspace_grada(temp_mem.dptr_, Shape1(temp_mem_size_grada), s);
Tensor<xpu, 1, char> workspace_gradb(workspace_grada.dptr_ + temp_mem_size_grada,
Shape1(temp_mem_size_gradb), s);
Tensor<xpu, 1, DType> temp_grada(
reinterpret_cast<DType*>(workspace_gradb.dptr_ + temp_mem_size_gradb),
Shape1(bc_size_a), s);
Tensor<xpu, 1, DType> temp_gradb(
reinterpret_cast<DType*>(temp_grada.dptr_ + bc_size_a),
Shape1(bc_size_b), s);
MatmulImpl<xpu, DType>(ctx, ograd, b, kWriteTo, temp_grada, workspace_grada,
ndim, batch_size, bc_size_out, bc_size_b,
out_shape, b_shape, grad_a_shape, false, true);
MatmulImpl<xpu, DType>(ctx, a, ograd, kWriteTo, temp_gradb, workspace_gradb,
ndim, batch_size, bc_size_a, bc_size_out,
a_shape, out_shape, grad_b_shape, true, false);
Kernel<SumByShape, xpu>::Launch(
s, a_shape.Size(), grad_a.dptr<DType>(), temp_grada.dptr_,
bc_size_a, a_shape.Size(), req[0]);
Kernel<SumByShape, xpu>::Launch(
s, b_shape.Size(), grad_b.dptr<DType>(), temp_gradb.dptr_,
bc_size_b, b_shape.Size(), req[1]);
});
}
} // namespace op
} // namespace mxnet
#endif // MXNET_OPERATOR_NUMPY_NP_MATMUL_OP_INL_H_