blob: 2e19d3219abb08db89f804bea46eaaa8186565ec [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 mkldnn_convolution.cc
* \brief
* \author Da Zheng
*/
#if MXNET_USE_MKLDNN == 1
#include "../convolution-inl.h"
#include "./mkldnn_ops-inl.h"
#include "./mkldnn_base-inl.h"
#include "./mkldnn_convolution-inl.h"
namespace mxnet {
namespace op {
bool SupportMKLDNNConv(const ConvolutionParam& params, const NDArray &input) {
if (params.kernel.ndim() != 2)
return false;
return input.dtype() == mshadow::kFloat32 && input.shape().ndim() == 4;
}
mkldnn::convolution_forward::primitive_desc GetConvFwdImpl(
const ConvolutionParam& param, const bool is_train, const NDArray &data,
const NDArray &weights, const NDArray *bias, const NDArray &output) {
auto prop = is_train ? mkldnn::prop_kind::forward_training : mkldnn::prop_kind::forward_scoring;
auto data_md = GetMemDesc(data);
auto weight_md = GetWeightDesc(weights, param.num_group);
auto out_md = GetMemDesc(output);
auto engine = CpuEngine::Get()->get_engine();
CHECK_GE(param.stride.ndim(), 2U);
CHECK_GE(param.pad.ndim(), 2U);
CHECK_GE(param.dilate.ndim(), 2U);
mkldnn::memory::dims strides{0, 0};
strides[0] = param.stride[0];
strides[1] = param.stride[1];
mkldnn::memory::dims padding{0, 0};
padding[0] = param.pad[0];
padding[1] = param.pad[1];
if (param.dilate.ndim() == 0 && bias == nullptr) {
mkldnn::convolution_forward::desc desc(prop, mkldnn::algorithm::convolution_direct,
data_md, weight_md, out_md, strides, padding, padding, mkldnn::padding_kind::zero);
return mkldnn::convolution_forward::primitive_desc(desc, engine);
} else if (param.dilate.ndim() == 0) {
auto bias_md = GetMemDesc(*bias);
mkldnn::convolution_forward::desc desc(prop, mkldnn::algorithm::convolution_direct,
data_md, weight_md, bias_md, out_md, strides, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_forward::primitive_desc(desc, engine);
} else {
mkldnn::memory::dims dilates{0, 0};
dilates[0] = param.dilate[0] - 1;
dilates[1] = param.dilate[1] - 1;
if (bias == nullptr) {
mkldnn::convolution_forward::desc desc(prop, mkldnn::algorithm::convolution_direct,
data_md, weight_md, out_md, strides, dilates, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_forward::primitive_desc(desc, engine);
} else {
auto bias_md = GetMemDesc(*bias);
mkldnn::convolution_forward::desc desc(prop, mkldnn::algorithm::convolution_direct,
data_md, weight_md, bias_md, out_md, strides,
dilates, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_forward::primitive_desc(desc, engine);
}
}
}
static mkldnn::convolution_backward_data::primitive_desc GetConvBwdData(
const ConvolutionParam& param, const NDArray &data, const NDArray &weights,
const NDArray &output, const mkldnn::convolution_forward::primitive_desc &fwd_pd) {
auto data_md = GetMemDesc(data);
auto weight_md = GetWeightDesc(weights, param.num_group);
auto out_md = GetMemDesc(output);
auto engine = CpuEngine::Get()->get_engine();
CHECK_GE(param.stride.ndim(), 2U);
CHECK_GE(param.pad.ndim(), 2U);
CHECK_GE(param.dilate.ndim(), 2U);
mkldnn::memory::dims strides{0, 0};
strides[0] = param.stride[0];
strides[1] = param.stride[1];
mkldnn::memory::dims padding{0, 0};
padding[0] = param.pad[0];
padding[1] = param.pad[1];
if (param.dilate.ndim() == 0) {
mkldnn::convolution_backward_data::desc desc(mkldnn::algorithm::convolution_direct,
data_md, weight_md, out_md, strides, padding, padding, mkldnn::padding_kind::zero);
return mkldnn::convolution_backward_data::primitive_desc(desc, engine, fwd_pd);
} else {
mkldnn::memory::dims dilates{0, 0};
dilates[0] = param.dilate[0] - 1;
dilates[1] = param.dilate[1] - 1;
mkldnn::convolution_backward_data::desc desc(mkldnn::algorithm::convolution_direct,
data_md, weight_md, out_md, strides, dilates, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_backward_data::primitive_desc(desc, engine, fwd_pd);
}
}
static mkldnn::convolution_backward_weights::primitive_desc GetConvBwdWeights(
const ConvolutionParam& param, const NDArray &data,
const NDArray &weights, const NDArray *bias, const NDArray &output,
const mkldnn::convolution_forward::primitive_desc &fwd_pd) {
auto data_md = GetMemDesc(data);
auto weight_md = GetWeightDesc(weights, param.num_group);
auto out_md = GetMemDesc(output);
auto engine = CpuEngine::Get()->get_engine();
CHECK_GE(param.stride.ndim(), 2U);
CHECK_GE(param.pad.ndim(), 2U);
CHECK_GE(param.dilate.ndim(), 2U);
mkldnn::memory::dims strides{0, 0};
strides[0] = param.stride[0];
strides[1] = param.stride[1];
mkldnn::memory::dims padding{0, 0};
padding[0] = param.pad[0];
padding[1] = param.pad[1];
if (param.dilate.ndim() == 0 && bias == nullptr) {
mkldnn::convolution_backward_weights::desc desc(mkldnn::algorithm::convolution_direct,
data_md, weight_md, out_md, strides, padding, padding, mkldnn::padding_kind::zero);
return mkldnn::convolution_backward_weights::primitive_desc(desc, engine, fwd_pd);
} else if (param.dilate.ndim() == 0) {
auto bias_md = GetMemDesc(*bias);
mkldnn::convolution_backward_weights::desc desc(mkldnn::algorithm::convolution_direct,
data_md, weight_md, bias_md, out_md, strides, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_backward_weights::primitive_desc(desc, engine, fwd_pd);
} else {
mkldnn::memory::dims dilates{0, 0};
dilates[0] = param.dilate[0] - 1;
dilates[1] = param.dilate[1] - 1;
if (bias == nullptr) {
mkldnn::convolution_backward_weights::desc desc(mkldnn::algorithm::convolution_direct,
data_md, weight_md, out_md, strides, dilates, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_backward_weights::primitive_desc(desc, engine, fwd_pd);
} else {
auto bias_md = GetMemDesc(*bias);
mkldnn::convolution_backward_weights::desc desc(mkldnn::algorithm::convolution_direct,
data_md, weight_md, bias_md, out_md,
strides, dilates, padding, padding,
mkldnn::padding_kind::zero);
return mkldnn::convolution_backward_weights::primitive_desc(desc, engine, fwd_pd);
}
}
}
void MKLDNNConvForward::SetNewMem(const mkldnn::memory &data,
const mkldnn::memory &weight,
const mkldnn::memory *bias,
const mkldnn::memory &output) {
if (this->data_ == nullptr)
this->data_ = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
fwd_pd.src_primitive_desc(), data.get_data_handle()));
else
this->data_->set_data_handle(data.get_data_handle());
if (this->weight_ == nullptr)
this->weight_ = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
fwd_pd.weights_primitive_desc(), weight.get_data_handle()));
else
this->weight_->set_data_handle(weight.get_data_handle());
if (this->out_ == nullptr)
this->out_ = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
fwd_pd.dst_primitive_desc(), output.get_data_handle()));
else
this->out_->set_data_handle(output.get_data_handle());
if (bias != nullptr) {
if (this->bias_ == nullptr)
this->bias_ = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
fwd_pd.bias_primitive_desc(), bias->get_data_handle()));
else
this->bias_->set_data_handle(bias->get_data_handle());
if (this->fwd_ == nullptr)
this->fwd_ = std::shared_ptr<mkldnn::convolution_forward>(
new mkldnn::convolution_forward(fwd_pd, mkldnn::primitive::at(*this->data_),
mkldnn::primitive::at(*this->weight_),
mkldnn::primitive::at(*this->bias_),
*this->out_));
} else if (this->fwd_ == nullptr) {
this->fwd_ = std::shared_ptr<mkldnn::convolution_forward>(
new mkldnn::convolution_forward(fwd_pd, mkldnn::primitive::at(*this->data_),
mkldnn::primitive::at(*this->weight_),
*this->out_));
}
}
MKLDNNConvForward &GetConvFwd(const nnvm::NodeAttrs& attrs, const bool is_train,
const NDArray &data, const NDArray &weights,
const NDArray *bias, const NDArray &output) {
#if DMLC_CXX11_THREAD_LOCAL
static thread_local std::unordered_map<MKLDNNConvSignature, MKLDNNConvForward, OpHash> fwds;
#else
static MX_THREAD_LOCAL std::unordered_map<MKLDNNConvSignature, MKLDNNConvForward, OpHash> fwds;
#endif
const ConvolutionParam& param = nnvm::get<ConvolutionParam>(attrs.parsed);
MKLDNNConvSignature key(param);
key.AddSign(is_train);
// Here we can sign the conv op with NDArray because conv primitive will
// decide the right layout for the, so we only need to get the shape and the
// data type of the arrays.
key.AddSign(data);
key.AddSign(weights);
key.AddSign(output);
if (bias)
key.AddSign(*bias);
auto it = fwds.find(key);
if (it == fwds.end()) {
MKLDNNConvForward fwd(param, is_train, data, weights, bias, output);
auto ins_ret = fwds.insert(
std::pair<MKLDNNConvSignature, MKLDNNConvForward>(key, fwd));
CHECK(ins_ret.second);
it = ins_ret.first;
}
return it->second;
}
void MKLDNNConvolutionForward(const nnvm::NodeAttrs& attrs, const OpContext &ctx,
const std::vector<NDArray> &in_data,
const std::vector<OpReqType> &req,
const std::vector<NDArray> &out_data) {
TmpMemMgr::Get()->Init(ctx.requested[conv::kTempSpace]);
const ConvolutionParam& param = nnvm::get<ConvolutionParam>(attrs.parsed);
NDArray weight = in_data[conv::kWeight];
MKLDNNConvForward &fwd = GetConvFwd(attrs, ctx.is_train, in_data[conv::kData], weight,
param.no_bias ? nullptr : &in_data[conv::kBias], out_data[conv::kOut]);
auto data_mem = in_data[conv::kData].GetMKLDNNDataReorder(fwd.fwd_pd.src_primitive_desc());
const mkldnn::memory *weight_mem;
if (ctx.is_train) {
// TODO(zhengda) kvstore doesn't handle MKLDNN correctly. Let's reorder it
// to the default format for now.
if (weight.IsMKLDNNData())
// This asks the engine to change the layout of the weight array after
// it's used.
weight.Reorder2DefaultAsync();
weight_mem = GetWeights(weight, fwd.fwd_pd.weights_primitive_desc(), param.num_group);
} else {
// For inference, we want to reorder the weight array so we don't need to
// reorder data every time.
if (weight.IsDefaultData()) {
weight_mem = GetWeights(weight, fwd.fwd_pd.weights_primitive_desc(), param.num_group);
// We also need to modify the layout on the original weight array. The
// data conversion happens after the weight array is used.
weight.MKLDNNDataReorderAsync(fwd.fwd_pd.weights_primitive_desc());
} else {
weight_mem = weight.GetMKLDNNData();
CHECK(weight_mem->get_primitive_desc() == fwd.fwd_pd.weights_primitive_desc());
}
}
auto out_mem = CreateMKLDNNMem(out_data[conv::kOut], fwd.fwd_pd.dst_primitive_desc(),
req[conv::kOut]);
const mkldnn::memory *bias_mem = nullptr;
if (!param.no_bias)
bias_mem = in_data[conv::kBias].GetMKLDNNDataReorder(fwd.fwd_pd.bias_primitive_desc());
fwd.SetNewMem(*data_mem, *weight_mem, bias_mem, *out_mem.second);
MKLDNNStream::Get()->RegisterPrim(fwd.GetFwd());
CommitOutput(out_data[conv::kOut], out_mem);
MKLDNNStream::Get()->Submit();
}
class MKLDNNConvBackward {
std::shared_ptr<mkldnn::convolution_backward_data> bwd_data;
std::shared_ptr<mkldnn::convolution_backward_weights> bwd_weight;
// conv::kData
std::shared_ptr<mkldnn::memory> out_grad;
std::shared_ptr<mkldnn::memory> in_grad;
std::shared_ptr<mkldnn::memory> weight;
// conv::kWeight
std::shared_ptr<mkldnn::memory> data;
std::shared_ptr<mkldnn::memory> output;
std::shared_ptr<mkldnn::memory> in_grad_weight;
std::shared_ptr<mkldnn::memory> in_grad_bias;
public:
mkldnn::convolution_backward_data::primitive_desc bwdData_pd;
mkldnn::convolution_backward_weights::primitive_desc bwdWeights_pd;
MKLDNNConvBackward(
const ConvolutionParam &param, const NDArray &data,
const NDArray &weights, const NDArray *bias, const NDArray &output,
const mkldnn::convolution_forward::primitive_desc &fwd_pd):
bwdData_pd(GetConvBwdData(param, data, weights, output, fwd_pd)),
bwdWeights_pd(GetConvBwdWeights(param, data, weights, bias, output, fwd_pd)) {
}
void SetDataNewMem(const mkldnn::memory &out_grad, const mkldnn::memory &weight,
const mkldnn::memory &in_grad) {
if (this->out_grad == nullptr)
this->out_grad = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdData_pd.diff_dst_primitive_desc(), out_grad.get_data_handle()));
else
this->out_grad->set_data_handle(out_grad.get_data_handle());
if (this->in_grad == nullptr)
this->in_grad = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdData_pd.diff_src_primitive_desc(), in_grad.get_data_handle()));
else
this->in_grad->set_data_handle(in_grad.get_data_handle());
if (this->weight == nullptr)
this->weight = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdData_pd.weights_primitive_desc(), weight.get_data_handle()));
else
this->weight->set_data_handle(weight.get_data_handle());
if (this->bwd_data == nullptr)
this->bwd_data = std::shared_ptr<mkldnn::convolution_backward_data>(
new mkldnn::convolution_backward_data(
this->bwdData_pd, mkldnn::primitive::at(*this->out_grad),
mkldnn::primitive::at(*this->weight), *this->in_grad));
}
void SetWeightNewMem(const mkldnn::memory &data,
const mkldnn::memory &out_grad,
const mkldnn::memory &in_grad_weight) {
if (this->data == nullptr)
this->data = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdWeights_pd.src_primitive_desc(), data.get_data_handle()));
else
this->data->set_data_handle(data.get_data_handle());
if (this->output == nullptr)
this->output = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdWeights_pd.diff_dst_primitive_desc(), out_grad.get_data_handle()));
else
this->output->set_data_handle(out_grad.get_data_handle());
if (this->in_grad_weight == nullptr)
this->in_grad_weight = std::shared_ptr<mkldnn::memory>(
new mkldnn::memory(bwdWeights_pd.diff_weights_primitive_desc(),
in_grad_weight.get_data_handle()));
else
this->in_grad_weight->set_data_handle(in_grad_weight.get_data_handle());
if (this->bwd_weight == nullptr)
this->bwd_weight = std::shared_ptr<mkldnn::convolution_backward_weights>(
new mkldnn::convolution_backward_weights(
this->bwdWeights_pd, mkldnn::primitive::at(*this->data),
mkldnn::primitive::at(*this->output), *this->in_grad_weight));
}
void SetWeightNewMem(const mkldnn::memory &data,
const mkldnn::memory &out_grad,
const mkldnn::memory &in_grad_weight,
const mkldnn::memory &in_grad_bias) {
if (this->data == nullptr)
this->data = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdWeights_pd.src_primitive_desc(), data.get_data_handle()));
else
this->data->set_data_handle(data.get_data_handle());
if (this->output == nullptr)
this->output = std::shared_ptr<mkldnn::memory>(new mkldnn::memory(
bwdWeights_pd.diff_dst_primitive_desc(), out_grad.get_data_handle()));
else
this->output->set_data_handle(out_grad.get_data_handle());
if (this->in_grad_weight == nullptr)
this->in_grad_weight = std::shared_ptr<mkldnn::memory>(
new mkldnn::memory(bwdWeights_pd.diff_weights_primitive_desc(),
in_grad_weight.get_data_handle()));
else
this->in_grad_weight->set_data_handle(in_grad_weight.get_data_handle());
if (this->in_grad_bias == nullptr)
this->in_grad_bias = std::shared_ptr<mkldnn::memory>(
new mkldnn::memory(bwdWeights_pd.diff_bias_primitive_desc(),
in_grad_bias.get_data_handle()));
else
this->in_grad_bias->set_data_handle(in_grad_bias.get_data_handle());
if (this->bwd_weight == nullptr)
this->bwd_weight = std::shared_ptr<mkldnn::convolution_backward_weights>(
new mkldnn::convolution_backward_weights(
this->bwdWeights_pd, mkldnn::primitive::at(*this->data),
mkldnn::primitive::at(*this->output), *this->in_grad_weight,
*this->in_grad_bias));
}
const mkldnn::convolution_backward_data &GetBwdData() const {
return *bwd_data;
}
const mkldnn::convolution_backward_weights &GetBwdWeights() const {
return *bwd_weight;
}
};
static inline MKLDNNConvBackward &GetConvBwd(
const nnvm::NodeAttrs &attrs, const NDArray &data, const NDArray &weights,
const NDArray *bias, const NDArray &output,
const mkldnn::convolution_forward::primitive_desc &fwd_pd) {
#if DMLC_CXX11_THREAD_LOCAL
static thread_local std::unordered_map<MKLDNNConvSignature, MKLDNNConvBackward, OpHash> bwds;
#else
static MX_THREAD_LOCAL std::unordered_map<MKLDNNConvSignature, MKLDNNConvBackward, OpHash> bwds;
#endif
const ConvolutionParam& param = nnvm::get<ConvolutionParam>(attrs.parsed);
MKLDNNConvSignature key(param);
// Here we can sign the conv op with NDArray because conv primitive will
// decide the right layout for the, so we only need to get the shape and the
// data type of the arrays.
key.AddSign(data);
key.AddSign(weights);
key.AddSign(output);
if (bias)
key.AddSign(*bias);
auto it = bwds.find(key);
if (it == bwds.end()) {
MKLDNNConvBackward bwd(param, data, weights, bias, output, fwd_pd);
auto ins_ret = bwds.insert(
std::pair<MKLDNNConvSignature, MKLDNNConvBackward>(key, bwd));
CHECK(ins_ret.second);
it = ins_ret.first;
}
return it->second;
}
void MKLDNNConvolutionBackward(const nnvm::NodeAttrs& attrs, const OpContext &ctx,
const std::vector<NDArray>& inputs,
const std::vector<OpReqType>& req,
const std::vector<NDArray>& outputs) {
TmpMemMgr::Get()->Init(ctx.requested[conv::kTempSpace]);
const std::vector<NDArray> &in_grad = outputs;
const ConvolutionParam& param = nnvm::get<ConvolutionParam>(attrs.parsed);
mkldnn::convolution_forward::primitive_desc fwd_pd = GetConvFwdImpl(param, ctx.is_train,
inputs[conv::kData + 1], inputs[conv::kWeight + 1],
param.no_bias ? nullptr : &inputs[conv::kBias + 1], inputs[conv::kOut]);
CHECK_NE(req[conv::kWeight], kWriteInplace) << "cannot write weight inplace";
MKLDNNConvBackward &convBwd = GetConvBwd(attrs, inputs[conv::kData + 1],
inputs[conv::kWeight + 1], nullptr, inputs[conv::kOut], fwd_pd);
auto out_grad_mem = inputs[conv::kOut].GetMKLDNNDataReorder(
convBwd.bwdData_pd.diff_dst_primitive_desc());
if (req[conv::kData]) {
auto weight_mem = GetWeights(inputs[conv::kWeight + 1],
convBwd.bwdData_pd.weights_primitive_desc(), param.num_group);
auto in_grad_mem = CreateMKLDNNMem(in_grad[conv::kData],
convBwd.bwdData_pd.diff_src_primitive_desc(), req[conv::kData]);
convBwd.SetDataNewMem(*out_grad_mem, *weight_mem, *in_grad_mem.second);
MKLDNNStream::Get()->RegisterPrim(convBwd.GetBwdData());
CommitOutput(in_grad[conv::kData], in_grad_mem);
}
if (req[conv::kWeight]) {
MKLDNNConvBackward &convBwdWeight = GetConvBwd(attrs, inputs[conv::kData + 1],
inputs[conv::kWeight + 1], param.no_bias ? nullptr : &inputs[conv::kBias + 1],
inputs[conv::kOut], fwd_pd);
if (convBwdWeight.bwdData_pd.diff_dst_primitive_desc() !=
convBwdWeight.bwdWeights_pd.diff_dst_primitive_desc())
out_grad_mem = inputs[conv::kOut].GetMKLDNNDataReorder(
convBwdWeight.bwdWeights_pd.diff_dst_primitive_desc());
auto data_mem = inputs[conv::kData + 1].GetMKLDNNDataReorder(
convBwdWeight.bwdWeights_pd.src_primitive_desc());
auto in_grad_weight = CreateMKLDNNWeightGrad(
in_grad[conv::kWeight],
convBwdWeight.bwdWeights_pd.diff_weights_primitive_desc(),
req[conv::kWeight]);
mkldnn_output_t in_grad_bias;
if (param.no_bias) {
convBwdWeight.SetWeightNewMem(*data_mem, *out_grad_mem,
*in_grad_weight.second);
MKLDNNStream::Get()->RegisterPrim(convBwdWeight.GetBwdWeights());
} else {
in_grad_bias = CreateMKLDNNMem(
in_grad[conv::kBias],
convBwdWeight.bwdWeights_pd.diff_bias_primitive_desc(), req[conv::kBias]);
convBwdWeight.SetWeightNewMem(*data_mem, *out_grad_mem,
*in_grad_weight.second, *in_grad_bias.second);
MKLDNNStream::Get()->RegisterPrim(convBwdWeight.GetBwdWeights());
CommitOutput(in_grad[conv::kBias], in_grad_bias);
}
CommitOutput(in_grad[conv::kWeight], in_grad_weight);
}
MKLDNNStream::Get()->Submit();
}
} // namespace op
} // namespace mxnet
#endif // MXNET_USE_MKLDNN == 1