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

444 lines
17 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 <string>
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/blas/blas.h"
#include "paddle/phi/kernels/funcs/fc_functor.h"
#include "paddle/phi/kernels/funcs/jit/kernels.h"
#include "paddle/phi/kernels/funcs/sequence2batch.h"
namespace phi {
#define INIT_BASE_DEFINES \
auto *x = &x_in; \
auto *h0 = h0_in.get_ptr(); \
auto *c0 = c0_in.get_ptr(); \
auto *wx = &weight_x_in; \
auto *wh = &weight_h_in; \
auto *bias = &bias_in; \
auto *hidden_out = hidden; \
auto *cell_out = cell; \
auto x_dims = x->dims(); /* T x M*/ \
auto wh_dims = wh->dims(); /* D x 4D*/ \
const int M = x_dims[1]; \
const int D = wh_dims[0]; \
const int D4 = wh_dims[1]
#define INIT_OTHER_DEFINES \
const T *x_data = x->data<T>(); \
const T *wx_data = wx->data<T>(); \
const T *wh_data = wh->data<T>(); \
/* diagonal weight*/ \
const T *wp_data = bias->data<T>() + D4; \
/* for peephole only*/ \
T *checked_cell_data = nullptr; \
if (use_peepholes) { \
/* w_ic * Ct-1, w_fc * Ct-1 ; w_oc * Ct => ih*/ \
checked_cell_data = dev_ctx.template Alloc<T>(checked_cell); \
} \
const phi::jit::lstm_attr_t attr( \
D, \
phi::jit::to_kerneltype(gate_activation), \
phi::jit::to_kerneltype(candidate_activation), \
phi::jit::to_kerneltype(cell_activation), \
use_peepholes); \
phi::jit::lstm_t one_step; \
one_step.wp = wp_data; \
one_step.checked = checked_cell_data; \
auto ComputeC1H1 = \
phi::jit::KernelFuncs<phi::jit::LSTMC1H1Tuple<T>, CPUPlace>::Cache().At( \
attr); \
auto ComputeCtHt = \
phi::jit::KernelFuncs<phi::jit::LSTMCtHtTuple<T>, CPUPlace>::Cache().At( \
attr)
// Wh GEMM
#define GEMM_WH_ADDON(bs, prev, out) \
blas.GEMM(CblasNoTrans, \
CblasNoTrans, \
bs, \
D4, \
D, \
static_cast<T>(1), \
prev, \
D, \
wh_data, \
D4, \
static_cast<T>(1), \
out, \
D4)
template <typename T, typename Context>
void SeqCompute(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) {
INIT_BASE_DEFINES;
INIT_OTHER_DEFINES;
auto x_lod = x->lod();
const int total_T = static_cast<int>(x_dims[0]);
const int N = static_cast<int>(x_lod[0].size() - 1);
const T *h0_data = h0 ? h0->data<T>() : nullptr;
const T *c0_data = c0 ? c0->data<T>() : nullptr;
T *xx_data = dev_ctx.template Alloc<T>(xx);
T *h_out_data = dev_ctx.template Alloc<T>(hidden_out);
T *c_out_data = dev_ctx.template Alloc<T>(cell_out);
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
funcs::FCFunctor<Context, T> fc;
fc(dev_ctx, total_T, D4, M, x_data, wx_data, xx_data, bias->data<T>());
int xx_offset = D4;
int gate_offset = D;
if (is_reverse) {
const int offset = (total_T - 1) * D;
xx_data = xx_data + offset * 4;
h_out_data = h_out_data + offset;
c_out_data = c_out_data + offset;
xx_offset = -D4;
gate_offset = -D;
}
for (int i = 0; i < N; ++i) {
int bid = is_reverse ? N - 1 - i : i;
int seq_len = static_cast<int>(x_lod[0][bid + 1] - x_lod[0][bid]);
const T *prev_c_data = nullptr;
const T *prev_h_data = nullptr;
int tstart = 0;
if (h0_data) {
prev_h_data = h0_data + bid * D;
prev_c_data = c0_data + bid * D;
} else {
one_step.gates = xx_data;
one_step.ct = c_out_data;
one_step.ht = h_out_data;
ComputeC1H1(&one_step, &attr);
tstart = 1;
// move one step
prev_h_data = h_out_data;
prev_c_data = c_out_data;
xx_data = xx_data + xx_offset;
h_out_data = h_out_data + gate_offset;
c_out_data = c_out_data + gate_offset;
}
for (int step = tstart; step < seq_len; ++step) {
GEMM_WH_ADDON(1, prev_h_data, xx_data);
one_step.gates = xx_data;
one_step.ct_1 = prev_c_data;
one_step.ct = c_out_data;
one_step.ht = h_out_data;
ComputeCtHt(&one_step, &attr);
// move one step
prev_h_data = h_out_data;
prev_c_data = c_out_data;
xx_data = xx_data + xx_offset;
h_out_data = h_out_data + gate_offset;
c_out_data = c_out_data + gate_offset;
}
}
}
template <typename T, typename Context>
void BatchCompute(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) {
INIT_BASE_DEFINES;
if (x->lod()[0].size() == 2) {
xx->Resize({x_dims[0], D4});
SeqCompute<T, Context>(dev_ctx,
x_in,
weight_x_in,
weight_h_in,
bias_in,
h0_in,
c0_in,
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);
return;
}
INIT_OTHER_DEFINES;
auto *batched_c_out = batched_cell;
auto *batched_h_out = batched_hidden;
T *xx_data = dev_ctx.template Alloc<T>(xx);
T *batched_input_data = dev_ctx.template Alloc<T>(batched_input);
T *batched_c_out_data = dev_ctx.template Alloc<T>(batched_c_out);
T *batched_h_out_data = dev_ctx.template Alloc<T>(batched_h_out);
dev_ctx.template Alloc<T>(hidden_out);
dev_ctx.template Alloc<T>(cell_out);
funcs::DenseTensor2BatchFunctor<Context, T> to_batch;
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
funcs::FCFunctor<Context, T> fc;
if (M > D4) {
fc(dev_ctx, x_dims[0], D4, M, x_data, wx_data, xx_data, bias->data<T>());
to_batch(dev_ctx, *xx, batched_input, true, is_reverse);
} else {
to_batch(dev_ctx, *x, xx, true, is_reverse);
batched_input->set_lod(xx->lod());
fc(dev_ctx,
x_dims[0],
D4,
M,
xx_data,
wx_data,
batched_input_data,
bias->data<T>());
}
auto batched_lod = batched_input->lod();
const auto &seq_order = batched_lod[2];
const int max_bs = static_cast<int>(seq_order.size());
reordered_h0->Resize({max_bs, D});
reordered_c0->Resize({max_bs, D});
int tstart = 0;
T *prev_h_data = nullptr;
T *prev_c_data = nullptr;
if (h0) {
// reorder h0, c0
T *reordered_h0_data = dev_ctx.template Alloc<T>(reordered_h0);
T *reordered_c0_data = dev_ctx.template Alloc<T>(reordered_c0);
const T *h0_data = h0->data<T>();
const T *c0_data = c0->data<T>();
prev_h_data = reordered_h0_data;
prev_c_data = reordered_c0_data;
size_t sz = D;
for (int i = 0; i < max_bs; ++i) {
blas.VCOPY(sz, h0_data + seq_order[i] * D, reordered_h0_data);
blas.VCOPY(sz, c0_data + seq_order[i] * D, reordered_c0_data);
reordered_h0_data += D;
reordered_c0_data += D;
}
} else {
// compute without h0, c0
T *cur_in_data = batched_input_data;
T *cur_h_out_data = batched_h_out_data;
T *cur_c_out_data = batched_c_out_data;
for (int i = 0; i < max_bs; ++i) {
one_step.gates = cur_in_data;
one_step.ct = cur_c_out_data;
one_step.ht = cur_h_out_data;
ComputeC1H1(&one_step, &attr);
cur_in_data += D4;
cur_c_out_data += D;
cur_h_out_data += D;
}
tstart = 1;
prev_h_data = batched_h_out_data;
prev_c_data = batched_c_out_data;
}
// compute kernel part
const auto &batch_starts = batched_lod[0];
const int max_seq_len = static_cast<int>(batch_starts.size() - 1);
const int offset = tstart * max_bs * D;
batched_input_data = batched_input_data + offset * 4;
batched_h_out_data = batched_h_out_data + offset;
batched_c_out_data = batched_c_out_data + offset;
for (int step = tstart; step < max_seq_len; ++step) {
const int cur_bs =
static_cast<int>(batch_starts[step + 1] - batch_starts[step]);
GEMM_WH_ADDON(cur_bs, prev_h_data, batched_input_data);
T *cur_in_data = batched_input_data;
T *cur_prev_c_data = prev_c_data;
T *cur_c_out_data = batched_c_out_data;
T *cur_h_out_data = batched_h_out_data;
for (int i = 0; i < cur_bs; ++i) {
one_step.gates = cur_in_data;
one_step.ct_1 = cur_prev_c_data;
one_step.ct = cur_c_out_data;
one_step.ht = cur_h_out_data;
ComputeCtHt(&one_step, &attr);
// move one batch
cur_in_data += D4;
cur_prev_c_data += D;
cur_c_out_data += D;
cur_h_out_data += D;
}
// move one step
prev_c_data = batched_c_out_data;
prev_h_data = batched_h_out_data;
batched_c_out_data = cur_c_out_data;
batched_h_out_data = cur_h_out_data;
batched_input_data = cur_in_data;
}
funcs::Batch2DenseTensorFunctor<Context, T> to_seq;
batched_h_out->set_lod(batched_lod);
to_seq(dev_ctx, *batched_h_out, hidden_out);
batched_c_out->set_lod(batched_lod);
to_seq(dev_ctx, *batched_c_out, cell_out);
}
template <typename T, typename Context>
void FusionLSTMKernel(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) {
if (use_seq) {
SeqCompute<T, Context>(dev_ctx,
x_in,
weight_x_in,
weight_h_in,
bias_in,
h0_in,
c0_in,
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 {
BatchCompute<T, Context>(dev_ctx,
x_in,
weight_x_in,
weight_h_in,
bias_in,
h0_in,
c0_in,
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);
}
}
#undef GEMM_WH_ADDON
#undef INIT_OTHER_DEFINES
#undef INIT_BASE_DEFINES
} // namespace phi
PD_REGISTER_KERNEL(
fusion_lstm, CPU, ALL_LAYOUT, phi::FusionLSTMKernel, float, double) {}