Files
paddlepaddle--paddle/paddle/phi/kernels/funcs/values_vectors_functor.h
T
2026-07-13 12:40:42 +08:00

702 lines
28 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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.
#pragma once
#ifdef PADDLE_WITH_CUDA
#include "paddle/phi/backends/dynload/cusolver.h"
#endif // PADDLE_WITH_CUDA
#ifdef PADDLE_WITH_HIP
// thrust/device_vector.h requires hipcc
// (rocThrust 7.0+ pulls in rocprim)
#ifdef __HIPCC__
#include <thrust/device_vector.h>
#endif
#include "paddle/phi/backends/dynload/rocsolver.h"
#endif // PADDLE_WITH_HIP
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
#include "paddle/common/errors.h"
#endif
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/kernels/funcs/complex_functors.h"
#include "paddle/phi/kernels/funcs/lapack/lapack_function.h"
#include "paddle/phi/kernels/transpose_kernel.h"
namespace phi {
namespace funcs {
inline int64_t GetBatchSize(const DDim &dims) {
int64_t batch_size = 1;
auto dim_size = dims.size();
for (int i = 0; i < dim_size - 2; ++i) {
batch_size *= dims[i];
}
return batch_size;
}
static void CheckEighResult(const int batch, const int info) {
PADDLE_ENFORCE_LE(
info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: the [%d] off-diagonal elements of an intermediate "
"tridiagonal form did not converge to zero",
batch,
info));
PADDLE_ENFORCE_GE(
info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: the [%d] argument had an illegal value",
batch,
info));
}
#ifdef PADDLE_WITH_CUDA
#if CUDA_VERSION >= 11031
static bool use_cusolver_syevj_batched = true;
#else
static bool use_cusolver_syevj_batched = false;
#endif
#define CUDASOLVER_SYEVJ_BATCHED_BUFFERSIZE_ARGTYPES(scalar_t, value_t) \
cusolverDnHandle_t handle, cusolverEigMode_t jobz, cublasFillMode_t uplo, \
int n, const scalar_t *A, int lda, const value_t *W, int *lwork, \
syevjInfo_t params, int batchsize
template <class scalar_t, class value_t = scalar_t>
void syevjBatched_bufferSize(
CUDASOLVER_SYEVJ_BATCHED_BUFFERSIZE_ARGTYPES(scalar_t, value_t)) {
PADDLE_THROW(common::errors::InvalidArgument(
"syevjBatched_bufferSize: not implemented for %s",
typeid(scalar_t).name()));
}
template <>
inline void syevjBatched_bufferSize<float>(
CUDASOLVER_SYEVJ_BATCHED_BUFFERSIZE_ARGTYPES(float, float)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnSsyevjBatched_bufferSize(
handle, jobz, uplo, n, A, lda, W, lwork, params, batchsize));
}
template <>
inline void syevjBatched_bufferSize<double>(
CUDASOLVER_SYEVJ_BATCHED_BUFFERSIZE_ARGTYPES(double, double)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDsyevjBatched_bufferSize(
handle, jobz, uplo, n, A, lda, W, lwork, params, batchsize));
}
template <>
inline void syevjBatched_bufferSize<phi::complex64, float>(
CUDASOLVER_SYEVJ_BATCHED_BUFFERSIZE_ARGTYPES(phi::complex64, float)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCheevjBatched_bufferSize(
handle,
jobz,
uplo,
n,
reinterpret_cast<const cuComplex *>(A),
lda,
W,
lwork,
params,
batchsize));
}
template <>
inline void syevjBatched_bufferSize<phi::complex128, double>(
CUDASOLVER_SYEVJ_BATCHED_BUFFERSIZE_ARGTYPES(phi::complex128, double)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnZheevjBatched_bufferSize(
handle,
jobz,
uplo,
n,
reinterpret_cast<const cuDoubleComplex *>(A),
lda,
W,
lwork,
params,
batchsize));
}
#define CUDASOLVER_SYEVJ_BATCHED_ARGTYPES(scalar_t, value_t) \
cusolverDnHandle_t handle, cusolverEigMode_t jobz, cublasFillMode_t uplo, \
int n, scalar_t *A, int lda, value_t *W, scalar_t *work, int lwork, \
int *info, syevjInfo_t params, int batchsize
template <class scalar_t, class value_t = scalar_t>
void syevjBatched(CUDASOLVER_SYEVJ_BATCHED_ARGTYPES(scalar_t, value_t)) {
PADDLE_THROW(common::errors::InvalidArgument(
"syevjBatched: not implemented for %s", typeid(scalar_t).name()));
}
template <>
inline void syevjBatched<float>(CUDASOLVER_SYEVJ_BATCHED_ARGTYPES(float,
float)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnSsyevjBatched(
handle, jobz, uplo, n, A, lda, W, work, lwork, info, params, batchsize));
}
template <>
inline void syevjBatched<double>(CUDASOLVER_SYEVJ_BATCHED_ARGTYPES(double,
double)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDsyevjBatched(
handle, jobz, uplo, n, A, lda, W, work, lwork, info, params, batchsize));
}
template <>
inline void syevjBatched<phi::complex64, float>(
CUDASOLVER_SYEVJ_BATCHED_ARGTYPES(phi::complex64, float)) {
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCheevjBatched(handle,
jobz,
uplo,
n,
reinterpret_cast<cuComplex *>(A),
lda,
W,
reinterpret_cast<cuComplex *>(work),
lwork,
info,
params,
batchsize));
}
template <>
inline void syevjBatched<phi::complex128, double>(
CUDASOLVER_SYEVJ_BATCHED_ARGTYPES(phi::complex128, double)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnZheevjBatched(
handle,
jobz,
uplo,
n,
reinterpret_cast<cuDoubleComplex *>(A),
lda,
W,
reinterpret_cast<cuDoubleComplex *>(work),
lwork,
info,
params,
batchsize));
}
#endif
#if defined(PADDLE_WITH_CUDA) || defined(PADDLE_WITH_HIP)
static void CheckEighResult(const GPUContext &dev_ctx,
const int64_t batch_size,
int *info) {
std::vector<int> error_info(batch_size);
memory_utils::Copy(CPUPlace(),
error_info.data(),
dev_ctx.GetPlace(),
info,
sizeof(int) * batch_size,
dev_ctx.stream());
dev_ctx.Wait();
for (auto i = 0; i < batch_size; ++i) {
CheckEighResult(i, error_info[i]);
}
}
#endif
template <typename DeviceContext, typename T>
struct MatrixEighFunctor {
void operator()(const DeviceContext &dev_ctx,
const DenseTensor &input,
DenseTensor *eigen_values,
DenseTensor *eigen_vectors,
bool is_lower,
bool has_vectors);
};
// Calculates the eigenvalues and eigenvectors of Hermitian or real
// symmetric matrices, and uses the variable has_vectors to
// control whether to return the eigenvectors.
template <typename T>
struct MatrixEighFunctor<CPUContext, T> {
public:
void operator()(const CPUContext &dev_ctx,
const DenseTensor &input,
DenseTensor *eigen_values,
DenseTensor *eigen_vectors,
bool is_lower,
bool has_vectors) {
using ValueType = phi::dtype::Real<T>;
ValueType *out_value = dev_ctx.template Alloc<ValueType>(eigen_values);
DenseTensor input_trans;
// lapack is a column-major storage, transpose make the input to
// have a continuous memory layout
input_trans = TransposeLast2Dim<T>(dev_ctx, input);
T *input_vector = input_trans.data<T>();
auto dims = input.dims();
int dim_size = dims.size();
int64_t batch_size = GetBatchSize(dims);
int64_t vector_stride = dims[dim_size - 1] * dims[dim_size - 2];
int values_stride = dims[dim_size - 1];
char uplo = is_lower ? 'L' : 'U';
char jobz = has_vectors ? 'V' : 'N';
int n = dims[dim_size - 1];
int64_t lda = std::max<int64_t>(1, n);
// if work = -1, it means that you need to use the lapack function to
// query
// the optimal value
int lwork = -1; // The length of the array work
int lrwork = -1; // The dimension of the array rwork,rwork is REAL array
int liwork = -1; // The dimension of the array iwork
int iwork_opt = -1; // The optimal length of the array liwork
T lwork_opt = static_cast<T>(-1); // The optimal length of the array work
ValueType rwork_opt =
static_cast<ValueType>(-1); // The optimal length of the array rwork
int info = 0;
// Call lapackEigh to get the optimal size of work data
funcs::lapackEigh<T, ValueType>(jobz,
uplo,
n,
input_vector,
lda,
out_value,
&lwork_opt,
lwork,
&rwork_opt,
lrwork,
&iwork_opt,
liwork,
&info);
lwork = std::max<int>(1, static_cast<int>(lwork_opt));
liwork = std::max<int>(1, iwork_opt);
DenseTensor rwork_tensor;
ValueType *rwork_data = nullptr;
// complex type
if (input.type() == DataType::COMPLEX64 ||
input.type() == DataType::COMPLEX128) {
lrwork = std::max<int>(1, static_cast<int>(rwork_opt));
rwork_tensor.Resize({lrwork});
rwork_data = dev_ctx.template Alloc<ValueType>(&rwork_tensor);
}
DenseTensor iwork_tensor, work_tensor;
iwork_tensor.Resize({liwork});
int *iwork_data = dev_ctx.template Alloc<int>(&iwork_tensor);
work_tensor.Resize({lwork});
T *work_data = dev_ctx.template Alloc<T>(&work_tensor);
for (auto i = 0; i < batch_size; i++) {
auto *value_data = out_value + i * values_stride;
auto *input_data = input_vector + i * vector_stride;
funcs::lapackEigh<T, ValueType>(jobz,
uplo,
n,
input_data,
lda,
value_data,
work_data,
lwork,
rwork_data,
lrwork,
iwork_data,
liwork,
&info);
CheckEighResult(i, info);
}
if (has_vectors) {
PADDLE_ENFORCE_NOT_NULL(eigen_vectors,
common::errors::InvalidArgument(
"When has_vectors is true,"
"the eigenvectors needs to be calculated, "
"so the eigenvectors must be provided."));
input_trans = TransposeLast2Dim<T>(dev_ctx, input_trans);
eigen_vectors->ShareDataWith(input_trans);
}
}
};
// HIP code using thrust::device_vector requires hipcc
// (rocThrust 7.0+ pulls in rocprim)
#if defined(PADDLE_WITH_HIP) && defined(__HIPCC__)
#define ROCSOLVER_SYEVJ_BATCHED_ARGTYPES(scalar_t, value_t) \
solverHandle_t handle, rocblas_esort esort, rocblas_evect evect, \
rocblas_fill uplo, int n, scalar_t *const A[], int lda, \
const scalar_t abstol, scalar_t *residual, const int max_sweeps, \
int *n_sweeps, value_t *W, const int strideW, int *info, \
const int batch_count
template <class scalar_t, class value_t = scalar_t>
void syevjBatched(ROCSOLVER_SYEVJ_BATCHED_ARGTYPES(scalar_t, value_t)) {
PADDLE_THROW(common::errors::InvalidArgument(
"syevjBatched: not implemented for %s", typeid(scalar_t).name()));
}
template <>
inline void syevjBatched<float>(ROCSOLVER_SYEVJ_BATCHED_ARGTYPES(float,
float)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::rocsolver_ssyevj_batched(handle,
esort,
evect,
uplo,
n,
A,
lda,
abstol,
residual,
max_sweeps,
n_sweeps,
W,
strideW,
info,
batch_count));
}
template <>
inline void syevjBatched<double>(ROCSOLVER_SYEVJ_BATCHED_ARGTYPES(double,
double)) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::rocsolver_dsyevj_batched(handle,
esort,
evect,
uplo,
n,
A,
lda,
abstol,
residual,
max_sweeps,
n_sweeps,
W,
strideW,
info,
batch_count));
}
template <typename T>
struct MatrixEighFunctor<GPUContext, T> {
public:
void operator()(const GPUContext &dev_ctx,
const DenseTensor &input,
DenseTensor *eigen_values,
DenseTensor *eigen_vectors,
bool is_lower,
bool has_vectors) {
using ValueType = phi::dtype::Real<T>;
auto &dims = input.dims();
int dim_size = dims.size();
int64_t batch_size = GetBatchSize(dims);
int last_dim = dims[dim_size - 1];
int lda = std::max<int>(1, last_dim);
auto vector_stride = dims[dim_size - 1] * dims[dim_size - 2];
auto values_stride = dims[dim_size - 1];
rocblas_fill uplo = is_lower ? rocblas_fill_lower : rocblas_fill_upper;
rocblas_evect evect =
has_vectors ? rocblas_evect_original : rocblas_evect_none;
ValueType *out_value = dev_ctx.template Alloc<ValueType>(eigen_values);
DenseTensor input_trans = TransposeLast2Dim<T>(dev_ctx, input);
T *input_vector = input_trans.data<T>();
auto handle = dev_ctx.cusolver_dn_handle();
size_t total_bytes = sizeof(T) * batch_size + sizeof(int) * batch_size * 2;
auto info = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
total_bytes,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
auto *residual_ptr = reinterpret_cast<T *>(info->ptr());
auto *info_ptr = reinterpret_cast<int *>(residual_ptr + batch_size);
auto *n_sweeps_ptr = reinterpret_cast<int *>(info_ptr + batch_size);
std::vector<T *> output_ptrs;
for (int i = 0; i < batch_size; i++) {
output_ptrs.emplace_back(input_vector + i * vector_stride);
}
thrust::device_vector<T *> dev_output_ptrs(output_ptrs.begin(),
output_ptrs.end());
syevjBatched<T>(handle,
rocblas_esort_ascending,
evect,
uplo,
last_dim,
thrust::raw_pointer_cast(dev_output_ptrs.data()),
lda,
0,
residual_ptr,
100, // 100 max_sweeps default
n_sweeps_ptr,
out_value,
values_stride,
info_ptr,
batch_size);
CheckEighResult(dev_ctx, batch_size, info_ptr);
if (has_vectors) {
PADDLE_ENFORCE_NOT_NULL(eigen_vectors,
common::errors::InvalidArgument(
"When has_vectors is true,"
"the eigenvectors needs to be calculated,"
"so the eigenvectors must be provided."));
input_trans = TransposeLast2Dim<T>(dev_ctx, input_trans);
eigen_vectors->ShareDataWith(input_trans);
}
}
};
#endif
#ifdef PADDLE_WITH_CUDA
// Calculates the eigenvalues and eigenvectors of Hermitian or real
// symmetric matrices on GPU, and uses the variable has_vectors
// to control whether to return the eigenvectors.
template <typename T>
struct MatrixEighFunctor<GPUContext, T> {
public:
void operator()(const GPUContext &dev_ctx,
const DenseTensor &input,
DenseTensor *eigen_values,
DenseTensor *eigen_vectors,
bool is_lower,
bool has_vectors) {
using ValueType = phi::dtype::Real<T>;
int workspace_size = 0;
auto &dims = input.dims();
int dim_size = dims.size();
int64_t batch_size = GetBatchSize(dims);
int last_dim = dims[dim_size - 1];
int lda = std::max<int>(1, last_dim);
auto vector_stride = dims[dim_size - 1] * dims[dim_size - 2];
auto values_stride = dims[dim_size - 1];
cublasFillMode_t uplo =
is_lower ? CUBLAS_FILL_MODE_LOWER : CUBLAS_FILL_MODE_UPPER;
cusolverEigMode_t jobz =
has_vectors ? CUSOLVER_EIG_MODE_VECTOR : CUSOLVER_EIG_MODE_NOVECTOR;
ValueType *out_value = dev_ctx.template Alloc<ValueType>(eigen_values);
DenseTensor input_trans = TransposeLast2Dim<T>(dev_ctx, input);
T *input_vector = input_trans.data<T>();
// Precision loss will occur in some cases while using
// cusolverDnZheevjBatched to calculate in Paddle(cuda11.7) but it works
// well in Paddle(cuda10.2)
use_cusolver_syevj_batched = (use_cusolver_syevj_batched) &&
(batch_size > 1) &&
(input.dtype() != DataType::COMPLEX128);
bool use_cusolver_syevj = (input.dtype() == DataType::FLOAT32 &&
last_dim >= 32 && last_dim <= 512);
auto handle = dev_ctx.cusolver_dn_handle();
syevjInfo_t syevj_params;
if (use_cusolver_syevj_batched) {
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCreateSyevjInfo(&syevj_params));
syevjBatched_bufferSize<T>(handle,
jobz,
uplo,
last_dim,
input_vector,
lda,
out_value,
&workspace_size,
syevj_params,
batch_size);
} else if (use_cusolver_syevj) {
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCreateSyevjInfo(&syevj_params));
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnSsyevj_bufferSize(
dev_ctx.cusolver_dn_handle(),
jobz,
uplo,
last_dim,
reinterpret_cast<const float *>(input_vector),
lda,
reinterpret_cast<const float *>(out_value),
&workspace_size,
syevj_params));
} else {
EvdBuffer(dev_ctx.cusolver_dn_handle(),
jobz,
uplo,
last_dim,
input_vector,
lda,
out_value,
&workspace_size);
}
size_t total_bytes = sizeof(T) * workspace_size + sizeof(int) * batch_size;
auto work = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
total_bytes,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
auto *work_ptr = reinterpret_cast<T *>(work->ptr());
auto *info_ptr = reinterpret_cast<int *>(work_ptr + workspace_size);
for (auto i = 0; i < batch_size; ++i) {
auto *input_data = input_vector + i * vector_stride;
auto *value_data = out_value + i * values_stride;
if (use_cusolver_syevj_batched) {
syevjBatched<T>(handle,
jobz,
uplo,
last_dim,
input_data,
lda,
value_data,
work_ptr,
workspace_size,
&info_ptr[i],
syevj_params,
batch_size);
break;
} else if (use_cusolver_syevj) {
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnSsyevj(handle,
jobz,
uplo,
last_dim,
reinterpret_cast<float *>(input_data),
lda,
reinterpret_cast<float *>(value_data),
reinterpret_cast<float *>(work_ptr),
workspace_size,
&info_ptr[i],
syevj_params));
} else {
Evd(handle,
jobz,
uplo,
last_dim,
input_data,
lda,
value_data,
work_ptr,
workspace_size,
&info_ptr[i]);
}
}
CheckEighResult(dev_ctx, batch_size, info_ptr);
if (use_cusolver_syevj_batched || use_cusolver_syevj) {
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnDestroySyevjInfo(syevj_params));
}
if (has_vectors) {
PADDLE_ENFORCE_NOT_NULL(eigen_vectors,
common::errors::InvalidArgument(
"When has_vectors is true,"
"the eigenvectors needs to be calculated,"
"so the eigenvectors must be provided."));
input_trans = TransposeLast2Dim<T>(dev_ctx, input_trans);
eigen_vectors->ShareDataWith(input_trans);
}
}
using ValueType = phi::dtype::Real<T>;
inline void EvdBuffer(cusolverDnHandle_t handle,
cusolverEigMode_t jobz,
cublasFillMode_t uplo,
int n,
const T *A,
int lda,
const ValueType *W,
int *lwork) const;
inline void Evd(cusolverDnHandle_t handle,
cusolverEigMode_t jobz,
cublasFillMode_t uplo,
int n,
T *A,
int lda,
ValueType *W,
T *work,
int lwork,
int *devInfo) const;
};
using phi::dtype::complex;
#define FUNC_WITH_TYPES(m) \
m(float, Ssy, float) m(double, Dsy, double) m( \
complex<float>, Che, cuComplex) m(complex<double>, Zhe, cuDoubleComplex)
#define EVDBUFFER_INSTANCE(T, C, CastType) \
template <> \
inline void MatrixEighFunctor<GPUContext, T>::EvdBuffer( \
cusolverDnHandle_t handle, \
cusolverEigMode_t jobz, \
cublasFillMode_t uplo, \
int n, \
const T *A, \
int lda, \
const ValueType *W, \
int *lwork) const { \
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDn##C##evd_bufferSize( \
handle, \
jobz, \
uplo, \
n, \
reinterpret_cast<const CastType *>(A), \
lda, \
W, \
lwork)); \
}
FUNC_WITH_TYPES(EVDBUFFER_INSTANCE);
#define EVD_INSTANCE(T, C, CastType) \
template <> \
inline void MatrixEighFunctor<GPUContext, T>::Evd(cusolverDnHandle_t handle, \
cusolverEigMode_t jobz, \
cublasFillMode_t uplo, \
int n, \
T *A, \
int lda, \
ValueType *W, \
T *work, \
int lwork, \
int *devInfo) const { \
PADDLE_ENFORCE_GPU_SUCCESS( \
dynload::cusolverDn##C##evd(handle, \
jobz, \
uplo, \
n, \
reinterpret_cast<CastType *>(A), \
lda, \
W, \
reinterpret_cast<CastType *>(work), \
lwork, \
devInfo)); \
}
FUNC_WITH_TYPES(EVD_INSTANCE);
#undef FUNC_WITH_TYPES
#undef EVDBUFFER_INSTANCE
#undef EVD_INSTANCE
#endif // PADDLE_WITH_CUDA
} // namespace funcs
} // namespace phi