/* Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved. Licensed 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. */ #include "boost/optional.hpp" #include "paddle/fluid/framework/data_layout_transform.h" #include "paddle/fluid/framework/op_registry.h" #include "paddle/fluid/memory/malloc.h" #include "paddle/fluid/operators/conv_op.h" #include "paddle/fluid/platform/mkldnn_reuse.h" namespace paddle { namespace operators { using Tensor = framework::Tensor; using framework::DataLayout; inline mkldnn::memory::dims GetWeightsTz(const Tensor* filter, const int groups) { auto iohw_weights_tz = framework::vectorize(filter->dims()); auto weights_tz = iohw_weights_tz; // IOHW -> OIHW weights_tz[0] = iohw_weights_tz[1]; weights_tz[1] = iohw_weights_tz[0]; int g = std::max(groups, 1); platform::GetGroupConvWeightsTz(weights_tz, g); return weights_tz; } template class ConvTransposeMKLDNNHandlerT : public platform::MKLDNNHandlerNoCachingT { public: ConvTransposeMKLDNNHandlerT(const framework::ExecutionContext& ctx, const mkldnn::engine mkldnn_engine, const Tensor* input, const Tensor* filter, const Tensor* bias, Tensor* output) : platform::MKLDNNHandlerNoCachingT( mkldnn_engine, ctx.GetPlace()), is_test_(ctx.Attr("is_test")) { PADDLE_ENFORCE_EQ(is_test_, true, platform::errors::InvalidArgument( "ConvTransposeMKLDNN works only for inference. " "The attribute \'is_test\' value should be set to " "True, but got is_test=False.")); PADDLE_ENFORCE_EQ( input->layout(), DataLayout::kMKLDNN, platform::errors::InvalidArgument( "Got wrong layout = %d for Input tensor.", input->layout())); PADDLE_ENFORCE_NE(input->format(), MKLDNNMemoryFormat::undef, platform::errors::InvalidArgument( "Got wrong format for Input tensor. The input " "format is undefined.")); PADDLE_ENFORCE_EQ( filter->layout(), DataLayout::kMKLDNN, platform::errors::InvalidArgument( "The filter tensor's laytout should be %d, but got %d.", DataLayout::kMKLDNN, filter->layout())); PADDLE_ENFORCE_NE(filter->format(), MKLDNNMemoryFormat::undef, platform::errors::InvalidArgument( "Got wrong formats for Filter tensor.")); PADDLE_ENFORCE_EQ( input->dims().size(), 4, platform::errors::InvalidArgument("Input must be with 4 dimensions, " "i.e. NCHW. but got dimension =%d", input->dims().size())); PADDLE_ENFORCE_EQ( filter->dims().size(), 4, platform::errors::InvalidArgument("Filter must be with 4 dimensions, " "i.e. OIHW, but got dimension =%d", filter->dims().size())); if (bias) { PADDLE_ENFORCE_EQ( bias->layout(), DataLayout::kMKLDNN, platform::errors::InvalidArgument( "The bias tensor's laytout should be %d, but got %d.", DataLayout::kMKLDNN, bias->layout())); PADDLE_ENFORCE_NE(bias->format(), MKLDNNMemoryFormat::undef, platform::errors::InvalidArgument( "Got wrong format for Bias tensor.")); PADDLE_ENFORCE_EQ( bias->dims().size(), 1, platform::errors::InvalidArgument("Bias must only have 1 dimension, " "i.e. X, but got dimension = %d .", bias->dims().size())); } std::vector strides_temp = ctx.Attr>("strides"); mkldnn::memory::dims strides(begin(strides_temp), end(strides_temp)); std::vector paddings_temp = ctx.Attr>("paddings"); mkldnn::memory::dims paddings(begin(paddings_temp), end(paddings_temp)); std::vector dilations_temp = ctx.Attr>("dilations"); mkldnn::memory::dims dilations(begin(dilations_temp), end(dilations_temp)); int groups = ctx.Attr("groups"); std::string padding_algorithm = ctx.Attr("padding_algorithm"); PADDLE_ENFORCE_EQ( strides.size(), 2, platform::errors::Unimplemented( "Now we only support 2d oneDNN convolution transpose op")); const auto& input_dims = input->dims(); const auto data_dims = framework::slice_ddim(input_dims, 2, input_dims.size()); const auto& filter_dims = filter->dims(); const auto filter_data_dims = framework::slice_ddim(filter_dims, 2, filter_dims.size()); const auto ksize = framework::vectorize(filter_data_dims); UpdatePaddingAndDilation(&paddings, &dilations, padding_algorithm, data_dims, strides, ksize); std::transform(dilations.begin(), dilations.end(), dilations.begin(), [](int64_t i) { return i - 1; }); const auto src_tz = framework::vectorize(input->dims()); const auto weights_tz = GetWeightsTz(filter, groups); const auto dst_tz = framework::vectorize(output->dims()); const auto mkldnn_paddings = platform::ToMkldnnPadding(paddings); /* create memory descriptor for convolution without specified format * ('any') which lets a primitive (convolution in this case) choose * the memory format preferred for best performance */ const auto chosen_memory_format = MKLDNNMemoryFormat::any; const std::string fuse_activation = ctx.Attr("fuse_activation"); const float fuse_alpha = ctx.Attr("fuse_alpha"); const float fuse_beta = ctx.Attr("fuse_beta"); auto data_type = mkldnn::memory::data_type::f32; if (ctx.Attr("mkldnn_data_type") == "bfloat16" || std::is_same::value) data_type = mkldnn::memory::data_type::bf16; const auto src_md = platform::MKLDNNMemDesc(src_tz, data_type, chosen_memory_format); const auto weights_md = platform::MKLDNNMemDesc(weights_tz, data_type, chosen_memory_format); const auto dst_md = platform::MKLDNNMemDesc( dst_tz, platform::MKLDNNGetDataType(), chosen_memory_format); const mkldnn::primitive_attr conv_trans_attr = CreatePostOps(fuse_activation, fuse_alpha, fuse_beta); auto fwd_prop_kind = is_test_ ? mkldnn::prop_kind::forward_inference : mkldnn::prop_kind::forward_training; if (bias) { std::vector bias_tz = framework::vectorize(bias->dims()); const auto bias_md = platform::MKLDNNMemDesc(bias_tz, data_type, MKLDNNMemoryFormat::x); this->AcquireForwardPrimitiveDescriptor( conv_trans_attr, fwd_prop_kind, dnnl::algorithm::deconvolution_direct, src_md, weights_md, bias_md, dst_md, strides, dilations, mkldnn_paddings[0], mkldnn_paddings[1]); } else { this->AcquireForwardPrimitiveDescriptor( conv_trans_attr, fwd_prop_kind, dnnl::algorithm::deconvolution_direct, src_md, weights_md, dst_md, strides, dilations, mkldnn_paddings[0], mkldnn_paddings[1]); } } mkldnn::primitive_attr CreatePostOps(const std::string& fuse_activation, const float& fuse_alpha, const float& fuse_beta) { mkldnn::primitive_attr conv_attr; mkldnn::post_ops post_operations; // Fusion with ReLU layer is executed through the PostOps feature. Create a // PostOps object and configure it to execute an eltwise relu operation. if (fuse_activation == "relu" || fuse_activation == "leaky_relu") { constexpr float scale = 1.0f; post_operations.append_eltwise(scale, mkldnn::algorithm::eltwise_relu, fuse_alpha, fuse_beta); } else if (fuse_activation == "relu6") { constexpr float scale = 1.0f; post_operations.append_eltwise(scale, mkldnn::algorithm::eltwise_bounded_relu, fuse_alpha, fuse_beta); } else if (fuse_activation == "swish") { constexpr float scale = 1.0f; post_operations.append_eltwise(scale, mkldnn::algorithm::eltwise_swish, fuse_alpha, fuse_beta); } conv_attr.set_post_ops(post_operations); return conv_attr; } std::shared_ptr AcquireSrcMemoryWithReorder( const framework::Tensor* input) { const T* input_data = input->data(); auto user_src_md = platform::MKLDNNMemDesc( framework::vectorize(input->dims()), platform::MKLDNNGetDataType(), input->format()); return platform::MKLDNNHandlerNoCachingT:: AcquireMemoryWithReorder(user_src_md, this->fwd_pd_->src_desc(), platform::to_void_cast(input_data)); } std::shared_ptr AcquireWeightsMemoryWithReorder( const platform::MKLDNNDeviceContext& dev_ctx, const std::string& key, const framework::Tensor* filter, const int& groups) { const K* filter_data = filter->data(); auto weights_tz = GetWeightsTz(filter, groups); int g = std::max(groups, 1); auto user_src_md = platform::MKLDNNMemDesc( weights_tz, platform::MKLDNNGetDataType(), (g == 1) ? filter->format() : MKLDNNMemoryFormat::goihw); auto iohw_weights_tz = framework::vectorize(filter->dims()); // Custom Reorder from IOHW to OIHW auto iohw2oihw_reorder = [&iohw_weights_tz](const K* filter_data) -> std::shared_ptr { int o = iohw_weights_tz[1]; int c = iohw_weights_tz[0]; int h = iohw_weights_tz[2]; int w = iohw_weights_tz[3]; std::shared_ptr reordered_filter_data(new K[o * c * h * w](), std::default_delete()); for (int i = 0; i < c; ++i) { for (int j = 0; j < o; ++j) { int in_offset = j * h * w + i * o * h * w; int out_offset = j * c * h * w + i * h * w; std::memcpy(&(reordered_filter_data.get())[out_offset], &filter_data[in_offset], h * w * sizeof(K)); } } return reordered_filter_data; }; return this->template AcquireMemoryWithReorder( dev_ctx, user_src_md, this->fwd_pd_->weights_desc(), platform::to_void_cast(filter_data), key, "@weights_mem_p", is_test_, iohw2oihw_reorder); } template std::shared_ptr AcquireMemoryWithReorder( const platform::MKLDNNDeviceContext& dev_ctx, const mkldnn::memory::desc& user_md, const mkldnn::memory::desc& target_md, void* ptr, const std::string& key, const std::string& suffix, bool is_persistent = false, std::function(const F*)> custom_reorder_func = {}, const std::vector& scale_data = {1.0f}, int mask = 0) { const auto target_key = key + suffix + "_target"; const auto key_reorder_p = key + suffix + "reorder_p"; const auto user_key = key + suffix + "_user"; auto target_memory_p = std::static_pointer_cast(dev_ctx.GetBlob(target_key)); if (target_memory_p == nullptr) { if (custom_reorder_func) { auto reordered_data = custom_reorder_func(reinterpret_cast(ptr)); dev_ctx.SetBlob(key_reorder_p + "-custom_reorder", reordered_data); ptr = reinterpret_cast(reordered_data.get()); } auto user_memory_p = std::make_shared(user_md, this->engine_, ptr); if (user_md != target_md) { target_memory_p = std::make_shared(target_md, this->engine_); dnnl::reorder::primitive_desc reorder_pdesc; if (platform::is_int8()) { dnnl::primitive_attr attr; attr.set_output_scales(mask, scale_data); reorder_pdesc = dnnl::reorder::primitive_desc(*user_memory_p, *target_memory_p, attr); } else { reorder_pdesc = dnnl::reorder::primitive_desc(*user_memory_p, *target_memory_p); } auto reorder_p = std::make_shared(reorder_pdesc); dev_ctx.SetBlob(key_reorder_p, reorder_p); auto& astream = platform::MKLDNNDeviceContext::tls().get_stream(); platform::RecordEvent record_reorder("int_reorder", platform::EventRole::kUniqueOp); reorder_p->execute(astream, {{MKLDNN_ARG_FROM, *user_memory_p}, {MKLDNN_ARG_TO, *target_memory_p}}); astream.wait(); } else { target_memory_p = user_memory_p; } dev_ctx.SetBlob(user_key, user_memory_p); dev_ctx.SetBlob(target_key, target_memory_p); } else if (!is_persistent) { auto& astream = platform::MKLDNNDeviceContext::tls().get_stream(); auto user_memory_p = std::static_pointer_cast(dev_ctx.GetBlob(user_key)); user_memory_p->set_data_handle(ptr); // TODO(jczaja): Here we detect if reorder is cached it means it is needed // need to change this to get rid of keys auto reorder_p = std::static_pointer_cast( dev_ctx.GetBlob(key_reorder_p)); if (reorder_p != nullptr) { platform::RecordEvent record_reorder("int_reorder", platform::EventRole::kUniqueOp); reorder_p->execute(astream, {{MKLDNN_ARG_FROM, *user_memory_p}, {MKLDNN_ARG_TO, *target_memory_p}}); astream.wait(); } } return target_memory_p; } std::shared_ptr AcquireBiasMemoryWithReorder( const platform::MKLDNNDeviceContext& dev_ctx, const std::string& key, const framework::Tensor* bias) { const K* bias_data = bias->data(); auto user_bias_md = platform::MKLDNNMemDesc( framework::vectorize(bias->dims()), platform::MKLDNNGetDataType(), MKLDNNMemoryFormat::x); return this->AcquireMemoryWithReorder( dev_ctx, user_bias_md, this->fwd_pd_->bias_desc(), platform::to_void_cast(bias_data), key, "@bias_mem_p", is_test_); } private: const bool is_test_; }; template class ConvTransposeMKLDNNOpKernel : public framework::OpKernel { public: void Compute(const framework::ExecutionContext& ctx) const override { PADDLE_ENFORCE_EQ(platform::is_cpu_place(ctx.GetPlace()), true, platform::errors::PreconditionNotMet( "Operator DNNL ConvTranspose must use CPUPlace")); const bool is_bfloat16 = ctx.Attr("mkldnn_data_type") == "bfloat16"; const bool force_fp32_output = ctx.Attr("force_fp32_output"); if (is_bfloat16) { if (force_fp32_output) Execute(ctx); else Execute(ctx); } else { Execute(ctx); } } template void Execute(const framework::ExecutionContext& ctx) const { auto& dev_ctx = ctx.template device_context(); const auto& mkldnn_engine = dev_ctx.GetEngine(); const auto* input = ctx.Input("Input"); const auto* filter = ctx.Input("Filter"); const auto* bias = ctx.HasInput("Bias") ? ctx.Input("Bias") : nullptr; auto* output = ctx.Output("Output"); ConvTransposeMKLDNNHandlerT handler(ctx, mkldnn_engine, input, filter, bias, output); auto src_memory_p = handler.AcquireSrcMemoryWithReorder(input); // Caching Key for weights is needed std::string key = platform::CreateKey(dev_ctx, ctx.InputName("Input"), ctx.InputName("Filter"), (bias ? ctx.InputName("Bias") : "")); key = platform::ExtendKeyWithThreadInfoIfNeeded(dev_ctx, key); auto weights_memory_p = handler.AcquireWeightsMemoryWithReorder( dev_ctx, key, filter, ctx.Attr("groups")); std::shared_ptr dst_memory_p = handler.template AcquireDstMemory(output); auto conv_p = handler.AcquireForwardPrimitive(); std::unordered_map args = { {MKLDNN_ARG_SRC, *src_memory_p}, {MKLDNN_ARG_WEIGHTS, *weights_memory_p}, {MKLDNN_ARG_DST, *dst_memory_p}}; if (bias) { auto bias_memory_p = handler.AcquireBiasMemoryWithReorder(dev_ctx, key, bias); args.insert({MKLDNN_ARG_BIAS, *bias_memory_p}); } auto& astream = platform::MKLDNNDeviceContext::tls().get_stream(); conv_p->execute(astream, args); astream.wait(); output->set_layout(DataLayout::kMKLDNN); output->set_format(platform::GetMKLDNNFormat(*dst_memory_p)); } }; } // namespace operators } // namespace paddle namespace ops = paddle::operators; REGISTER_OP_KERNEL( conv2d_transpose, MKLDNN, ::paddle::platform::CPUPlace, ops::ConvTransposeMKLDNNOpKernel, ops::ConvTransposeMKLDNNOpKernel);