// 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 #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(); \ const T *wx_data = wx->data(); \ const T *wh_data = wh->data(); \ /* diagonal weight*/ \ const T *wp_data = bias->data() + 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(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, CPUPlace>::Cache().At( \ attr); \ auto ComputeCtHt = \ phi::jit::KernelFuncs, CPUPlace>::Cache().At( \ attr) // Wh GEMM #define GEMM_WH_ADDON(bs, prev, out) \ blas.GEMM(CblasNoTrans, \ CblasNoTrans, \ bs, \ D4, \ D, \ static_cast(1), \ prev, \ D, \ wh_data, \ D4, \ static_cast(1), \ out, \ D4) template 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 &h0_in, const optional &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 &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(x_dims[0]); const int N = static_cast(x_lod[0].size() - 1); const T *h0_data = h0 ? h0->data() : nullptr; const T *c0_data = c0 ? c0->data() : nullptr; T *xx_data = dev_ctx.template Alloc(xx); T *h_out_data = dev_ctx.template Alloc(hidden_out); T *c_out_data = dev_ctx.template Alloc(cell_out); auto blas = funcs::GetBlas(dev_ctx); funcs::FCFunctor fc; fc(dev_ctx, total_T, D4, M, x_data, wx_data, xx_data, bias->data()); 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(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 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 &h0_in, const optional &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 &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(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(xx); T *batched_input_data = dev_ctx.template Alloc(batched_input); T *batched_c_out_data = dev_ctx.template Alloc(batched_c_out); T *batched_h_out_data = dev_ctx.template Alloc(batched_h_out); dev_ctx.template Alloc(hidden_out); dev_ctx.template Alloc(cell_out); funcs::DenseTensor2BatchFunctor to_batch; auto blas = funcs::GetBlas(dev_ctx); funcs::FCFunctor fc; if (M > D4) { fc(dev_ctx, x_dims[0], D4, M, x_data, wx_data, xx_data, bias->data()); 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()); } auto batched_lod = batched_input->lod(); const auto &seq_order = batched_lod[2]; const int max_bs = static_cast(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(reordered_h0); T *reordered_c0_data = dev_ctx.template Alloc(reordered_c0); const T *h0_data = h0->data(); const T *c0_data = c0->data(); 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(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(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 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 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 &h0_in, const optional &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 &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(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(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) {}