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

940 lines
34 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.
#ifndef PADDLE_WITH_HIP
// HIP not support cusolver
#include "paddle/phi/kernels/matrix_rank_tol_kernel.h"
#include <algorithm>
#include <vector>
#include "paddle/phi/backends/dynload/cusolver.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/common/type_traits.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/abs_kernel.h"
#include "paddle/phi/kernels/compare_kernel.h"
#include "paddle/phi/kernels/complex_kernel.h"
#include "paddle/phi/kernels/elementwise_multiply_kernel.h"
#include "paddle/phi/kernels/full_kernel.h"
#include "paddle/phi/kernels/funcs/broadcast_function.h"
#include "paddle/phi/kernels/funcs/compare_functors.h"
#include "paddle/phi/kernels/impl/matrix_rank_kernel_impl.h"
#include "paddle/phi/kernels/reduce_max_kernel.h"
#include "paddle/phi/kernels/reduce_sum_kernel.h"
#include "paddle/phi/kernels/scale_kernel.h"
#include "paddle/phi/kernels/where_kernel.h"
namespace phi {
template <typename T>
static void GesvdjBatched(const GPUContext& dev_ctx,
int batchSize,
int m,
int n,
int k,
T* A,
T* U,
T* V,
phi::dtype::Real<T>* S,
int* info,
int thin_UV = 1);
template <typename T>
void SyevjBatched(const GPUContext& dev_ctx,
int batchSize,
int n,
T* A,
phi::dtype::Real<T>* W,
int* info);
template <>
void GesvdjBatched<float>(const GPUContext& dev_ctx,
int batchSize,
int m,
int n,
int k,
float* A,
float* U,
float* V,
float* S,
int* info,
int thin_UV) {
// do not compute singular vectors
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
gesvdjInfo_t gesvdj_params = NULL;
int lda = m;
int ldu = m;
int ldt = n;
int lwork = 0;
auto handle = dev_ctx.cusolver_dn_handle();
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCreateGesvdjInfo(&gesvdj_params));
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnSgesvdj_bufferSize(handle,
jobz,
thin_UV,
m,
n,
A,
lda,
S,
U,
ldu,
V,
ldt,
&lwork,
gesvdj_params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(float),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
float* workspace_ptr = reinterpret_cast<float*>(workspace->ptr());
int64_t stride_A = static_cast<int64_t>(lda) * n;
int64_t stride_U = static_cast<int64_t>(ldu) * (thin_UV ? k : m);
int64_t stride_V = static_cast<int64_t>(ldt) * (thin_UV ? k : n);
for (int i = 0; i < batchSize; i++) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnSgesvdj(handle,
jobz,
thin_UV,
m,
n,
A + stride_A * i,
lda,
S + k * i,
U + stride_U * i,
ldu,
V + stride_V * i,
ldt,
workspace_ptr,
lwork,
info,
gesvdj_params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver SVD is not zero. [%d]", i, error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnDestroyGesvdjInfo(gesvdj_params));
}
template <>
void GesvdjBatched<double>(const GPUContext& dev_ctx,
int batchSize,
int m,
int n,
int k,
double* A,
double* U,
double* V,
double* S,
int* info,
int thin_UV) {
// do not compute singular vectors
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
gesvdjInfo_t gesvdj_params = NULL;
int lda = m;
int ldu = m;
int ldt = n;
int lwork = 0;
auto handle = dev_ctx.cusolver_dn_handle();
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCreateGesvdjInfo(&gesvdj_params));
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnDgesvdj_bufferSize(handle,
jobz,
thin_UV,
m,
n,
A,
lda,
S,
U,
ldu,
V,
ldt,
&lwork,
gesvdj_params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(double),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
double* workspace_ptr = reinterpret_cast<double*>(workspace->ptr());
int64_t stride_A = static_cast<int64_t>(lda) * n;
int64_t stride_U = static_cast<int64_t>(ldu) * (thin_UV ? k : m);
int64_t stride_V = static_cast<int64_t>(ldt) * (thin_UV ? k : n);
for (int i = 0; i < batchSize; ++i) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDgesvdj(handle,
jobz,
thin_UV,
m,
n,
A + stride_A * i,
lda,
S + k * i,
U + stride_U * i,
ldu,
V + stride_V * i,
ldt,
workspace_ptr,
lwork,
info,
gesvdj_params));
// check the error info
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver SVD is not zero. [%d]", i, error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnDestroyGesvdjInfo(gesvdj_params));
}
template <>
void GesvdjBatched<phi::complex64>(const GPUContext& dev_ctx,
int batchSize,
int m,
int n,
int k,
phi::complex64* A,
phi::complex64* U,
phi::complex64* V,
float* S,
int* info,
int thin_UV) {
// do not compute singular vectors
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
gesvdjInfo_t gesvdj_params = NULL;
int lda = m;
int ldu = m;
int ldt = n;
int lwork = 0;
auto handle = dev_ctx.cusolver_dn_handle();
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCreateGesvdjInfo(&gesvdj_params));
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCgesvdj_bufferSize(handle,
jobz,
thin_UV,
m,
n,
reinterpret_cast<cuComplex*>(A),
lda,
S,
reinterpret_cast<cuComplex*>(U),
ldu,
reinterpret_cast<cuComplex*>(V),
ldt,
&lwork,
gesvdj_params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(cuComplex),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
cuComplex* workspace_ptr = reinterpret_cast<cuComplex*>(workspace->ptr());
int64_t stride_A = static_cast<int64_t>(lda) * n;
int64_t stride_U = static_cast<int64_t>(ldu) * (thin_UV ? k : m);
int64_t stride_V = static_cast<int64_t>(ldt) * (thin_UV ? k : n);
for (int i = 0; i < batchSize; ++i) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCgesvdj(
handle,
jobz,
thin_UV,
m,
n,
reinterpret_cast<cuComplex*>(A + stride_A * i),
lda,
S + k * i,
reinterpret_cast<cuComplex*>(U + stride_U * i),
ldu,
reinterpret_cast<cuComplex*>(V + stride_V * i),
ldt,
workspace_ptr,
lwork,
info,
gesvdj_params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver SVD is not zero. [%d]", i, error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnDestroyGesvdjInfo(gesvdj_params));
}
template <>
void GesvdjBatched<phi::complex128>(const GPUContext& dev_ctx,
int batchSize,
int m,
int n,
int k,
phi::complex128* A,
phi::complex128* U,
phi::complex128* V,
double* S,
int* info,
int thin_UV) {
// do not compute singular vectors
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
gesvdjInfo_t gesvdj_params = NULL;
int lda = m;
int ldu = m;
int ldt = n;
int lwork = 0;
auto handle = dev_ctx.cusolver_dn_handle();
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCreateGesvdjInfo(&gesvdj_params));
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnZgesvdj_bufferSize(
handle,
jobz,
thin_UV,
m,
n,
reinterpret_cast<cuDoubleComplex*>(A),
lda,
S,
reinterpret_cast<cuDoubleComplex*>(U),
ldu,
reinterpret_cast<cuDoubleComplex*>(V),
ldt,
&lwork,
gesvdj_params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(cuDoubleComplex),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
cuDoubleComplex* workspace_ptr =
reinterpret_cast<cuDoubleComplex*>(workspace->ptr());
int64_t stride_A = static_cast<int64_t>(lda) * n;
int64_t stride_U = static_cast<int64_t>(ldu) * (thin_UV ? k : m);
int64_t stride_V = static_cast<int64_t>(ldt) * (thin_UV ? k : n);
for (int i = 0; i < batchSize; ++i) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnZgesvdj(
handle,
jobz,
thin_UV,
m,
n,
reinterpret_cast<cuDoubleComplex*>(A + stride_A * i),
lda,
S + k * i,
reinterpret_cast<cuDoubleComplex*>(U + stride_U * i),
ldu,
reinterpret_cast<cuDoubleComplex*>(V + stride_V * i),
ldt,
workspace_ptr,
lwork,
info,
gesvdj_params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver SVD is not zero. [%d]", i, error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnDestroyGesvdjInfo(gesvdj_params));
}
template <>
void SyevjBatched<float>(const GPUContext& dev_ctx,
int batchSize,
int n,
float* A,
float* W,
int* info) {
auto handle = dev_ctx.cusolver_dn_handle();
// Compute eigenvalues only
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
// matrix is saved as column-major in cusolver.
// numpy and torch use lower triangle to compute eigenvalues, so here use
// upper triangle
cublasFillMode_t uplo = CUBLAS_FILL_MODE_UPPER;
int lda = n;
int64_t stride_A = static_cast<int64_t>(lda) * n;
int lwork = 0;
syevjInfo_t params = NULL;
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCreateSyevjInfo(&params));
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnSsyevj_bufferSize(
handle, jobz, uplo, n, A, lda, W, &lwork, params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(float),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
float* workspace_ptr = reinterpret_cast<float*>(workspace->ptr());
for (int i = 0; i < batchSize; i++) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnSsyevj(handle,
jobz,
uplo,
n,
A + stride_A * i,
lda,
W + n * i,
workspace_ptr,
lwork,
info,
params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver eigenvalues is not zero. [%d]",
i,
error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDestroySyevjInfo(params));
}
template <>
void SyevjBatched<double>(const GPUContext& dev_ctx,
int batchSize,
int n,
double* A,
double* W,
int* info) {
auto handle = dev_ctx.cusolver_dn_handle();
// Compute eigenvalues only
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
// upper triangle of A is stored
cublasFillMode_t uplo = CUBLAS_FILL_MODE_UPPER;
int lda = n;
int64_t stride_A = static_cast<int64_t>(lda) * n;
int lwork = 0;
syevjInfo_t params = NULL;
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCreateSyevjInfo(&params));
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDsyevj_bufferSize(
handle, jobz, uplo, n, A, lda, W, &lwork, params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(double),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
double* workspace_ptr = reinterpret_cast<double*>(workspace->ptr());
for (int i = 0; i < batchSize; i++) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDsyevj(handle,
jobz,
uplo,
n,
A + stride_A * i,
lda,
W + n * i,
workspace_ptr,
lwork,
info,
params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver eigenvalues is not zero. [%d]",
i,
error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDestroySyevjInfo(params));
}
template <>
void SyevjBatched<phi::complex64>(const GPUContext& dev_ctx,
int batchSize,
int n,
phi::complex64* A,
float* W,
int* info) {
auto handle = dev_ctx.cusolver_dn_handle();
// Compute eigenvalues only
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
// upper triangle of A is stored
cublasFillMode_t uplo = CUBLAS_FILL_MODE_UPPER;
int lda = n;
int64_t stride_A = static_cast<int64_t>(lda) * n;
int lwork = 0;
syevjInfo_t params = NULL;
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCreateSyevjInfo(&params));
PADDLE_ENFORCE_GPU_SUCCESS(
dynload::cusolverDnCheevj_bufferSize(handle,
jobz,
uplo,
n,
reinterpret_cast<cuComplex*>(A),
lda,
W,
&lwork,
params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(cuComplex),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
cuComplex* workspace_ptr = reinterpret_cast<cuComplex*>(workspace->ptr());
for (int i = 0; i < batchSize; i++) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCheevj(
handle,
jobz,
uplo,
n,
reinterpret_cast<cuComplex*>(A + stride_A * i),
lda,
W + n * i,
workspace_ptr,
lwork,
info,
params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver eigenvalues is not zero. [%d]",
i,
error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDestroySyevjInfo(params));
}
template <>
void SyevjBatched<phi::complex128>(const GPUContext& dev_ctx,
int batchSize,
int n,
phi::complex128* A,
double* W,
int* info) {
auto handle = dev_ctx.cusolver_dn_handle();
// Compute eigenvalues only
const cusolverEigMode_t jobz = CUSOLVER_EIG_MODE_NOVECTOR;
// upper triangle of A is stored
cublasFillMode_t uplo = CUBLAS_FILL_MODE_UPPER;
int lda = n;
int64_t stride_A = static_cast<int64_t>(lda) * n;
int lwork = 0;
syevjInfo_t params = NULL;
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnCreateSyevjInfo(&params));
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnZheevj_bufferSize(
handle,
jobz,
uplo,
n,
reinterpret_cast<cuDoubleComplex*>(A),
lda,
W,
&lwork,
params));
auto workspace = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
lwork * sizeof(cuDoubleComplex),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
cuDoubleComplex* workspace_ptr =
reinterpret_cast<cuDoubleComplex*>(workspace->ptr());
for (int i = 0; i < batchSize; i++) {
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnZheevj(
handle,
jobz,
uplo,
n,
reinterpret_cast<cuDoubleComplex*>(A + stride_A * i),
lda,
W + n * i,
workspace_ptr,
lwork,
info,
params));
int error_info;
memory_utils::Copy(CPUPlace(),
&error_info,
dev_ctx.GetPlace(),
info,
sizeof(int),
dev_ctx.stream());
PADDLE_ENFORCE_EQ(
error_info,
0,
common::errors::PreconditionNotMet(
"For batch [%d]: CUSolver eigenvalues is not zero. [%d]",
i,
error_info));
}
PADDLE_ENFORCE_GPU_SUCCESS(dynload::cusolverDnDestroySyevjInfo(params));
}
template <typename T, typename Context>
void MatrixRankTolKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& atol_tensor,
bool use_default_tol,
bool hermitian,
DenseTensor* out) {
using RealType = phi::dtype::Real<T>;
auto* x_data = x.data<T>();
dev_ctx.template Alloc<int64_t>(out);
auto dim_x = x.dims();
auto dim_out = out->dims();
int64_t rows = dim_x[dim_x.size() - 2];
int64_t cols = dim_x[dim_x.size() - 1];
// cusolverDn<t>gesvdj() don't support int64_t, so we need to check it.
int64_t numel_single_batch = rows * cols;
PADDLE_ENFORCE_LE(numel_single_batch,
(1LL << 31) - 1,
common::errors::PreconditionNotMet(
"The element size of x should be <= INT_MAX(2147483647)"
", but got %lld",
numel_single_batch));
if (x.numel() == 0) {
dev_ctx.template Alloc<int64_t>(out);
if (out && out->numel() != 0) {
Full<int64_t, Context>(dev_ctx, out->dims(), 0, out);
}
return;
}
int k = std::min(rows, cols);
auto numel = x.numel();
int batches = numel / (rows * cols);
RealType rtol_T = 0;
if (use_default_tol) {
rtol_T = std::numeric_limits<RealType>::epsilon() * std::max(rows, cols);
}
// Must Copy X once, because the gesvdj will destroy the content when exit.
DenseTensor x_tmp;
Copy(dev_ctx, x, dev_ctx.GetPlace(), false, &x_tmp);
auto info = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
sizeof(int) * batches,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
int* info_ptr = reinterpret_cast<int*>(info->ptr());
DenseTensor eigenvalue_tensor;
eigenvalue_tensor.Resize(detail::GetEigenvalueDim(dim_x, k));
auto* eigenvalue_data = dev_ctx.template Alloc<RealType>(&eigenvalue_tensor);
if (hermitian) {
SyevjBatched<T>(
dev_ctx, batches, rows, x_tmp.data<T>(), eigenvalue_data, info_ptr);
phi::AbsKernel<RealType, Context>(
dev_ctx, eigenvalue_tensor, &eigenvalue_tensor);
} else {
DenseTensor U, VH;
U.Resize(detail::GetUDDim(dim_x, k));
VH.Resize(detail::GetVHDDim(dim_x, k));
auto* u_data = dev_ctx.template Alloc<T>(&U);
auto* vh_data = dev_ctx.template Alloc<T>(&VH);
GesvdjBatched<T>(dev_ctx,
batches,
cols,
rows,
k,
x_tmp.data<T>(),
vh_data,
u_data,
eigenvalue_data,
info_ptr,
1);
}
DenseTensor max_eigenvalue_tensor;
dev_ctx.template Alloc<RealType>(&max_eigenvalue_tensor);
max_eigenvalue_tensor.Resize(detail::RemoveLastDim(eigenvalue_tensor.dims()));
phi::MaxKernel<RealType, Context>(dev_ctx,
eigenvalue_tensor,
phi::IntArray({-1}),
false,
&max_eigenvalue_tensor);
DenseTensor rtol_tensor = phi::Scale<RealType, Context>(
dev_ctx, max_eigenvalue_tensor, rtol_T, 0.0f, false);
DenseTensor atol_tensor_real;
if (atol_tensor.dtype() == DataType::COMPLEX64 ||
atol_tensor.dtype() == DataType::COMPLEX128) {
atol_tensor_real = phi::Real<T, Context>(dev_ctx, atol_tensor);
} else {
atol_tensor_real = atol_tensor;
}
DenseTensor tol_tensor;
tol_tensor.Resize(dim_out);
dev_ctx.template Alloc<RealType>(&tol_tensor);
funcs::ElementwiseCompute<GreaterElementFunctor<RealType>, RealType>(
dev_ctx,
atol_tensor_real,
rtol_tensor,
GreaterElementFunctor<RealType>(),
&tol_tensor);
tol_tensor.Resize(detail::NewAxisDim(tol_tensor.dims(), 1));
DenseTensor compare_result;
compare_result.Resize(detail::NewAxisDim(dim_out, k));
dev_ctx.template Alloc<int64_t>(&compare_result);
funcs::ElementwiseCompute<funcs::GreaterThanFunctor<RealType, int64_t>,
RealType,
int64_t>(
dev_ctx,
eigenvalue_tensor,
tol_tensor,
funcs::GreaterThanFunctor<RealType, int64_t>(),
&compare_result);
phi::SumKernel<int64_t>(dev_ctx,
compare_result,
std::vector<int64_t>{-1},
compare_result.dtype(),
false,
out);
}
template <typename T, typename Context>
void MatrixRankAtolRtolKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& atol,
const optional<DenseTensor>& rtol,
bool hermitian,
DenseTensor* out) {
using RealType = phi::dtype::Real<T>;
auto* x_data = x.data<T>();
auto dim_x = x.dims();
auto dim_out = out->dims();
int rows = dim_x[dim_x.size() - 2];
int cols = dim_x[dim_x.size() - 1];
dev_ctx.template Alloc<int64_t>(out);
if (x.numel() == 0) {
out->Resize(dim_out);
if (out && out->numel() != 0) {
Full<int64_t, Context>(dev_ctx, out->dims(), 0, out);
}
return;
}
int k = std::min(rows, cols);
auto numel = x.numel();
int batches = numel / (rows * cols);
// Must Copy X once, because the gesvdj will destroy the content when exit.
DenseTensor x_tmp;
Copy(dev_ctx, x, dev_ctx.GetPlace(), false, &x_tmp);
auto info = phi::memory_utils::Alloc(
dev_ctx.GetPlace(),
sizeof(int) * batches,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx.stream())));
int* info_ptr = reinterpret_cast<int*>(info->ptr());
DenseTensor eigenvalue_tensor;
eigenvalue_tensor.Resize(detail::GetEigenvalueDim(dim_x, k));
auto* eigenvalue_data = dev_ctx.template Alloc<RealType>(&eigenvalue_tensor);
if (hermitian) {
SyevjBatched<T>(
dev_ctx, batches, rows, x_tmp.data<T>(), eigenvalue_data, info_ptr);
phi::AbsKernel<RealType, Context>(
dev_ctx, eigenvalue_tensor, &eigenvalue_tensor);
} else {
DenseTensor U, VH;
U.Resize(detail::GetUDDim(dim_x, k));
VH.Resize(detail::GetVHDDim(dim_x, k));
auto* u_data = dev_ctx.template Alloc<T>(&U);
auto* vh_data = dev_ctx.template Alloc<T>(&VH);
GesvdjBatched<T>(dev_ctx,
batches,
cols,
rows,
k,
x_tmp.data<T>(),
vh_data,
u_data,
eigenvalue_data,
info_ptr,
1);
}
DenseTensor max_eigenvalue_tensor;
dev_ctx.template Alloc<RealType>(&max_eigenvalue_tensor);
max_eigenvalue_tensor.Resize(detail::RemoveLastDim(eigenvalue_tensor.dims()));
phi::MaxKernel<RealType, Context>(dev_ctx,
eigenvalue_tensor,
phi::IntArray({-1}),
false,
&max_eigenvalue_tensor);
DenseTensor atol_tensor;
if (atol.dtype() == DataType::COMPLEX64 ||
atol.dtype() == DataType::COMPLEX128) {
atol_tensor = phi::Real<T, Context>(dev_ctx, atol);
} else {
atol_tensor = atol;
}
DenseTensor tol_tensor;
tol_tensor.Resize(dim_out);
dev_ctx.template Alloc<RealType>(&tol_tensor);
if (rtol) {
DenseTensor rtol_tensor = *rtol;
if (rtol_tensor.dtype() == DataType::COMPLEX64 ||
rtol_tensor.dtype() == DataType::COMPLEX128) {
rtol_tensor = phi::Real<T, Context>(dev_ctx, *rtol);
}
DenseTensor tmp_rtol_tensor;
tmp_rtol_tensor =
phi::Multiply<RealType>(dev_ctx, rtol_tensor, max_eigenvalue_tensor);
funcs::ElementwiseCompute<GreaterElementFunctor<RealType>, RealType>(
dev_ctx,
atol_tensor,
tmp_rtol_tensor,
GreaterElementFunctor<RealType>(),
&tol_tensor);
} else {
// when `rtol` is specified to be None in py api
// use rtol=eps*max(m, n) only if `atol` is passed with value 0.0, else use
// rtol=0.0
RealType rtol_T =
std::numeric_limits<RealType>::epsilon() * std::max(rows, cols);
DenseTensor default_rtol_tensor = phi::Scale<RealType, Context>(
dev_ctx, max_eigenvalue_tensor, rtol_T, 0.0f, false);
DenseTensor zero_tensor;
zero_tensor = FullLike<RealType, Context>(
dev_ctx, default_rtol_tensor, static_cast<RealType>(0.0));
DenseTensor atol_compare_result;
atol_compare_result.Resize(default_rtol_tensor.dims());
phi::EqualKernel<RealType, Context>(
dev_ctx, atol_tensor, zero_tensor, &atol_compare_result);
DenseTensor selected_rtol_tensor;
selected_rtol_tensor.Resize(default_rtol_tensor.dims());
phi::WhereKernel<RealType, Context>(dev_ctx,
atol_compare_result,
default_rtol_tensor,
zero_tensor,
&selected_rtol_tensor);
funcs::ElementwiseCompute<GreaterElementFunctor<RealType>, RealType>(
dev_ctx,
atol_tensor,
selected_rtol_tensor,
GreaterElementFunctor<RealType>(),
&tol_tensor);
}
tol_tensor.Resize(detail::NewAxisDim(tol_tensor.dims(), 1));
DenseTensor compare_result;
compare_result.Resize(detail::NewAxisDim(dim_out, k));
dev_ctx.template Alloc<int64_t>(&compare_result);
funcs::ElementwiseCompute<funcs::GreaterThanFunctor<RealType, int64_t>,
RealType,
int64_t>(
dev_ctx,
eigenvalue_tensor,
tol_tensor,
funcs::GreaterThanFunctor<RealType, int64_t>(),
&compare_result);
phi::SumKernel<int64_t>(dev_ctx,
compare_result,
std::vector<int64_t>{-1},
compare_result.dtype(),
false,
out);
}
} // namespace phi
PD_REGISTER_KERNEL(matrix_rank_tol, // cuda_only
GPU,
ALL_LAYOUT,
phi::MatrixRankTolKernel,
float,
double,
phi::complex64,
phi::complex128) {
kernel->OutputAt(0).SetDataType(phi::DataType::INT64);
}
PD_REGISTER_KERNEL(matrix_rank_atol_rtol, // cuda_only
GPU,
ALL_LAYOUT,
phi::MatrixRankAtolRtolKernel,
float,
double,
phi::complex64,
phi::complex128) {
kernel->OutputAt(0).SetDataType(phi::DataType::INT64);
}
#endif // not PADDLE_WITH_HIP