// 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 // for memcpy #include #include #include "paddle/common/errors.h" #include "paddle/phi/core/enforce.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/core/tensor_utils.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::fusion { #define INIT_BASE_DEFINES \ auto x_lod = x.lod(); \ auto x_dims = x.dims(); /* T x M*/ \ auto x_mat_dims = (x_dims.size() == 3 && x_dims[1] == 1) \ ? common::flatten_to_2d(x_dims, 1) \ : x_dims; \ auto wh_dims = weight_h.dims(); /* D x 3D*/ \ const int total_T = x_mat_dims[0]; \ const int D3 = wh_dims[1] #define INIT_OTHER_DEFINES \ const int M = x_mat_dims[1]; \ const int D = wh_dims[0]; \ const int D2 = D * 2; \ const phi::jit::gru_attr_t attr(D, \ phi::jit::to_kerneltype(gate_activation), \ phi::jit::to_kerneltype(activation)); \ phi::jit::gru_t one_step; \ auto ComputeH1 = \ phi::jit::KernelFuncs, CPUPlace>::Cache().At( \ attr); \ auto ComputeHtPart1 = \ phi::jit::KernelFuncs, CPUPlace>::Cache() \ .At(attr); \ auto ComputeHtPart2 = \ phi::jit::KernelFuncs, CPUPlace>::Cache() \ .At(attr); \ const T* x_data = x.data(); \ const T* wx_data = weight_x.data(); \ const T* wh_data = weight_h.data(); \ T* xx_data = dev_ctx.template Alloc(xx) template void SeqCompute(const Context& dev_ctx, const DenseTensor& x, const optional& h0, const DenseTensor& weight_x, const DenseTensor& weight_h, const optional& bias, const std::string& activation, const std::string& gate_activation, const bool is_reverse, const bool use_seq, DenseTensor* reordered_h0, DenseTensor* xx, DenseTensor* batched_input, DenseTensor* batched_out, DenseTensor* hidden) { INIT_BASE_DEFINES; INIT_OTHER_DEFINES; const int N = static_cast(x_lod[0].size() - 1); const T* h0_data = h0 ? h0->data() : nullptr; const T* wh_state_data = wh_data + D * D2; T* hidden_out_data = dev_ctx.template Alloc(hidden); auto blas = funcs::GetBlas(dev_ctx); funcs::FCFunctor fc; fc(dev_ctx, total_T, D3, M, x_data, wx_data, xx_data, bias ? bias->data() : nullptr); int xx_offset = D3; int gate_offset = D; if (is_reverse) { const int offset = (total_T - 1) * D; xx_data = xx_data + offset * 3; hidden_out_data = hidden_out_data + offset; xx_offset = -D3; gate_offset = -D; } auto move_step = [&]() { xx_data = xx_data + xx_offset; hidden_out_data = hidden_out_data + gate_offset; }; 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_hidden_data = nullptr; int tstart = 0; if (h0_data) { prev_hidden_data = h0_data + bid * D; } else { one_step.gates = xx_data; one_step.ht = hidden_out_data; ComputeH1(&one_step, &attr); prev_hidden_data = hidden_out_data; tstart = 1; move_step(); } for (int step = tstart; step < seq_len; ++step) { // gemm prev * (Wu + Wr) blas.GEMM(CblasNoTrans, CblasNoTrans, 1, D2, D, static_cast(1), prev_hidden_data, D, wh_data, D2, static_cast(1), xx_data, D3); one_step.gates = xx_data; one_step.ht_1 = prev_hidden_data; one_step.ht = hidden_out_data; ComputeHtPart1(&one_step, &attr); // gemm rt * Ws blas.GEMM(CblasNoTrans, CblasNoTrans, 1, D, D, static_cast(1), hidden_out_data, D, wh_state_data, D, static_cast(1), xx_data + D2, D3); ComputeHtPart2(&one_step, &attr); // save prev prev_hidden_data = hidden_out_data; move_step(); } } } template void BatchCompute(const Context& dev_ctx, const DenseTensor& x, const optional& h0, const DenseTensor& weight_x, const DenseTensor& weight_h, const optional& bias, const std::string& activation, const std::string& gate_activation, const bool is_reverse, const bool use_seq, DenseTensor* reordered_h0, DenseTensor* xx, DenseTensor* batched_input, DenseTensor* batched_out, DenseTensor* hidden) { INIT_BASE_DEFINES; if (x_lod[0].size() == 2) { xx->Resize({total_T, D3}); SeqCompute(dev_ctx, x, h0, weight_x, weight_h, bias, activation, gate_activation, is_reverse, use_seq, reordered_h0, xx, batched_input, batched_out, hidden); return; } INIT_OTHER_DEFINES; T* batched_input_data = dev_ctx.template Alloc(batched_input); T* batched_out_data = dev_ctx.template Alloc(batched_out); dev_ctx.template Alloc(hidden); auto blas = funcs::GetBlas(dev_ctx); funcs::DenseTensor2BatchFunctor to_batch; funcs::FCFunctor fc; if (M > D3) { fc(dev_ctx, total_T, D3, M, x_data, wx_data, xx_data, bias ? bias->data() : nullptr); 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, total_T, D3, M, xx_data, wx_data, batched_input_data, bias ? bias->data() : nullptr); } 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}); int tstart = 0; T* prev_hidden_data = nullptr; if (h0) { // reorder h0 T* reordered_h0_data = dev_ctx.template Alloc(reordered_h0); const T* h0_data = h0->data(); prev_hidden_data = reordered_h0_data; size_t sz = sizeof(T) * D; for (int i = 0; i < max_bs; ++i) { std::memcpy(reordered_h0_data, h0_data + seq_order[i] * D, sz); reordered_h0_data += D; } } else { // compute without h0 T* cur_in_data = batched_input_data; T* cur_out_data = batched_out_data; // W: {W_update, W_reset; W_state} for (int i = 0; i < max_bs; ++i) { one_step.gates = cur_in_data; one_step.ht = cur_out_data; ComputeH1(&one_step, &attr); // add offset cur_in_data += D3; cur_out_data += D; } tstart = 1; prev_hidden_data = batched_out_data; } // Then start from next const T* wh_state_data = wh_data + D * D2; const auto& batch_starts = batched_lod[0]; const int max_seq_len = static_cast(batch_starts.size() - 1); batched_input_data = batched_input_data + tstart * max_bs * D3; batched_out_data = batched_out_data + tstart * max_bs * D; for (int step = tstart; step < max_seq_len; ++step) { const int cur_bs = static_cast(batch_starts[step + 1] - batch_starts[step]); // gemm prev * (Wu + Wr) blas.GEMM(CblasNoTrans, CblasNoTrans, cur_bs, D2, D, static_cast(1), prev_hidden_data, D, wh_data, D2, static_cast(1), batched_input_data, D3); T* cur_batched_data = batched_input_data; T* cur_out_data = batched_out_data; T* cur_prev_hidden_data = prev_hidden_data; for (int i = 0; i < cur_bs; ++i) { one_step.gates = cur_batched_data; one_step.ht_1 = cur_prev_hidden_data; one_step.ht = cur_out_data; ComputeHtPart1(&one_step, &attr); cur_batched_data += D3; cur_prev_hidden_data += D; cur_out_data += D; } cur_batched_data = batched_input_data; cur_out_data = batched_out_data; blas.GEMM(CblasNoTrans, CblasNoTrans, cur_bs, D, D, static_cast(1), cur_out_data, D, wh_state_data, D, static_cast(1), cur_batched_data + D2, D3); cur_prev_hidden_data = prev_hidden_data; for (int i = 0; i < cur_bs; ++i) { one_step.gates = cur_batched_data; one_step.ht_1 = cur_prev_hidden_data; one_step.ht = cur_out_data; ComputeHtPart2(&one_step, &attr); cur_batched_data += D3; cur_prev_hidden_data += D; cur_out_data += D; } prev_hidden_data = batched_out_data; batched_out_data = cur_out_data; batched_input_data = cur_batched_data; } funcs::Batch2DenseTensorFunctor to_seq; batched_out->set_lod(batched_lod); to_seq(dev_ctx, *batched_out, hidden); } template void FusionGRUKernel(const Context& dev_ctx, const DenseTensor& x, const optional& h0, const DenseTensor& weight_x, const DenseTensor& weight_h, const optional& 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) { if (use_seq) { SeqCompute(dev_ctx, x, h0, weight_x, weight_h, bias, activation, gate_activation, is_reverse, use_seq, reordered_h0, xx, batched_input, batched_out, hidden); } else { BatchCompute(dev_ctx, x, h0, weight_x, weight_h, bias, activation, gate_activation, is_reverse, use_seq, reordered_h0, xx, batched_input, batched_out, hidden); } } } // namespace phi::fusion PD_REGISTER_KERNEL( fusion_gru, CPU, ALL_LAYOUT, phi::fusion::FusionGRUKernel, float, double) {}