Files
paddlepaddle--paddle/paddle/phi/kernels/onednn/conv_transpose_kernel.cc
T
2026-07-13 12:40:42 +08:00

645 lines
25 KiB
C++

// Copyright (c) 2022 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 "paddle/phi/kernels/conv_transpose_kernel.h"
#include "paddle/phi/backends/onednn/onednn_helper.h"
#include "paddle/phi/backends/onednn/onednn_reuse.h"
#include "paddle/phi/core/compat/get_kerneltype_forvar_utils.h"
#include "paddle/phi/core/expect.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/cpu/conv_util.h"
#include "paddle/phi/kernels/funcs/data_layout_transform.h"
namespace phi {
struct DeconvolutionCache {
dnnl::deconvolution_forward deconvolution_forward;
dnnl::memory src_mem;
dnnl::memory weights_mem;
dnnl::memory bias_mem;
dnnl::memory dst_mem;
};
inline dnnl::memory::dims GetWeightsTz(const DenseTensor* filter,
const int groups) {
auto weights_tz = vectorize(filter->dims());
int g = std::max(groups, 1);
int g_dim = (g > 1) ? 1 : 0;
funcs::GetGroupConvWeightsTz(weights_tz, g);
// gIOHW -> gOIHW || IOHW -> OIHW
std::swap(weights_tz[g_dim + 0], weights_tz[g_dim + 1]);
return weights_tz;
}
template <typename T, typename K, typename T_out>
class ConvTransposeOneDNNHandlerT
: public funcs::OneDNNHandlerNoCachingT<T, dnnl::deconvolution_forward> {
private:
const bool is_test_;
public:
ConvTransposeOneDNNHandlerT(const OneDNNContext& dev_ctx,
const DenseTensor* x,
const DenseTensor* filter,
const DenseTensor* bias,
const std::vector<int>& strides_in,
const std::vector<int>& paddings_in,
const std::string& padding_algorithm,
int groups,
const std::vector<int>& dilations_in,
DenseTensor* out)
: funcs::OneDNNHandlerNoCachingT<T, dnnl::deconvolution_forward>(
dev_ctx.GetEngine(), dev_ctx.GetPlace()),
is_test_(dev_ctx.HasDnnAttr("is_test")
? PADDLE_GET_CONST(bool, dev_ctx.GetDnnAttr("is_test"))
: false) {
PADDLE_ENFORCE_EQ(is_test_,
true,
common::errors::InvalidArgument(
"ConvTransposeOneDNN works only for inference. "
"The attribute \'is_test\' value should be set to "
"True, but got is_test=False."));
PADDLE_ENFORCE_EQ(
x->layout(),
DataLayout::ONEDNN,
common::errors::InvalidArgument(
"Got wrong layout = %d for Input tensor.", x->layout()));
PADDLE_ENFORCE_EQ(
filter->layout(),
DataLayout::ONEDNN,
common::errors::InvalidArgument(
"The filter tensor's layout should be %d, but got %d.",
DataLayout::ONEDNN,
filter->layout()));
PADDLE_ENFORCE_EQ(
x->dims().size(),
4,
common::errors::InvalidArgument("Input must be with 4 dimensions, "
"i.e. NCHW. but got dimension =%d",
x->dims().size()));
PADDLE_ENFORCE_EQ(
filter->dims().size(),
4,
common::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::ONEDNN,
common::errors::InvalidArgument(
"The bias tensor's laytout should be %d, but got %d.",
DataLayout::ONEDNN,
bias->layout()));
PADDLE_ENFORCE_EQ(
bias->dims().size(),
1,
common::errors::InvalidArgument("Bias must only have 1 dimension, "
"i.e. X, but got dimension = %d .",
bias->dims().size()));
}
dnnl::memory::dims strides(begin(strides_in), end(strides_in));
dnnl::memory::dims paddings(begin(paddings_in), end(paddings_in));
dnnl::memory::dims dilations(begin(dilations_in), end(dilations_in));
PADDLE_ENFORCE_EQ(
strides.size(),
2,
common::errors::Unimplemented(
"Now we only support 2d oneDNN convolution transpose op"));
const auto x_dims = x->dims();
const auto x_data_dims = slice_ddim(x_dims, 2, x_dims.size());
const auto filter_dims = filter->dims();
const auto filter_data_dims =
slice_ddim(filter_dims, 2, filter_dims.size());
const auto ksize = vectorize(filter_data_dims);
UpdatePaddingAndDilation(
&paddings, &dilations, padding_algorithm, x_data_dims, strides, ksize);
std::transform(
dilations.begin(), dilations.end(), dilations.begin(), [](int64_t i) {
return i - 1;
});
const auto src_tz = vectorize(x->dims());
const auto weights_tz = GetWeightsTz(filter, groups);
const auto dst_tz = vectorize(out->dims());
const auto onednn_paddings = funcs::ToOneDNNPadding(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
*/
auto chosen_memory_format = funcs::OneDNNMemoryFormat::any;
auto data_type = dnnl::memory::data_type::f32;
const bool is_bfloat16 =
dev_ctx.HasDnnAttr("mkldnn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("mkldnn_data_type")) ==
"bfloat16"
: false;
const bool is_onednn_BFLOAT16 =
dev_ctx.HasDnnAttr("onednn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("onednn_data_type")) ==
"bfloat16"
: is_bfloat16;
if (is_onednn_BFLOAT16 || std::is_same<T_out, dtype::bfloat16>::value) {
data_type = dnnl::memory::data_type::bf16;
}
const auto src_md =
funcs::OneDNNMemDesc(src_tz, data_type, chosen_memory_format);
const auto weights_md =
funcs::OneDNNMemDesc(weights_tz, data_type, chosen_memory_format);
const auto dst_md = funcs::OneDNNMemDesc(
dst_tz, funcs::OneDNNGetDataType<T_out>(), chosen_memory_format);
auto fwd_prop_kind = is_test_ ? dnnl::prop_kind::forward_inference
: dnnl::prop_kind::forward_training;
if (bias) {
std::vector<int64_t> bias_tz = vectorize(bias->dims());
const auto bias_md = funcs::OneDNNMemDesc(
bias_tz, data_type, funcs::OneDNNMemoryFormat::x);
this->AcquireForwardPrimitiveDescriptor(
fwd_prop_kind,
dnnl::algorithm::deconvolution_direct,
src_md,
weights_md,
bias_md,
dst_md,
strides,
dilations,
onednn_paddings[0],
onednn_paddings[1]);
} else {
this->AcquireForwardPrimitiveDescriptor(
fwd_prop_kind,
dnnl::algorithm::deconvolution_direct,
src_md,
weights_md,
dst_md,
strides,
dilations,
onednn_paddings[0],
onednn_paddings[1]);
}
}
std::shared_ptr<dnnl::memory> AcquireSrcMemoryWithReorder(
const DenseTensor* x) {
const T* input_data = x->data<T>();
return funcs::OneDNNHandlerNoCachingT<T, dnnl::deconvolution_forward>::
AcquireMemoryWithReorder(x->mem_desc(),
this->fwd_pd_->src_desc(),
funcs::to_void_cast<T>(input_data));
}
std::shared_ptr<dnnl::memory> AcquireWeightsMemoryWithReorder(
const OneDNNContext& dev_ctx,
const std::string& key,
const DenseTensor* filter,
const int& groups) {
const K* filter_data = filter->data<K>();
auto weights_tz = GetWeightsTz(filter, groups);
int g = std::max(groups, 1);
auto user_src_md =
funcs::OneDNNMemDesc(weights_tz,
funcs::OneDNNGetDataType<K>(),
(g == 1) ? funcs::OneDNNMemoryFormat::iohw
: funcs::OneDNNMemoryFormat::giohw);
return this->template AcquireMemoryWithReorder<K>(
dev_ctx,
user_src_md,
this->fwd_pd_->weights_desc(),
funcs::to_void_cast<K>(filter_data),
key,
"@weights_mem_p",
is_test_);
}
template <typename F = T>
std::shared_ptr<dnnl::memory> AcquireMemoryWithReorder(
const OneDNNContext& dev_ctx,
const dnnl::memory::desc& user_md,
const dnnl::memory::desc& target_md,
void* ptr,
const std::string& key,
const std::string& suffix,
bool is_persistent = false,
const std::vector<float>& 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<dnnl::memory>(dev_ctx.GetBlob(target_key));
if (target_memory_p == nullptr) {
auto user_memory_p =
std::make_shared<dnnl::memory>(user_md, this->engine_, ptr);
if (user_md != target_md) {
target_memory_p =
std::make_shared<dnnl::memory>(target_md, this->engine_);
dnnl::reorder::primitive_desc reorder_pdesc;
if (funcs::is_int8<T>()) {
dnnl::primitive_attr attr;
attr.set_scales_mask(DNNL_ARG_DST, mask);
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<dnnl::reorder>(reorder_pdesc);
dev_ctx.SetBlob(key_reorder_p, reorder_p);
auto& astream = OneDNNContext::tls().get_stream();
std::unordered_map<int, dnnl::memory> reorder_args;
reorder_args.insert({DNNL_ARG_SRC, *user_memory_p});
reorder_args.insert({DNNL_ARG_DST, *target_memory_p});
if (funcs::is_int8<T>()) {
auto scale_md =
dnnl::memory::desc({static_cast<int64_t>(scale_data.size())},
dnnl::memory::data_type::f32,
dnnl::memory::format_tag::x);
auto scale_data_mem = dnnl::memory(scale_md, this->engine_);
scale_data_mem.set_data_handle(
funcs::to_void_cast(scale_data.data()));
reorder_args.insert(
{DNNL_ARG_ATTR_SCALES | DNNL_ARG_DST, scale_data_mem});
}
reorder_p->execute(astream, reorder_args);
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 = OneDNNContext::tls().get_stream();
auto user_memory_p =
std::static_pointer_cast<dnnl::memory>(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<dnnl::reorder>(
dev_ctx.GetBlob(key_reorder_p));
if (reorder_p != nullptr) {
reorder_p->execute(
astream,
{{DNNL_ARG_FROM, *user_memory_p}, {DNNL_ARG_TO, *target_memory_p}});
astream.wait();
}
}
return target_memory_p;
}
std::shared_ptr<dnnl::memory> AcquireBiasMemoryWithReorder(
const OneDNNContext& dev_ctx,
const std::string& key,
const DenseTensor* bias) {
const K* bias_data = bias->data<K>();
auto user_bias_md = funcs::OneDNNMemDesc(vectorize(bias->dims()),
funcs::OneDNNGetDataType<K>(),
funcs::OneDNNMemoryFormat::x);
return this->AcquireMemoryWithReorder(dev_ctx,
user_bias_md,
this->fwd_pd_->bias_desc(),
funcs::to_void_cast<K>(bias_data),
key,
"@bias_mem_p",
is_test_);
}
};
template <typename T>
void PrepareSrcMem(const std::shared_ptr<dnnl::deconvolution_forward>& fc_p
UNUSED,
const std::shared_ptr<dnnl::memory>& src_mem,
const DenseTensor* x,
const dnnl::engine& engine) {
auto x_md = x->mem_desc().reshape(src_mem->get_desc().get_dims());
if (x_md != src_mem->get_desc()) {
dnnl::memory x_mem(x_md, engine, funcs::to_void_cast<T>(x->data<T>()));
auto reorder_p = dnnl::reorder(x_mem, *src_mem);
auto& astream = OneDNNContext::tls().get_stream();
reorder_p.execute(astream, x_mem, *src_mem);
astream.wait();
} else {
src_mem->set_data_handle(funcs::to_void_cast<T>(x->data<T>()));
}
}
template <typename T, typename T_out>
void Execute(const OneDNNContext& dev_ctx,
const DenseTensor* x,
const DenseTensor* filter,
const DenseTensor* bias,
const std::vector<int>& strides,
const std::vector<int>& paddings,
const std::string& padding_algorithm,
int groups,
const std::vector<int>& dilations,
DenseTensor* out) {
std::shared_ptr<dnnl::deconvolution_forward> conv_p;
std::shared_ptr<dnnl::memory> src_memory_p;
std::shared_ptr<dnnl::memory> weights_memory_p;
std::shared_ptr<dnnl::memory> bias_memory_p;
std::shared_ptr<dnnl::memory> dst_memory_p;
std::unordered_map<int, dnnl::memory> args;
// Note(ZKK):
// Add thread_id to cache_key
// fix issue https://github.com/PaddlePaddle/PaddleOCR/issues/15621
// https://github.com/PaddlePaddle/PaddleOCR/issues/15393
std::string cache_key = funcs::CreateKey(dev_ctx,
funcs::ThreadIDasStr(),
dev_ctx.GetInputsName("Input")[0],
dev_ctx.GetInputsName("Filter")[0],
vectorize(x->dims()),
vectorize(filter->dims()));
const auto& onednn_engine = dev_ctx.GetEngine();
auto deconvolution_cache =
std::static_pointer_cast<DeconvolutionCache>(dev_ctx.GetBlob(cache_key));
if (deconvolution_cache) {
conv_p = std::make_shared<dnnl::deconvolution_forward>(
deconvolution_cache->deconvolution_forward);
src_memory_p = std::make_shared<dnnl::memory>(deconvolution_cache->src_mem);
PrepareSrcMem<T>(conv_p, src_memory_p, x, onednn_engine);
weights_memory_p =
std::make_shared<dnnl::memory>(deconvolution_cache->weights_mem);
dst_memory_p = std::make_shared<dnnl::memory>(deconvolution_cache->dst_mem);
auto out_ptr =
dev_ctx.template Alloc<T_out>(out, dst_memory_p->get_desc().get_size());
dst_memory_p->set_data_handle(out_ptr);
args.insert({DNNL_ARG_SRC, *src_memory_p});
args.insert({DNNL_ARG_WEIGHTS, *weights_memory_p});
args.insert({DNNL_ARG_DST, *dst_memory_p});
if (bias) {
bias_memory_p =
std::make_shared<dnnl::memory>(deconvolution_cache->bias_mem);
args.insert({DNNL_ARG_BIAS, *bias_memory_p});
}
} else {
// Check if bias obey the rules
if (bias) {
PADDLE_ENFORCE_EQ(
bias->layout(),
DataLayout::ONEDNN,
common::errors::InvalidArgument(
"The Bias tensor's layout should be %d, but got %d.",
DataLayout::ONEDNN,
bias->layout()));
PADDLE_ENFORCE_EQ(
bias->dims().size(),
1,
common::errors::InvalidArgument("Bias must only have 1 dimension, "
"i.e. X, but got dimension = %d .",
bias->dims().size()));
}
// Caching Key for weights is needed
std::string key =
funcs::CreateKey(dev_ctx,
dev_ctx.GetInputsName("Input")[0],
dev_ctx.GetInputsName("Filter")[0],
(bias ? dev_ctx.GetInputsName("Bias")[0] : ""));
ConvTransposeOneDNNHandlerT<T, float, T_out> handler(dev_ctx,
x,
filter,
bias,
strides,
paddings,
padding_algorithm,
groups,
dilations,
out);
src_memory_p = handler.AcquireSrcMemoryWithReorder(x);
key = funcs::ExtendKeyWithThreadInfoIfNeeded(dev_ctx, key);
weights_memory_p =
handler.AcquireWeightsMemoryWithReorder(dev_ctx, key, filter, groups);
dst_memory_p = handler.template AcquireDstMemory<T_out>(out);
conv_p = handler.AcquireForwardPrimitive();
args.insert({DNNL_ARG_SRC, *src_memory_p});
args.insert({DNNL_ARG_WEIGHTS, *weights_memory_p});
args.insert({DNNL_ARG_DST, *dst_memory_p});
if (bias) {
bias_memory_p = handler.AcquireBiasMemoryWithReorder(dev_ctx, key, bias);
args.insert({DNNL_ARG_BIAS, *bias_memory_p});
}
auto cache = std::make_shared<DeconvolutionCache>();
cache->deconvolution_forward = *conv_p;
cache->src_mem = *src_memory_p;
cache->weights_mem = *weights_memory_p;
cache->dst_mem = *dst_memory_p;
if (bias) {
cache->bias_mem = *bias_memory_p;
}
dev_ctx.SetBlob(cache_key, cache);
}
auto& astream = OneDNNContext::tls().get_stream();
conv_p->execute(astream, args);
astream.wait();
out->set_mem_desc(dst_memory_p->get_desc());
}
template <typename T, typename Context>
void Conv2dTransposeKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& filter,
const std::vector<int>& strides,
const std::vector<int>& paddings,
const std::vector<int>& output_padding UNUSED,
const IntArray& output_size UNUSED,
const std::string& padding_algorithm,
int groups,
const std::vector<int>& dilations,
const std::string& data_format UNUSED,
DenseTensor* out) {
const bool is_bfloat16 =
dev_ctx.HasDnnAttr("mkldnn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("mkldnn_data_type")) ==
"bfloat16"
: false;
const bool is_onednn_BFLOAT16 =
dev_ctx.HasDnnAttr("onednn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("onednn_data_type")) ==
"bfloat16"
: is_bfloat16;
const bool force_fp32_output =
dev_ctx.HasDnnAttr("force_fp32_output")
? PADDLE_GET_CONST(bool, dev_ctx.GetDnnAttr("force_fp32_output"))
: false;
const bool use_bfloat16 = (!force_fp32_output && is_onednn_BFLOAT16);
if (use_bfloat16) {
Execute<T, dtype::bfloat16>(dev_ctx,
&x,
&filter,
nullptr,
strides,
paddings,
padding_algorithm,
groups,
dilations,
out);
} else {
Execute<T, float>(dev_ctx,
&x,
&filter,
nullptr,
strides,
paddings,
padding_algorithm,
groups,
dilations,
out);
}
}
template <typename T, typename Context>
void Conv2dTransposeBiasKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& filter,
const optional<DenseTensor>& bias,
const std::vector<int>& strides,
const std::vector<int>& paddings,
const std::vector<int>& output_padding UNUSED,
const IntArray& output_size UNUSED,
const std::string& padding_algorithm,
int groups,
const std::vector<int>& dilations,
const std::string& data_format UNUSED,
DenseTensor* out) {
const bool is_bfloat16 =
dev_ctx.HasDnnAttr("mkldnn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("mkldnn_data_type")) ==
"bfloat16"
: false;
const bool is_one_BFLOAT16 =
dev_ctx.HasDnnAttr("onednn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("onednn_data_type")) ==
"bfloat16"
: is_bfloat16;
const bool force_fp32_output =
dev_ctx.HasDnnAttr("force_fp32_output")
? PADDLE_GET_CONST(bool, dev_ctx.GetDnnAttr("force_fp32_output"))
: false;
const bool use_bfloat16 = (!force_fp32_output && is_one_BFLOAT16);
if (use_bfloat16) {
Execute<T, dtype::bfloat16>(dev_ctx,
&x,
&filter,
bias.get_ptr(),
strides,
paddings,
padding_algorithm,
groups,
dilations,
out);
} else {
Execute<T, float>(dev_ctx,
&x,
&filter,
bias.get_ptr(),
strides,
paddings,
padding_algorithm,
groups,
dilations,
out);
}
}
KernelKey ConvTransposeGetKernelTypeForVar(
const GetKernelTypeForVarContext* ctx) {
const std::string& var_name = ctx->GetVarName();
const DenseTensor& tensor = ctx->GetTensor();
const KernelKey& expected_kernel_type = ctx->GetKernelKey();
const AttributeMap& attrs = ctx->GetAttrs();
// Only input require reshaping, weights and
// bias are having shape in NCHW order
if ((var_name == "Input") &&
(expected_kernel_type.layout() == DataLayout::ONEDNN) &&
(tensor.layout() != DataLayout::ONEDNN)) {
auto it = attrs.find("data_format");
const std::string data_format = PADDLE_GET_CONST(std::string, it->second);
auto dl = StringToDataLayout(data_format);
// Some models may have intentionally set "AnyLayout" for pool
// op. Treat this as NCHW (default data_format value)
if (dl != DataLayout::ANY) {
return phi::KernelKey(tensor.place(), dl, expected_kernel_type.dtype());
}
}
return phi::KernelKey(
tensor.place(), tensor.layout(), expected_kernel_type.dtype());
}
} // namespace phi
PD_REGISTER_KERNEL(conv2d_transpose,
OneDNN,
ONEDNN,
phi::Conv2dTransposeKernel,
float,
phi::bfloat16) {
kernel->get_kerneltype_forvar_fn_ = phi::ConvTransposeGetKernelTypeForVar;
}
PD_REGISTER_KERNEL(conv2d_transpose_bias,
OneDNN,
ONEDNN,
phi::Conv2dTransposeBiasKernel,
float,
phi::bfloat16) {
kernel->get_kerneltype_forvar_fn_ = phi::ConvTransposeGetKernelTypeForVar;
}