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

486 lines
16 KiB
Plaintext

// Copyright (c) 2022 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/kernels/rnn_grad_kernel.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/core/tensor_utils.h"
#include "paddle/phi/kernels/empty_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/gpu/rnn_functor.h"
namespace phi {
#ifdef PADDLE_WITH_HIP
template <typename T>
void TensorToPermutedWeight(const Place &place,
gpuStream_t stream,
const DenseTensor &tensor,
std::vector<DenseTensor *> *weight_grad_list,
const gpuRNNMode_t rnn_mode,
bool is_bidirec) {
if (is_bidirec) {
for (size_t i = 0; i < weight_grad_list->size(); i += 4) {
auto tmp = (*weight_grad_list)[i + 1];
(*weight_grad_list)[i + 1] = (*weight_grad_list)[i + 2];
(*weight_grad_list)[i + 2] = tmp;
}
}
size_t weight_offset = 0;
for (size_t i = 0; i < weight_grad_list->size(); ++i) {
auto numel_size = (*weight_grad_list)[i]->numel();
DenseTensor temp;
temp.Resize({numel_size});
temp.ShareDataWith(tensor.Slice(weight_offset, weight_offset + numel_size));
if (rnn_mode == miopenLSTM) {
std::vector<DenseTensor> split_tensor = temp.Chunk(4, 0);
WeightListToTensor<T>(
place,
stream,
{split_tensor[0], split_tensor[1], split_tensor[3], split_tensor[2]},
(*weight_grad_list)[i]);
} else if (rnn_mode == miopenGRU) {
std::vector<DenseTensor> split_tensor = temp.Chunk(3, 0);
WeightListToTensor<T>(place,
stream,
{split_tensor[1], split_tensor[0], split_tensor[2]},
(*weight_grad_list)[i]);
} else {
WeightListToTensor<T>(place, stream, {temp}, (*weight_grad_list)[i]);
}
weight_offset += numel_size;
}
if (is_bidirec) {
for (size_t i = 0; i < weight_grad_list->size(); i += 4) {
auto tmp = (*weight_grad_list)[i + 1];
(*weight_grad_list)[i + 1] = (*weight_grad_list)[i + 2];
(*weight_grad_list)[i + 2] = tmp;
}
}
}
#endif
template <typename T, typename Context>
void RnnGradKernel(const Context &dev_ctx,
const DenseTensor &x,
const std::vector<const DenseTensor *> &pre_state,
const std::vector<const DenseTensor *> &weight_list,
const optional<DenseTensor> &sequence_length,
const DenseTensor &out,
const DenseTensor &dropout_state,
const DenseTensor &reserve,
const DenseTensor &out_grad,
const std::vector<const DenseTensor *> &state_grad,
float dropout_prob,
bool is_bidirec,
int input_size UNUSED,
int hidden_size,
int num_layers,
const std::string &mode,
int seed,
bool is_test,
DenseTensor *x_grad,
std::vector<DenseTensor *> pre_state_grad,
std::vector<DenseTensor *> weight_grad_list) {
#ifdef PADDLE_WITH_HIP
miopenRNNMode_t rnn_mode = miopenLSTM;
if (mode == "LSTM")
rnn_mode = miopenLSTM;
else if (mode == "GRU")
rnn_mode = miopenGRU;
else if (mode == "RNN_RELU")
rnn_mode = miopenRNNRELU;
else if (mode == "RNN_TANH")
rnn_mode = miopenRNNTANH;
#else
cudnnRNNMode_t rnn_mode = CUDNN_LSTM;
if (mode == "LSTM")
rnn_mode = CUDNN_LSTM;
else if (mode == "GRU")
rnn_mode = CUDNN_GRU;
else if (mode == "RNN_RELU")
rnn_mode = CUDNN_RNN_RELU;
else if (mode == "RNN_TANH")
rnn_mode = CUDNN_RNN_TANH;
#endif
else
PADDLE_THROW(common::errors::InvalidArgument(
"rnn_mode should be LSTM, GRU, RNN_RELU or RNN_TANH, but received: "
"%s.",
mode));
auto handle = dev_ctx.cudnn_handle();
auto place = dev_ctx.GetPlace();
auto weight_numel = std::accumulate(
weight_list.begin(),
weight_list.end(),
0,
[](int64_t num, const DenseTensor *t) { return num + t->numel(); });
bool continuous =
IsContinuous<T, std::vector<const DenseTensor *>>(weight_list);
auto stream = dev_ctx.stream();
DenseTensor weight_whole;
T *weight_data = nullptr;
#ifdef PADDLE_WITH_HIP
// Need to permute weight, set continuous to false
continuous = false;
#endif
if (!continuous) {
weight_whole.Resize({weight_numel});
dev_ctx.template Alloc<T>(&weight_whole);
#ifdef PADDLE_WITH_HIP
// MIOPEN need to permute weight for miopenLSTM or miopenGRU
std::vector<const DenseTensor *> weight_list_tmp = weight_list;
WeightToPermutedTensor<T>(
place, stream, &weight_list_tmp, &weight_whole, rnn_mode, is_bidirec);
#else
WeightToTensor<T>(place, stream, weight_list, &weight_whole);
#endif
weight_data = weight_whole.data<T>();
} else {
weight_data = const_cast<T *>(weight_list[0]->data<T>()); // NOLINT
}
DenseTensor weight_grad = Full<T>(dev_ctx, {weight_numel}, 0);
T *weight_grad_data = weight_grad.data<T>();
#ifdef PADDLE_WITH_HIP
// MIOPEN need to permute weight_grad_list, so do not share data with
// weight_grad
for (size_t i = 0; i < weight_grad_list.size(); ++i) {
dev_ctx.template Alloc<T>(weight_grad_list[i]);
}
#else
int offset = 0;
for (auto &item : weight_grad_list) {
size_t len = item->numel();
auto dim = item->dims();
item->ShareDataWith(weight_grad.Slice(static_cast<int64_t>(offset),
static_cast<int64_t>(offset + len)))
.Resize(dim);
offset += len;
}
#endif
DenseTensor input_grad_value;
if (!x_grad) {
x_grad = &input_grad_value;
x_grad->Resize(x.dims());
}
auto *init_h_data = pre_state[0]->data<T>();
// auto *last_h_data = state[0]->data<T>();
auto *last_h_grad_data = state_grad[0]->data<T>();
const T *init_c_data = nullptr;
// const T *last_c_data = nullptr;
const T *last_c_grad_data = nullptr;
T *init_h_grad_data = !pre_state_grad.empty() && pre_state_grad[0]
? dev_ctx.template Alloc<T>(pre_state_grad[0])
: nullptr;
T *init_c_grad_data = nullptr;
#ifdef PADDLE_WITH_HIP
if (rnn_mode == miopenLSTM) {
#else
if (rnn_mode == CUDNN_LSTM) {
#endif
init_c_data = pre_state[1]->data<T>();
// last_c_data = state[1]->data<T>();
last_c_grad_data = state_grad[1]->data<T>();
init_c_grad_data = pre_state_grad.size() >= 2 && pre_state_grad[1]
? dev_ctx.template Alloc<T>(pre_state_grad[1])
: nullptr;
}
auto *out_data = out.data<T>();
auto *out_grad_data = out_grad.data<T>();
// need check exist
T *x_grad_data = nullptr;
if (x_grad) {
x_grad_data = dev_ctx.template Alloc<T>(x_grad);
}
bool has_seq_length = sequence_length.is_initialized();
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_EQ(has_seq_length,
false,
common::errors::InvalidArgument(
"ROCm do not support SequenceLength yet."));
#endif
std::vector<int> SequenceLength;
if (has_seq_length) {
SequenceLength = GetVectorFromTensor<int>(sequence_length.get_ptr());
}
auto input_dims = x.dims();
int seq_length = input_dims[0];
int batch_size = input_dims[1];
int input_size_local = input_dims[2];
size_t workspace_size;
size_t reserve_size;
RNNDescriptors rnn(seq_length,
batch_size,
input_size_local,
hidden_size,
num_layers,
dropout_prob,
seed,
weight_numel,
rnn_mode,
is_bidirec,
is_test);
rnn.Create<T>(handle,
dev_ctx,
SequenceLength,
&workspace_size,
&reserve_size,
const_cast<DenseTensor *>(&dropout_state)); // NOLINT
DenseTensor workspace_data_ =
Empty<uint8_t>(dev_ctx, {static_cast<int64_t>(workspace_size)});
const uint8_t *reserve_data = reserve.data<uint8_t>();
#if CUDNN_VERSION >= 90000
if (x_grad) {
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cudnnRNNBackwardData_v8(handle,
rnn.rnn_desc(),
nullptr,
rnn.y_seq_desc(),
out_data,
out_grad_data,
rnn.x_seq_desc(),
x_grad_data,
rnn.init_h_desc(),
init_h_data,
last_h_grad_data,
init_h_grad_data,
rnn.init_c_desc(),
init_c_data,
last_c_grad_data,
init_c_grad_data,
rnn.weights_size(),
weight_data,
workspace_size,
workspace_data_.data<uint8_t>(),
reserve_size,
const_cast<uint8_t *>(reserve_data)));
}
if (!weight_grad_list.empty()) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cudnnRNNBackwardWeights_v8(
handle,
rnn.rnn_desc(),
CUDNN_WGRAD_MODE_ADD,
nullptr,
rnn.x_seq_desc(),
x.data<T>(),
rnn.init_h_desc(),
init_h_data,
rnn.y_seq_desc(),
out.data<T>(),
rnn.weights_size(),
weight_grad_data,
workspace_size,
workspace_data_.data<uint8_t>(),
reserve_size,
const_cast<uint8_t *>(reserve_data)));
}
#else
if (!has_seq_length) {
if (x_grad) {
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::miopenRNNBackwardData(handle,
rnn.rnn_desc(),
seq_length,
rnn.y_descs(),
out_data,
rnn.y_descs(),
out_grad_data,
rnn.last_h_desc(),
last_h_grad_data,
rnn.last_c_desc(),
last_c_grad_data,
rnn.weight_desc(),
weight_data,
rnn.init_h_desc(),
init_h_data,
rnn.init_c_desc(),
init_c_data,
rnn.x_descs(),
x_grad_data,
rnn.init_h_desc(),
init_h_grad_data,
rnn.init_c_desc(),
init_c_grad_data,
workspace_data_.data<uint8_t>(),
workspace_size,
const_cast<uint8_t *>(reserve_data),
reserve_size));
#else
// This interface is used when the input/output is unpadded.
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cudnnRNNBackwardData(
handle,
rnn.rnn_desc(),
seq_length,
rnn.y_descs(),
out_data,
rnn.y_descs(),
out_grad_data,
rnn.last_h_desc(),
last_h_grad_data,
rnn.last_c_desc(),
last_c_grad_data,
rnn.weight_desc(),
weight_data,
rnn.init_h_desc(),
init_h_data,
rnn.init_c_desc(),
init_c_data,
rnn.x_descs(),
x_grad_data,
rnn.init_h_desc(),
init_h_grad_data,
rnn.init_c_desc(),
init_c_grad_data,
workspace_data_.data<uint8_t>(),
workspace_size,
const_cast<uint8_t *>(reserve_data), // NOLINT
reserve_size));
#endif
}
if (!weight_grad_list.empty()) {
#ifdef PADDLE_WITH_HIP
PADDLE_ENFORCE_GPU_SUCCESS(dynload::miopenRNNBackwardWeights(
handle,
rnn.rnn_desc(),
seq_length,
rnn.x_descs(),
x.data<T>(),
rnn.init_h_desc(),
init_h_data,
rnn.y_descs(),
out.data<T>(),
rnn.weight_desc(),
weight_grad_data,
workspace_data_.data<uint8_t>(),
workspace_size,
const_cast<uint8_t *>(reserve_data), // NOLINT
reserve_size));
// permute weight grad list from weight grad tensor
TensorToPermutedWeight<T>(
place, stream, weight_grad, &weight_grad_list, rnn_mode, is_bidirec);
#else
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cudnnRNNBackwardWeights(
handle,
rnn.rnn_desc(),
seq_length,
rnn.x_descs(),
x.data<T>(),
rnn.init_h_desc(),
init_h_data,
rnn.y_descs(),
out.data<T>(),
workspace_data_.data<uint8_t>(),
workspace_size,
rnn.weight_desc(),
weight_grad_data,
const_cast<uint8_t *>(reserve_data), // NOLINT
reserve_size));
#endif
}
} else {
#if defined(PADDLE_WITH_CUDA) && CUDNN_VERSION >= 7201
// for train
// This interface is used when the input/output is padded.
if (x_grad) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cudnnRNNBackwardDataEx(
handle,
rnn.rnn_desc(),
rnn.y_seq_desc(),
out_data,
rnn.y_seq_desc(),
out_grad_data,
nullptr,
nullptr,
rnn.last_h_desc(),
last_h_grad_data,
rnn.last_c_desc(),
last_c_grad_data,
rnn.weight_desc(),
weight_data,
rnn.init_h_desc(),
init_h_data,
rnn.init_c_desc(),
init_c_data,
rnn.x_seq_desc(),
x_grad_data,
rnn.init_h_desc(),
init_h_grad_data,
rnn.init_c_desc(),
init_c_grad_data,
nullptr,
nullptr,
workspace_data_.data<uint8_t>(),
workspace_size,
const_cast<uint8_t *>(reserve_data), // NOLINT
reserve_size));
}
if (!weight_grad_list.empty()) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cudnnRNNBackwardWeightsEx(
handle,
rnn.rnn_desc(),
rnn.x_seq_desc(),
x.data<T>(),
rnn.init_h_desc(),
init_h_data,
rnn.y_seq_desc(),
out.data<T>(),
workspace_data_.data<uint8_t>(),
workspace_size,
rnn.weight_desc(),
weight_grad_data,
const_cast<uint8_t *>(reserve_data), // NOLINT
reserve_size));
}
#else
PADDLE_THROW(common::errors::Unavailable(
"The padded input of rnn is supported by cudnnRNNBackwardDataEx, "
"cudnnRNNBackwardWeightsEx, but it only works when the version "
"of cudnn is larger than 7.2.1"));
#endif
}
#endif // end CUDNN_VERSION >= 90000
}
} // namespace phi
#ifdef PADDLE_WITH_HIP
// MIOPEN do not support double
PD_REGISTER_KERNEL(rnn_grad, GPU, ALL_LAYOUT, phi::RnnGradKernel, float) {}
#else
PD_REGISTER_KERNEL(
rnn_grad, GPU, ALL_LAYOUT, phi::RnnGradKernel, float, double) {}
#endif