Files
2026-07-13 12:40:42 +08:00

234 lines
9.0 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/backends/cpu/cpu_info.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/blas/blas.h"
#include "paddle/phi/kernels/funcs/cpu_vec.h"
#include "paddle/phi/kernels/funcs/fc_functor.h"
#include "paddle/utils/optional.h"
namespace phi {
// y[i] = (x[i] + bias[0]) > 0 ? (x[i] + bias[0]) : 0;
template <typename T>
inline void bias_relu(const int n, const T* x, const T* bias, T* y) {
if (bias) {
funcs::vec_add_bias<T, backends::cpu::avx>(n, *bias, x, y);
funcs::vec_relu<T, backends::cpu::avx>(n, y, y);
} else {
funcs::vec_relu<T, backends::cpu::avx>(n, x, y);
}
}
template <typename T>
inline void vec_softmax(const int n, const T* x, T* y) {
T scalar = x[0];
// max
for (int i = 1; i < n; ++i) {
scalar = scalar < x[i] ? x[i] : scalar;
}
funcs::vec_add_bias<T, backends::cpu::avx>(n, -scalar, x, y); // sub
funcs::vec_exp<T>(n, y, y); // exp
// sum
scalar = T(0);
for (int i = 0; i < n; ++i) {
scalar += y[i];
}
funcs::vec_scal<T>(n, static_cast<T>(1) / scalar, y); // scale
}
template <typename T, typename Context>
void AttentionLSTMKernel(const Context& dev_ctx,
const DenseTensor& x_in,
const DenseTensor& c0_in,
const optional<DenseTensor>& h0_in,
const DenseTensor& attention_weight_in,
const optional<DenseTensor>& attention_bias_in,
const optional<DenseTensor>& attention_scalar_in,
const optional<DenseTensor>& attention_scalar_bias_in,
const DenseTensor& lstm_weight_in,
const DenseTensor& lstm_bias_in,
const std::string& gate_activation,
const std::string& cell_activation,
const std::string& candidate_activation,
DenseTensor* hidden,
DenseTensor* cell,
DenseTensor* attentioned_x,
DenseTensor* attention_fc_out,
DenseTensor* lstm_x,
DenseTensor* lstm_out) {
auto* x = &x_in;
auto* h0 = h0_in.get_ptr();
auto* c0 = &c0_in;
auto* atten_w = &attention_weight_in;
auto* atten_b = attention_bias_in.get_ptr();
auto* atten_scalar = attention_scalar_in.get_ptr();
auto* atten_scalar_bias = attention_scalar_bias_in.get_ptr();
auto* lstm_w = &lstm_weight_in;
auto* lstm_b = &lstm_bias_in;
auto* hidden_out = hidden;
auto* cell_out = cell;
auto* atted_x = attentioned_x;
auto* fc_out = attention_fc_out;
// some shape should be reshape here since infershape can not get lod info
auto x_lod = x->lod();
const int N = static_cast<int>(x_lod[0].size() - 1); // batch size
auto x_dims = x->dims(); // T x M
auto w_dims = lstm_w->dims(); // (D+M) x 4D
const int total_T = static_cast<int>(x_dims[0]);
const int M = static_cast<int>(x_dims[1]); // x frame size
const int D = static_cast<int>(w_dims[1] / 4); // gate frame size
const int D2 = static_cast<int>(D * 2);
const int D3 = static_cast<int>(D * 3);
const int D4 = static_cast<int>(w_dims[1]);
int max_seq_len = static_cast<int>(x_lod[0][1]);
for (int i = 1; i < N; ++i) {
int len = static_cast<int>(x_lod[0][i + 1] - x_lod[0][i]);
max_seq_len = max_seq_len < len ? len : max_seq_len;
}
PADDLE_ENFORCE_EQ(
x_lod.size(),
1UL,
common::errors::InvalidArgument("Input(X)'s lod size must be 1."));
PADDLE_ENFORCE_EQ(
c0->dims()[0],
N,
common::errors::InvalidArgument("C0 dims should be %d x %d.", N, D));
fc_out->Resize({max_seq_len, 1});
std::function<void(const int, const T*, T*)> act_gate, act_cell, act_cand;
auto& act_gate_str = gate_activation;
auto& act_cell_str = cell_activation;
auto& act_cand_str = candidate_activation;
if (backends::cpu::MayIUse(backends::cpu::avx)) {
funcs::VecActivations<T, backends::cpu::avx> act_functor;
act_gate = act_functor(act_gate_str);
act_cell = act_functor(act_cell_str);
act_cand = act_functor(act_cand_str);
} else {
funcs::VecActivations<T, backends::cpu::isa_any> act_functor;
act_gate = act_functor(act_gate_str);
act_cell = act_functor(act_cell_str);
act_cand = act_functor(act_cand_str);
}
const T* x_data = x->data<T>();
const T* h0_data = h0 ? h0->data<T>() : NULL;
const T* c0_data = c0->data<T>();
const T* lstm_w_data = lstm_w->data<T>();
const T* lstm_b_data = lstm_b->data<T>();
const T* atten_w_data = atten_w->data<T>();
const T* atten_b_data = atten_b ? atten_b->data<T>() : NULL;
const T* atten_scalar_data = atten_scalar ? atten_scalar->data<T>() : NULL;
const T* atten_scalar_bias_data =
atten_scalar_bias ? atten_scalar_bias->data<T>() : NULL;
T* hidden_out_data = dev_ctx.template Alloc<T>(hidden_out);
T* cell_out_data = dev_ctx.template Alloc<T>(cell_out);
T* atted_x_data = dev_ctx.template Alloc<T>(atted_x);
T* fc_out_data = dev_ctx.template Alloc<T>(fc_out);
T* lstm_x_data = dev_ctx.template Alloc<T>(lstm_x);
T* lstm_out_data = dev_ctx.template Alloc<T>(lstm_out);
// x(TxM) * fc (Mx1) part of atten_wgt(M+D)x1
auto blas = funcs::GetBlas<CPUContext, T>(dev_ctx);
funcs::FCFunctor<Context, T> fc;
fc(dev_ctx, total_T, 1, M, x_data, atten_w_data, atted_x_data, atten_b_data);
const T* cur_atten_x_data = atted_x_data;
const T* cur_x_data = x_data;
const T* prev_cell_data = NULL;
const T* prev_hidden_data = NULL;
T* cur_cell_out_data = cell_out_data;
T* cur_hidden_out_data = hidden_out_data;
for (int i = 0; i < N; ++i) {
int seq_len = static_cast<int>(x_lod[0][i + 1] - x_lod[0][i]);
prev_cell_data = c0_data + i * D;
prev_hidden_data = h0_data ? h0_data + i * D : NULL;
for (int step = 0; step < seq_len; ++step) {
/// 1. compute attention vector
// 1a. prev_cell(1xD) * fc(D) rest part of atten_wgt
T prev_cell_bias = blas.DOT(D, prev_cell_data, atten_w_data + M);
// 1b. add cell bias and relu
bias_relu<T>(seq_len, cur_atten_x_data, &prev_cell_bias, fc_out_data);
// 1c. fc scalar
if (atten_scalar_data) {
blas.SCAL(seq_len, *atten_scalar_data, fc_out_data);
bias_relu<T>(seq_len, fc_out_data, atten_scalar_bias_data, fc_out_data);
}
// 1d. softmax
vec_softmax<T>(seq_len, fc_out_data, fc_out_data);
// mul x(seq_len*M) and sum pool
fc(dev_ctx, 1, M, seq_len, fc_out_data, cur_x_data, lstm_x_data);
/// 2. compute LSTM step
// lstm weight : concat[forget , input , output , tilde]
// shape : (D + M) x (4 * D)
// fc inputX(1xM) * weightX(M*(4D)) => 1 x 4D
blas.MatMul(1, D4, M, lstm_x_data, lstm_w_data + D * D4, lstm_out_data);
if (prev_hidden_data) {
blas.GEMM(CblasNoTrans,
CblasNoTrans,
1,
D4,
D,
static_cast<T>(1),
prev_hidden_data,
D,
lstm_w_data,
D4,
static_cast<T>(1),
lstm_out_data,
D4);
}
// since input is 1xM, so can use add bias
blas.VADD(D4, lstm_b_data, lstm_out_data, lstm_out_data);
// gate act: sigmoid
act_gate(D3, lstm_out_data, lstm_out_data);
// candidate act: tanh
act_cand(D, lstm_out_data + D3, lstm_out_data + D3);
// a = forget * prev_cell
blas.VMUL(D, lstm_out_data, prev_cell_data, lstm_out_data);
// b = input * tilde
blas.VMUL(D, lstm_out_data + D, lstm_out_data + D3, lstm_out_data + D);
// cell_out = a + b
blas.VADD(D, lstm_out_data, lstm_out_data + D, cur_cell_out_data);
// state act tanh(cell_out) * output_gate
act_cell(D, cur_cell_out_data, lstm_out_data);
blas.VMUL(D, lstm_out_data, lstm_out_data + D2, cur_hidden_out_data);
prev_hidden_data = cur_hidden_out_data;
prev_cell_data = cur_cell_out_data;
cur_cell_out_data = cur_cell_out_data + D;
cur_hidden_out_data = cur_hidden_out_data + D;
}
cur_x_data = cur_x_data + seq_len * M;
cur_atten_x_data = cur_atten_x_data + seq_len;
}
}
} // namespace phi
PD_REGISTER_KERNEL(
attention_lstm, CPU, ALL_LAYOUT, phi::AttentionLSTMKernel, float, double) {}