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

655 lines
25 KiB
C++

// Copyright (c) 2023 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/common/errors.h"
#include "paddle/phi/backends/onednn/onednn_reuse.h"
#include "paddle/phi/core/compat/convert_utils.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/expect.h"
#include "paddle/phi/core/utils/data_type.h"
#include "paddle/phi/core/kernel_registry.h"
namespace phi::fusion {
using funcs::CreateKey;
using funcs::OneDNNGetDataType;
using funcs::OneDNNMemDesc;
using funcs::RNNReorderType;
using phi::OneDNNContext;
using OneDNNMemoryFormat = dnnl::memory::format_tag;
template <typename T, typename T_out = T>
class GRUOneDNNHandler : public funcs::OneDNNHandlerT<T, dnnl::gru_forward> {
public:
GRUOneDNNHandler(const OneDNNContext& dev_ctx,
const dnnl::engine onednn_engine,
phi::Place cpu_place UNUSED,
const DenseTensor* input,
const DenseTensor* weight_h,
const DenseTensor* h0,
const bool is_reverse,
const float scale_data,
const float shift_data,
const std::string& gate_activation,
const std::string& activation,
const std::vector<float>& scale_weights,
const int64_t N,
const int64_t Ti,
const int64_t IC,
const int64_t OC)
: funcs::OneDNNHandlerT<T, dnnl::gru_forward>(
dev_ctx,
dev_ctx.GetEngine(),
cpu_place,
CreateKey(dev_ctx,
dev_ctx.GetInputsName("X")[0] +
dev_ctx.GetInputsName("WeightH")[0],
OneDNNGetDataType<T>(),
Ti)),
N(N),
Ti(Ti),
IC(IC),
OC(OC),
G(3) {
std::string unique_name =
dev_ctx.GetInputsName("X")[0] + dev_ctx.GetInputsName("WeightH")[0];
// Create memory key without Ti because weights, bias and h0 memories
// do not depend on Ti size but primitive and input/output memory do
memory_key_ = funcs::ExtendKeyWithThreadInfoIfNeeded(
dev_ctx, CreateKey(dev_ctx, unique_name, OneDNNGetDataType<T>()));
// Is it int8 kernel
const bool is_INT8 = std::is_same<T, uint8_t>::value;
if (is_INT8) {
const int weights_scale_mask =
0 +
(1 << 3) // bit, indicating the unique scales for `g` dim in `ldigo`
+
(1 << 4); // bit, indicating the unique scales for `o` dim in `ldigo`
attr_.set_rnn_data_qparams(scale_data, shift_data);
attr_.set_rnn_weights_qparams(weights_scale_mask, scale_weights);
}
if (unlikely(!this->isCached())) {
// oneDNN kernel has hardcoded activation functions
PADDLE_ENFORCE_EQ(
gate_activation,
"sigmoid",
common::errors::Unimplemented(
"oneDNN fusion_gru supports only sigmoid as a gate activation."));
PADDLE_ENFORCE_EQ(
activation,
"tanh",
common::errors::Unimplemented(
"oneDNN fusion_gru supports only tanh as an activation."));
// Weights for int8 kernel are of a type s8
const auto weights_dt =
is_INT8 ? dnnl::memory::data_type::s8 : OneDNNGetDataType<T>();
// oneDNN RNN dimensions
const int64_t D = 1; // Directions
const int64_t L = 1; // Layers (PP supports only 1 stacked layer)
const int64_t G = 3; // Number of Gates, 3 for GRU
// Create memory descriptors
auto input_md = OneDNNMemDesc(
{Ti, N, IC}, OneDNNGetDataType<T>(), OneDNNMemoryFormat::any);
auto weight_x_md =
OneDNNMemDesc({L, D, IC, G, OC}, weights_dt, OneDNNMemoryFormat::any);
auto weight_h_md =
OneDNNMemDesc({L, D, OC, G, OC}, weights_dt, OneDNNMemoryFormat::any);
auto bias_md = OneDNNMemDesc(
{L, D, G, OC}, OneDNNGetDataType<float>(), OneDNNMemoryFormat::ldgo);
auto hidden_md = OneDNNMemDesc(
{Ti, N, OC}, OneDNNGetDataType<T_out>(), OneDNNMemoryFormat::any);
auto h0_md = OneDNNMemDesc(
{L, D, N, OC}, OneDNNGetDataType<T>(), OneDNNMemoryFormat::ldnc);
// Create GRU oneDNN primitive
const auto direction =
is_reverse ? dnnl::rnn_direction::unidirectional_right2left
: dnnl::rnn_direction::unidirectional_left2right;
this->AcquireForwardPrimitiveDescriptor(
this->attr_,
dnnl::prop_kind::forward_inference,
direction,
input_md,
h0_md,
weight_x_md,
weight_h_md,
bias_md,
hidden_md,
dnnl::memory::desc());
}
}
bool is_NTC() { return this->is_NTC(this->fwd_pd_->dst_desc()); }
bool is_NTC(const dnnl::memory::desc& md) {
auto ntc_md = dnnl::memory::desc(
md.get_dims(), md.get_data_type(), dnnl::memory::format_tag::ntc);
return md == ntc_md;
}
void reorderRNNdata(void* input_data,
void* output_data,
std::vector<size_t> lod,
const bool is_reverse,
RNNReorderType reorder_type) {
switch (reorder_type) {
// Reorder input memory [WORDS, C] + LoD -> [N, T, C]
case RNNReorderType::PP_NTC: {
auto* input_data_iter = reinterpret_cast<T*>(input_data);
auto* output_data_iter = reinterpret_cast<T*>(output_data);
for (int n = 0; n < N; ++n) {
const auto num_elements = (lod[n + 1] - lod[n]) * IC;
const auto offset = is_reverse ? (Ti * IC - num_elements) : 0;
memcpy(output_data_iter + n * Ti * IC + offset,
input_data_iter,
sizeof(T) * num_elements);
input_data_iter += num_elements;
}
} break;
// Reorder input memory [WORDS, C] + LoD -> [T, N, C]
case RNNReorderType::PP_TNC: {
auto* input_data_iter = reinterpret_cast<T*>(input_data);
auto* output_data_iter = reinterpret_cast<T*>(output_data);
for (int n = 0; n < N; ++n) {
const auto num_elements = (lod[n + 1] - lod[n]);
const auto offset = is_reverse ? (Ti - num_elements) : 0;
for (size_t t = 0; t < num_elements; ++t) {
memcpy(output_data_iter + (t + offset) * N * IC + n * IC,
input_data_iter,
sizeof(T) * IC);
input_data_iter += IC;
}
}
} break;
// Reorder output values to PP format [N, T, C] -> [WORDS, C]
case RNNReorderType::NTC_PP: {
auto* input_data_iter = reinterpret_cast<T_out*>(input_data);
auto* output_data_iter = reinterpret_cast<T_out*>(output_data);
for (int n = 0; n < N; ++n) {
const auto num_elements = (lod[n + 1] - lod[n]) * OC;
const auto offset = is_reverse ? (Ti * OC - num_elements) : 0;
memcpy(output_data_iter,
input_data_iter + n * Ti * OC + offset,
sizeof(T_out) * num_elements);
output_data_iter += num_elements;
}
} break;
// Reorder output values to PP format [T, N, C] -> [WORDS, C]
case RNNReorderType::TNC_PP: {
auto* input_data_iter = reinterpret_cast<T_out*>(input_data);
auto* output_data_iter = reinterpret_cast<T_out*>(output_data);
for (int n = 0; n < N; ++n) {
const auto num_elements = lod[n + 1] - lod[n];
const auto offset = is_reverse ? (Ti - num_elements) : 0;
for (size_t t = 0; t < num_elements; ++t) {
memcpy(output_data_iter,
input_data_iter + (t + offset) * N * OC + n * OC,
sizeof(T_out) * OC);
output_data_iter += OC;
}
}
} break;
}
}
std::shared_ptr<dnnl::memory> AcquireInputMemoryWithReorder(
const DenseTensor* input, const bool is_reverse) {
const auto name = this->key_ + "@input_mem";
auto memory_p =
std::static_pointer_cast<dnnl::memory>(this->dev_ctx_.GetBlob(name));
if (!memory_p) {
memory_p = std::make_shared<dnnl::memory>(this->fwd_pd_->src_desc(),
this->engine_);
this->dev_ctx_.SetBlob(name, memory_p);
}
const auto& input_lod = input->lod()[0];
auto* x_data = funcs::to_void_cast(input->data<T>());
auto* x_onednn_data = memory_p->get_data_handle();
memset(x_onednn_data, 0, sizeof(T) * N * Ti * IC);
if (is_NTC(this->fwd_pd_->src_desc())) {
reorderRNNdata(
x_data, x_onednn_data, input_lod, is_reverse, RNNReorderType::PP_NTC);
} else {
reorderRNNdata(
x_data, x_onednn_data, input_lod, is_reverse, RNNReorderType::PP_TNC);
}
return memory_p;
}
std::shared_ptr<dnnl::memory> AcquireOutputMemory() {
const auto name = this->key_ + "@output_mem";
auto memory_p =
std::static_pointer_cast<dnnl::memory>(this->dev_ctx_.GetBlob(name));
if (!memory_p) {
memory_p = std::make_shared<dnnl::memory>(this->fwd_pd_->dst_desc(),
this->engine_);
this->dev_ctx_.SetBlob(name, memory_p);
}
return memory_p;
}
// H0 is for now persistable
template <typename U>
std::shared_ptr<dnnl::memory> AcquireH0Memory(const DenseTensor* h0) {
const std::string h0_key = memory_key_ + "@h0";
auto memory_p =
std::static_pointer_cast<dnnl::memory>(this->dev_ctx_.GetBlob(h0_key));
if (!memory_p) {
auto user_h0_memory = dnnl::memory();
if (h0) {
user_h0_memory = dnnl::memory(
{{1, 1, N, OC}, OneDNNGetDataType<U>(), OneDNNMemoryFormat::ldnc},
this->engine_,
funcs::to_void_cast(h0->data<U>()));
} else {
user_h0_memory = dnnl::memory(
{{1, 1, N, OC}, OneDNNGetDataType<U>(), OneDNNMemoryFormat::ldnc},
this->engine_);
memset(user_h0_memory.get_data_handle(), 0, sizeof(U) * N * OC);
}
memory_p = std::make_shared<dnnl::memory>(this->fwd_pd_->src_iter_desc(),
this->engine_);
auto& astream = phi::OneDNNContext::tls().get_stream();
dnnl::reorder(user_h0_memory, *memory_p, attr_)
.execute(astream, user_h0_memory, *memory_p);
this->dev_ctx_.SetBlob(h0_key, memory_p);
}
return memory_p;
}
template <typename U>
std::shared_ptr<dnnl::memory> AcquireWeightXMemory(
const DenseTensor* weight_x, const bool origin_mode) {
const std::string wx_key = this->memory_key_ + "@weight_x";
auto memory_p =
std::static_pointer_cast<dnnl::memory>(this->dev_ctx_.GetBlob(wx_key));
if (!memory_p) {
auto user_md = OneDNNMemDesc({1, 1, this->IC, this->G, this->OC},
OneDNNGetDataType<U>(),
OneDNNMemoryFormat::ldigo);
auto user_memory = dnnl::memory(user_md, this->engine_);
auto* weight_x_data = reinterpret_cast<U*>(user_memory.get_data_handle());
memcpy(weight_x_data,
weight_x->data<U>(),
sizeof(U) * this->IC * this->G * this->OC);
if (origin_mode == false) {
for (int64_t i = 0; i < this->IC; ++i) {
for (int64_t j = 0; j < this->OC; ++j) {
U minus_one(-1.0f);
weight_x_data[j] = minus_one * weight_x_data[j];
}
weight_x_data += 3 * this->OC;
}
}
memory_p = std::make_shared<dnnl::memory>(
this->fwd_pd_->weights_layer_desc(), this->engine_);
auto& astream = OneDNNContext::tls().get_stream();
dnnl::reorder(user_memory, *memory_p, this->attr_)
.execute(astream, user_memory, *memory_p);
this->dev_ctx_.SetBlob(wx_key, memory_p);
}
return memory_p;
}
template <typename U>
std::shared_ptr<dnnl::memory> AcquireWeightHMemory(
const DenseTensor* weight_h, const bool origin_mode) {
const std::string wh_key = this->memory_key_ + "@weight_h";
auto memory_p =
std::static_pointer_cast<dnnl::memory>(this->dev_ctx_.GetBlob(wh_key));
if (!memory_p) {
auto user_md = OneDNNMemDesc({1, 1, this->OC, this->G, this->OC},
OneDNNGetDataType<U>(),
OneDNNMemoryFormat::ldigo);
auto user_memory = dnnl::memory(user_md, this->engine_);
// Reorder weights_h from PP format [OC, 2OC] + [OC, OC] to
// oneDNN format [OC, 3OC]
auto* weight_h_data = reinterpret_cast<U*>(user_memory.get_data_handle());
auto* user_weight_h_data = weight_h->data<U>();
auto src1_iter = user_weight_h_data;
auto src2_iter = user_weight_h_data + 2 * this->OC * this->OC;
for (int64_t c = 0; c < this->OC; ++c) {
memcpy(weight_h_data, src1_iter, 2 * this->OC * sizeof(U));
memcpy(weight_h_data + 2 * this->OC, src2_iter, this->OC * sizeof(U));
src1_iter += 2 * this->OC;
src2_iter += this->OC;
weight_h_data += 3 * this->OC;
}
weight_h_data = reinterpret_cast<U*>(user_memory.get_data_handle());
if (origin_mode == false) {
for (int64_t i = 0; i < this->OC; ++i) {
for (int64_t j = 0; j < this->OC; ++j) {
U minus_one(-1.0f);
weight_h_data[j] = minus_one * weight_h_data[j];
}
weight_h_data += 3 * this->OC;
}
}
memory_p = std::make_shared<dnnl::memory>(
this->fwd_pd_->weights_iter_desc(), this->engine_);
auto& astream = OneDNNContext::tls().get_stream();
dnnl::reorder(user_memory, *memory_p, this->attr_)
.execute(astream, user_memory, *memory_p);
this->dev_ctx_.SetBlob(wh_key, memory_p);
}
return memory_p;
}
std::shared_ptr<dnnl::memory> AcquireBiasMemory(const DenseTensor* bias,
const bool origin_mode) {
const std::string bias_key = this->memory_key_ + "@bias";
auto memory_p = std::static_pointer_cast<dnnl::memory>(
this->dev_ctx_.GetBlob(bias_key));
if (!memory_p) {
memory_p = std::make_shared<dnnl::memory>(this->fwd_pd_->bias_desc(),
this->engine_);
auto* bias_data = reinterpret_cast<float*>(memory_p->get_data_handle());
if (bias) {
const float* user_bias_data =
bias->data<float>(); // Bias in oneDNN is always float
memcpy(bias_data, user_bias_data, sizeof(float) * this->G * this->OC);
} else {
// oneDNN always need bias memory, if it's not provided in PP, let
// oneDNN allocate memory and set it to 0
memset(bias_data, 0, sizeof(float) * this->G * this->OC);
}
if (origin_mode == false && bias) {
for (int64_t i = 0; i < this->OC; ++i) {
bias_data[i] *= -1;
}
}
this->dev_ctx_.SetBlob(bias_key, memory_p);
}
return memory_p;
}
protected:
// RNN dimensions
// N - Batch Size
// Ti - Max sentence length
// IC - Input Channels
// OC - Output Channels
// G - Number of gates
const int64_t N, Ti, IC, OC, G;
// Memory size of weights, bias and h0 does not depend
// on Ti size, thus we need another key to cache them
std::string memory_key_;
dnnl::primitive_attr attr_;
};
template <typename T, typename Tout = T>
void RunKernel(const phi::OneDNNContext& dev_ctx,
const DenseTensor& x,
const optional<DenseTensor>& h0,
const DenseTensor& weight_x,
const DenseTensor& weight_h,
const optional<DenseTensor>& bias,
const std::string& activation,
const std::string& gate_activation,
const bool is_reverse,
const bool use_seq,
const bool origin_mode,
const float scale_data,
const float shift_data,
const std::vector<float>& scale_weights,
DenseTensor* reordered_h0,
DenseTensor* xx,
DenseTensor* batched_input,
DenseTensor* batched_out,
DenseTensor* hidden) {
const auto& onednn_engine = dev_ctx.GetEngine();
auto x_dims = x.dims();
auto x_mat_dims = (x_dims.size() == 3 && x_dims[1] == 1)
? common::flatten_to_2d(x_dims, 1)
: x_dims;
// Get tensor dimensions
const auto x_mat_dims_vec = vectorize(x_mat_dims);
const auto weight_h_dims = vectorize(weight_h.dims());
const auto& input_lod = x.lod()[0];
// Calculate RNN dimensions
const int64_t N = static_cast<int64_t>(input_lod.size() -
1); // Number of sentences (batches)
const int64_t Ti = // Max length of the sentence in a batch
[&input_lod]() {
size_t res = 0;
for (size_t i = 0; i < (input_lod.size() - 1); ++i) {
res = std::max(res, input_lod[i + 1] - input_lod[i]);
}
return res;
}();
const int64_t IC = x_mat_dims_vec[1]; // Input channels
const int64_t OC = weight_h_dims[0]; // Output channels
GRUOneDNNHandler<T, Tout> handler(dev_ctx,
onednn_engine,
dev_ctx.GetPlace(),
&x,
&weight_h,
h0.get_ptr(),
is_reverse,
scale_data,
shift_data,
gate_activation,
activation,
scale_weights,
N,
Ti,
IC,
OC);
auto input_memory_p = handler.AcquireInputMemoryWithReorder(&x, is_reverse);
std::shared_ptr<dnnl::memory> h0_memory_p, weight_h_memory_p,
weight_x_memory_p;
if (weight_h.dtype() == DataType::FLOAT32) {
h0_memory_p = handler.template AcquireH0Memory<float>(h0.get_ptr());
weight_x_memory_p =
handler.template AcquireWeightXMemory<float>(&weight_x, origin_mode);
weight_h_memory_p =
handler.template AcquireWeightHMemory<float>(&weight_h, origin_mode);
} else if (weight_h.dtype() == DataType::BFLOAT16) {
h0_memory_p = handler.template AcquireH0Memory<phi::bfloat16>(h0.get_ptr());
weight_x_memory_p = handler.template AcquireWeightXMemory<phi::bfloat16>(
&weight_x, origin_mode);
weight_h_memory_p = handler.template AcquireWeightHMemory<phi::bfloat16>(
&weight_h, origin_mode);
} else {
h0_memory_p = handler.template AcquireH0Memory<uint8_t>(h0.get_ptr());
weight_x_memory_p =
handler.template AcquireWeightXMemory<int8_t>(&weight_x, origin_mode);
weight_h_memory_p =
handler.template AcquireWeightHMemory<int8_t>(&weight_h, origin_mode);
}
auto bias_memory_p = handler.AcquireBiasMemory(bias.get_ptr(), origin_mode);
auto hidden_onednn_memory_p = handler.AcquireOutputMemory();
std::unordered_map<int, dnnl::memory> gru_args = {
{DNNL_ARG_SRC_LAYER, *input_memory_p},
{DNNL_ARG_SRC_ITER, *h0_memory_p},
{DNNL_ARG_WEIGHTS_LAYER, *weight_x_memory_p},
{DNNL_ARG_WEIGHTS_ITER, *weight_h_memory_p},
{DNNL_ARG_BIAS, *bias_memory_p},
{DNNL_ARG_DST_LAYER, *hidden_onednn_memory_p}};
auto gru_forward_p = handler.AcquireForwardPrimitive();
auto& astream = OneDNNContext::tls().get_stream();
gru_forward_p->execute(astream, gru_args);
astream.wait();
auto* hidden_onednn_data = hidden_onednn_memory_p->get_data_handle();
auto* hidden_tmp_data = dev_ctx.template Alloc<Tout>(hidden);
auto* hidden_data = funcs::to_void_cast(hidden_tmp_data);
if (handler.is_NTC()) {
handler.reorderRNNdata(hidden_onednn_data,
hidden_data,
input_lod,
is_reverse,
RNNReorderType::NTC_PP);
} else {
handler.reorderRNNdata(hidden_onednn_data,
hidden_data,
input_lod,
is_reverse,
RNNReorderType::TNC_PP);
}
}
template <typename T, typename Context>
void FusionGRUKernel(const Context& dev_ctx,
const DenseTensor& x,
const optional<DenseTensor>& h0,
const DenseTensor& weight_x,
const DenseTensor& weight_h,
const optional<DenseTensor>& bias,
const std::string& activation,
const std::string& gate_activation,
const bool is_reverse,
const bool use_seq,
const bool origin_mode,
const bool force_fp32_output,
DenseTensor* reordered_h0,
DenseTensor* xx,
DenseTensor* batched_input,
DenseTensor* batched_out,
DenseTensor* hidden) {
const std::string mkldnn_data_type =
dev_ctx.HasDnnAttr("mkldnn_data_type")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("mkldnn_data_type"))
: "float32";
const std::string onednn_data_type =
(dev_ctx.HasDnnAttr("onednn_data_type") &&
PADDLE_GET_CONST(std::string, dev_ctx.GetDnnAttr("onednn_data_type")) !=
"")
? PADDLE_GET_CONST(std::string,
dev_ctx.GetDnnAttr("onednn_data_type"))
: mkldnn_data_type;
std::vector<std::string> onednn_data_type_list = {
"float32", "int8", "bfloat16"};
PADDLE_ENFORCE_EQ(std::find(onednn_data_type_list.begin(),
onednn_data_type_list.end(),
onednn_data_type) != onednn_data_type_list.end(),
true,
common::errors::InvalidArgument(
"The onednn_data_type should be [float32, "
"int8, bfloat16], but found %s.",
onednn_data_type.c_str()));
const float scale_data =
dev_ctx.HasDnnAttr("Scale_data")
? PADDLE_GET_CONST(float, dev_ctx.GetDnnAttr("Scale_data"))
: 1.0f;
const float shift_data =
dev_ctx.HasDnnAttr("Shift_data")
? PADDLE_GET_CONST(float, dev_ctx.GetDnnAttr("Shift_data"))
: 1.0f;
std::vector<float> tmp_scale_weights = {1.0f};
const std::vector<float> scale_weights =
dev_ctx.HasDnnAttr("Scale_weights")
? PADDLE_GET_CONST(std::vector<float>,
dev_ctx.GetDnnAttr("Scale_weights"))
: tmp_scale_weights;
const bool is_bf16 = std::is_same<T, phi::bfloat16>::value;
// BF16 does not support force output
if (!is_bf16 && force_fp32_output) { // NOLINT
RunKernel<T, float>(dev_ctx,
x,
h0,
weight_x,
weight_h,
bias,
activation,
gate_activation,
is_reverse,
use_seq,
origin_mode,
scale_data,
shift_data,
scale_weights,
reordered_h0,
xx,
batched_input,
batched_out,
hidden);
} else {
RunKernel<T>(dev_ctx,
x,
h0,
weight_x,
weight_h,
bias,
activation,
gate_activation,
is_reverse,
use_seq,
origin_mode,
scale_data,
shift_data,
scale_weights,
reordered_h0,
xx,
batched_input,
batched_out,
hidden);
}
}
} // namespace phi::fusion
PD_REGISTER_KERNEL(fusion_gru,
OneDNN,
ONEDNN,
phi::fusion::FusionGRUKernel,
float,
phi::bfloat16,
uint8_t) {}