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

573 lines
22 KiB
C++

// Copyright (c) 2024 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/core/compat/convert_utils.h"
#include "paddle/phi/core/expect.h"
#include "paddle/phi/kernels/fusion/onednn/fusion_rnn_onednn.h"
#include "paddle/utils/optional.h"
namespace phi {
namespace fusion {
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 LSTMONEDNNHandler
: public RNNONEDNNHandler<T, dnnl::lstm_forward, T_out> {
public:
LSTMONEDNNHandler(const OneDNNContext& dev_ctx,
const dnnl::engine onednn_engine,
phi::Place cpu_place,
const DenseTensor* input,
const DenseTensor* weight_h,
const DenseTensor* h0,
const DenseTensor* c0 UNUSED,
const bool is_reverse,
const int64_t N,
const int64_t Ti,
const int64_t IC,
const int64_t OC,
const std::string& unique_name,
float scale_data,
float shift_data,
std::vector<float> scale_weights,
bool use_peepholes,
std::string gate_activation,
std::string cell_activation,
std::string candidate_activation)
: RNNONEDNNHandler<T, dnnl::lstm_forward, T_out>(dev_ctx,
onednn_engine,
cpu_place,
input,
weight_h,
h0,
is_reverse,
N,
Ti,
IC,
OC,
4,
unique_name,
scale_data,
shift_data,
scale_weights) {
if (unlikely(!this->isCached())) {
const bool is_INT8 = std::is_same<T, uint8_t>::value;
// oneDNN kernel has hardcoded activation functions
PADDLE_ENFORCE_EQ(
gate_activation,
"sigmoid",
common::errors::Unimplemented("oneDNN fusion_lstm supports only "
"sigmoid as a gate activation."));
PADDLE_ENFORCE_EQ(
cell_activation,
"tanh",
common::errors::Unimplemented(
"oneDNN fusion_lstm supports only tanh as a cell activation."));
PADDLE_ENFORCE_EQ(
candidate_activation,
"tanh",
common::errors::Unimplemented(
"oneDNN fusion_lstm supports only tanh a candidate 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 = 4; // Number of Gates, 4 for LSTM
// Create memory descriptors
auto input_md = OneDNNMemDesc(
{Ti, N, IC}, OneDNNGetDataType<T>(), OneDNNMemoryFormat::tnc);
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::any);
auto c0_md = OneDNNMemDesc(
{L, D, N, OC}, OneDNNGetDataType<float>(), OneDNNMemoryFormat::any);
// Create LSTM oneDNN primitive
const auto direction =
is_reverse ? dnnl::rnn_direction::unidirectional_right2left
: dnnl::rnn_direction::unidirectional_left2right;
if (!use_peepholes) {
this->AcquireForwardPrimitiveDescriptor(
this->attr_,
dnnl::prop_kind::forward_inference,
direction,
input_md,
h0_md,
c0_md,
weight_x_md,
weight_h_md,
bias_md,
hidden_md,
dnnl::memory::desc(),
dnnl::memory::desc());
} else {
auto weight_peephole_md = OneDNNMemDesc({L, D, 3, OC},
OneDNNGetDataType<float>(),
OneDNNMemoryFormat::ldgo);
this->AcquireForwardPrimitiveDescriptor(
this->attr_,
dnnl::prop_kind::forward_inference,
direction,
input_md,
h0_md,
c0_md,
weight_x_md,
weight_h_md,
weight_peephole_md,
bias_md,
hidden_md,
dnnl::memory::desc(),
dnnl::memory::desc());
}
}
}
// PaddlePaddle has different order of weights than oneDNN, so a reorder is
// needed
// PaddlePaddle: {c, i, f, o}
// oneDNN: {i, f, c, o}
template <typename U>
void ReorderGates(U* weights, int64_t I) {
size_t inner_block_size = this->OC;
size_t block_size = inner_block_size * this->G;
for (size_t i = 0; i < (size_t)I; ++i) { // NOLINT
size_t offset = i * block_size;
U* base_pos = weights + offset;
std::swap_ranges(base_pos,
base_pos + inner_block_size,
base_pos + inner_block_size); // c <-> i
std::swap_ranges(base_pos + inner_block_size,
base_pos + 2 * inner_block_size,
base_pos + 2 * inner_block_size); // c <-> f
}
}
template <typename U>
std::shared_ptr<dnnl::memory> AcquireWeightXMemory(
const DenseTensor* weight_x) {
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);
ReorderGates(weight_x_data, this->IC);
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 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_);
auto* weight_h_data = reinterpret_cast<U*>(user_memory.get_data_handle());
memcpy(weight_h_data,
weight_h->data<U>(),
sizeof(U) * this->OC * this->G * this->OC);
ReorderGates(weight_h_data, 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 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);
ReorderGates(bias_data, 1);
} 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);
}
this->dev_ctx_.SetBlob(bias_key, memory_p);
}
return memory_p;
}
std::shared_ptr<dnnl::memory> AcquirePeepholeWeights(
const DenseTensor* bias) {
const std::string peepholes_key = this->memory_key_ + "@peepholes_weights";
auto memory_p = std::static_pointer_cast<dnnl::memory>(
this->dev_ctx_.GetBlob(peepholes_key));
if (!memory_p) {
auto user_md = OneDNNMemDesc({1, 1, 3, this->OC},
OneDNNGetDataType<float>(),
OneDNNMemoryFormat::ldgo);
auto user_memory = dnnl::memory(user_md, this->engine_);
memory_p = std::make_shared<dnnl::memory>(
this->fwd_pd_->weights_peephole_desc(), this->engine_);
auto* peephole_weights_data =
reinterpret_cast<float*>(memory_p->get_data_handle());
const float* user_bias_data =
bias->data<float>(); // Bias in oneDNN is always float
memcpy(peephole_weights_data,
user_bias_data + 4 * this->OC,
sizeof(float) * 3 * this->OC);
this->dev_ctx_.SetBlob(peepholes_key, memory_p);
}
return memory_p;
}
std::shared_ptr<dnnl::memory> AcquireC0Memory(const DenseTensor* c0) {
const std::string c0_key = this->memory_key_ + "@c0";
auto memory_p =
std::static_pointer_cast<dnnl::memory>(this->dev_ctx_.GetBlob(c0_key));
if (!memory_p) {
auto user_c0_memory = dnnl::memory();
if (c0) {
user_c0_memory = dnnl::memory({{1, 1, this->N, this->OC},
OneDNNGetDataType<float>(),
OneDNNMemoryFormat::ldnc},
this->engine_,
funcs::to_void_cast(c0->data<float>()));
} else {
user_c0_memory = dnnl::memory({{1, 1, this->N, this->OC},
OneDNNGetDataType<float>(),
OneDNNMemoryFormat::ldnc},
this->engine_);
memset(user_c0_memory.get_data_handle(),
0,
sizeof(float) * this->N * this->OC);
}
memory_p = std::make_shared<dnnl::memory>(
this->fwd_pd_->src_iter_c_desc(), this->engine_);
auto& astream = OneDNNContext::tls().get_stream();
dnnl::reorder(user_c0_memory, *memory_p)
.execute(astream, user_c0_memory, *memory_p);
this->dev_ctx_.SetBlob(c0_key, memory_p);
}
return memory_p;
}
};
template <typename T, typename Context, typename Tout = T>
void RunKernel(const Context& dev_ctx,
const DenseTensor& x_in,
const DenseTensor& weight_x_in,
const DenseTensor& weight_h_in,
const DenseTensor& bias_in,
const optional<DenseTensor>& h0_in,
const optional<DenseTensor>& c0_in,
bool use_peepholes,
bool is_reverse,
bool use_seq,
const std::string& gate_activation,
const std::string& cell_activation,
const std::string& candidate_activation,
float scale_data,
float shift_data,
const std::vector<float>& scale_weights,
bool force_fp32_output,
DenseTensor* hidden,
DenseTensor* cell,
DenseTensor* xx,
DenseTensor* batched_input,
DenseTensor* batched_hidden,
DenseTensor* batched_cell,
DenseTensor* reordered_h0,
DenseTensor* reordered_c0,
DenseTensor* checked_cell) {
const auto& onednn_engine = dev_ctx.GetEngine();
// Get Tensors
const auto* input = &x_in;
const auto* h0 = h0_in.get_ptr();
const auto* c0 = c0_in.get_ptr();
const auto* weight_x = &weight_x_in;
const auto* weight_h = &weight_h_in;
const auto* bias = &bias_in;
auto x_dims = input->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 = input->lod()[0];
// Calculate RNN dimensions
const int64_t N = 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
std::string unique_name =
dev_ctx.GetInputsName("X")[0] + dev_ctx.GetInputsName("WeightH")[0];
LSTMONEDNNHandler<T, Tout> handler(dev_ctx,
onednn_engine,
dev_ctx.GetPlace(),
input,
weight_h,
h0,
c0,
is_reverse,
N,
Ti,
IC,
OC,
unique_name,
scale_data,
shift_data,
scale_weights,
use_peepholes,
gate_activation,
cell_activation,
candidate_activation);
auto input_memory_p =
handler.AcquireInputMemoryWithReorder(input, is_reverse);
auto c0_memory_p = handler.AcquireC0Memory(c0);
std::shared_ptr<dnnl::memory> h0_memory_p, weight_h_memory_p,
weight_x_memory_p;
if (weight_h->dtype() == phi::DataType::FLOAT32) {
h0_memory_p = handler.template AcquireH0Memory<float>(h0);
weight_x_memory_p = handler.template AcquireWeightXMemory<float>(weight_x);
weight_h_memory_p = handler.template AcquireWeightHMemory<float>(weight_h);
} else if (weight_h->dtype() == phi::DataType::BFLOAT16) {
h0_memory_p = handler.template AcquireH0Memory<phi::bfloat16>(h0);
weight_x_memory_p =
handler.template AcquireWeightXMemory<phi::bfloat16>(weight_x);
weight_h_memory_p =
handler.template AcquireWeightHMemory<phi::bfloat16>(weight_h);
} else {
h0_memory_p = handler.template AcquireH0Memory<uint8_t>(h0);
weight_x_memory_p = handler.template AcquireWeightXMemory<int8_t>(weight_x);
weight_h_memory_p = handler.template AcquireWeightHMemory<int8_t>(weight_h);
}
auto bias_memory_p = handler.AcquireBiasMemory(bias);
auto hidden_onednn_memory_p = handler.AcquireOutputMemory();
std::unordered_map<int, dnnl::memory> lstm_args = {
{DNNL_ARG_SRC_LAYER, *input_memory_p},
{DNNL_ARG_SRC_ITER, *h0_memory_p},
{DNNL_ARG_SRC_ITER_C, *c0_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}};
if (use_peepholes) {
auto peephole_weight_p = handler.AcquirePeepholeWeights(bias);
std::pair<int, dnnl::memory> peepholes_weights(DNNL_ARG_WEIGHTS_PEEPHOLE,
*peephole_weight_p);
lstm_args.insert(peepholes_weights);
}
auto lstm_forward_p = handler.AcquireForwardPrimitive();
auto& astream = OneDNNContext::tls().get_stream();
lstm_forward_p->execute(astream, lstm_args);
astream.wait();
auto* hidden_onednn_data = hidden_onednn_memory_p->get_data_handle();
auto* hidden_data = funcs::to_void_cast(dev_ctx.template Alloc<Tout>(hidden));
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 FusionLSTMMKLDNNKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& weight_x,
const DenseTensor& weight_h,
const DenseTensor& bias,
const optional<DenseTensor>& h0,
const optional<DenseTensor>& c0,
bool use_peepholes,
bool is_reverse,
bool use_seq,
const std::string& gate_activation,
const std::string& cell_activation,
const std::string& candidate_activation,
float scale_data,
float shift_data,
const std::vector<float>& scale_weights,
bool force_fp32_output,
DenseTensor* hidden,
DenseTensor* cell,
DenseTensor* xx,
DenseTensor* batched_input,
DenseTensor* batched_hidden,
DenseTensor* batched_cell,
DenseTensor* reordered_h0,
DenseTensor* reordered_c0,
DenseTensor* checked_cell) {
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, Context, float>(dev_ctx,
x,
weight_x,
weight_h,
bias,
h0,
c0,
use_peepholes,
is_reverse,
use_seq,
gate_activation,
cell_activation,
candidate_activation,
scale_data,
shift_data,
scale_weights,
force_fp32_output,
hidden,
cell,
xx,
batched_input,
batched_hidden,
batched_cell,
reordered_h0,
reordered_c0,
checked_cell);
} else {
RunKernel<T, Context, T>(dev_ctx,
x,
weight_x,
weight_h,
bias,
h0,
c0,
use_peepholes,
is_reverse,
use_seq,
gate_activation,
cell_activation,
candidate_activation,
scale_data,
shift_data,
scale_weights,
force_fp32_output,
hidden,
cell,
xx,
batched_input,
batched_hidden,
batched_cell,
reordered_h0,
reordered_c0,
checked_cell);
}
}
} // namespace fusion
} // namespace phi
PD_REGISTER_KERNEL(fusion_lstm,
OneDNN,
ONEDNN,
phi::fusion::FusionLSTMMKLDNNKernel,
float,
uint8_t,
phi::bfloat16) {}