与 MATLAB 相比,使用 cuSolver 时 SVD 非常慢
SVD very slow when using cuSolver as compared to MATLAB
我正在尝试使用 cuSOLVER
中的 gesvd
函数,我发现它比 MATLAB 中的 svd
函数慢得多,对于使用 [=18= 的两种情况] 数组或 gpuArray
。
C++ 代码[使用cuSolver
]:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <cuda_runtime.h>
#include <cusolverDn.h>
// Macro for timing kernel runs
#define START_METER {\
cudaEvent_t start, stop;\
float elapsedTime;\
cudaEventCreate(&start);\
cudaEventRecord(start, 0);
#define STOP_METER cudaEventCreate(&stop);\
cudaEventRecord(stop, 0);\
cudaEventSynchronize(stop);\
cudaEventElapsedTime(&elapsedTime, start, stop);\
printf("Elapsed time : %f ms\n", elapsedTime);\
}
void cusolverSVD_Test()
{
const int m = 64;
const int rows = m;
const int cols = m;
/* | 3.5 0.5 0 |
* A = | 0.5 3.5 0 |
* | 0 0 2 |
*
*/
double A[rows*m];
for (int i = 0; i < cols; i++)
{
for (int j = 0; j < rows; j++)
{
A[i*rows + j] = (double)rand() / RAND_MAX;
if (i == j){
A[i*rows + j] += 1;
}
}
}
cusolverDnHandle_t handle;
cusolverDnCreate(&handle);
int lwork;
cusolverDnDgesvd_bufferSize(
handle,
rows,
cols,
&lwork);
double *d_A;
cudaMalloc(&d_A, sizeof(double)*rows*cols);
cudaMemcpy(d_A, A, sizeof(double)*rows*cols, cudaMemcpyHostToDevice);
double *d_S;
cudaMalloc(&d_S, sizeof(double)*rows);
double *d_U;
cudaMalloc(&d_U, sizeof(double)*rows*rows);
double *d_VT;
cudaMalloc(&d_VT, sizeof(double)*rows*rows);
double *d_work;
cudaMalloc(&d_work, sizeof(double)*lwork);
double *d_rwork;
cudaMalloc(&d_rwork, sizeof(double)*(rows - 1));
int *devInfo;
cudaMalloc(&devInfo, sizeof(int));
for (int t = 0; t < 10; t++)
{
signed char jobu = 'A';
signed char jobvt = 'A';
START_METER
cusolverDnDgesvd(
handle,
jobu,
jobvt,
rows,
cols,
d_A,
rows,
d_S,
d_U,
rows,
d_VT,
rows,
d_work,
lwork,
d_rwork,
devInfo);
STOP_METER
}
cudaFree(d_A);
cudaFree(d_rwork);
cudaFree(d_S);
cudaFree(d_U);
cudaFree(d_VT);
cudaFree(d_work);
}
int main()
{
cusolverSVD_Test();
}
输出:
Elapsed time : 63.318016 ms
Elapsed time : 66.745316 ms
Elapsed time : 65.966530 ms
Elapsed time : 65.999939 ms
Elapsed time : 64.821053 ms
Elapsed time : 65.184547 ms
Elapsed time : 65.722916 ms
Elapsed time : 60.618786 ms
Elapsed time : 54.937569 ms
Elapsed time : 53.751263 ms
Press any key to continue . . .
**使用svd
函数的Matlab代码*:
%% SVD on gpu
A = rand(64, 64) + eye(64);
tic
[~, ~, ~] = svd(A);
t = toc;
fprintf('CPU time: %f ms\n', t*1000);
d_A = gpuArray(A);
tic
[~, ~, ~] = svd(d_A);
t = toc;
fprintf('GPU time: %f ms\n', t*1000);
%% Output
% >> CPU time: 0.947754 ms
% >> GPU time: 2.168100 ms
Matlab 是否使用了一些更快的算法?还是我只是犯了一些错误?我真的需要一个好的 implementation/algorithm 用于 SVD,我可以在 CUDA
.
中使用
更新:使用 1000 x 1000 矩阵时的执行时间
C++:
3655 ms (Double Precision)
2970 ms (Single Precision)
Matlab:
CPU time: 280.641123 ms
GPU time: 646.033498 ms
SVD 算法不能很好地并行化是一个已知问题。您会发现您需要非常大的数组才能看到双精度的好处。对于 GPU 的单精度,您可能会获得更好的结果。如果您只请求一个输出,您也会得到更好的结果,因为单独计算奇异值使用的算法要快得多。
这也高度依赖于您的 GPU 质量。如果您使用的是 GeForce GTX 等显卡,对于像 SVD 这样的算法,GPU 在双精度方面确实看不到太多好处。
从根本上说,GPU 核心的性能远低于现代 CPU 核心,它们通过非常广泛的并行性弥补了这一点。 SVD 算法过于依赖串行因式分解迭代。或许您可以通过重新思考代数来解决您的问题,这样您就不需要每次都计算完全因式分解。
我正在尝试使用 cuSOLVER
中的 gesvd
函数,我发现它比 MATLAB 中的 svd
函数慢得多,对于使用 [=18= 的两种情况] 数组或 gpuArray
。
C++ 代码[使用cuSolver
]:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <cuda_runtime.h>
#include <cusolverDn.h>
// Macro for timing kernel runs
#define START_METER {\
cudaEvent_t start, stop;\
float elapsedTime;\
cudaEventCreate(&start);\
cudaEventRecord(start, 0);
#define STOP_METER cudaEventCreate(&stop);\
cudaEventRecord(stop, 0);\
cudaEventSynchronize(stop);\
cudaEventElapsedTime(&elapsedTime, start, stop);\
printf("Elapsed time : %f ms\n", elapsedTime);\
}
void cusolverSVD_Test()
{
const int m = 64;
const int rows = m;
const int cols = m;
/* | 3.5 0.5 0 |
* A = | 0.5 3.5 0 |
* | 0 0 2 |
*
*/
double A[rows*m];
for (int i = 0; i < cols; i++)
{
for (int j = 0; j < rows; j++)
{
A[i*rows + j] = (double)rand() / RAND_MAX;
if (i == j){
A[i*rows + j] += 1;
}
}
}
cusolverDnHandle_t handle;
cusolverDnCreate(&handle);
int lwork;
cusolverDnDgesvd_bufferSize(
handle,
rows,
cols,
&lwork);
double *d_A;
cudaMalloc(&d_A, sizeof(double)*rows*cols);
cudaMemcpy(d_A, A, sizeof(double)*rows*cols, cudaMemcpyHostToDevice);
double *d_S;
cudaMalloc(&d_S, sizeof(double)*rows);
double *d_U;
cudaMalloc(&d_U, sizeof(double)*rows*rows);
double *d_VT;
cudaMalloc(&d_VT, sizeof(double)*rows*rows);
double *d_work;
cudaMalloc(&d_work, sizeof(double)*lwork);
double *d_rwork;
cudaMalloc(&d_rwork, sizeof(double)*(rows - 1));
int *devInfo;
cudaMalloc(&devInfo, sizeof(int));
for (int t = 0; t < 10; t++)
{
signed char jobu = 'A';
signed char jobvt = 'A';
START_METER
cusolverDnDgesvd(
handle,
jobu,
jobvt,
rows,
cols,
d_A,
rows,
d_S,
d_U,
rows,
d_VT,
rows,
d_work,
lwork,
d_rwork,
devInfo);
STOP_METER
}
cudaFree(d_A);
cudaFree(d_rwork);
cudaFree(d_S);
cudaFree(d_U);
cudaFree(d_VT);
cudaFree(d_work);
}
int main()
{
cusolverSVD_Test();
}
输出:
Elapsed time : 63.318016 ms
Elapsed time : 66.745316 ms
Elapsed time : 65.966530 ms
Elapsed time : 65.999939 ms
Elapsed time : 64.821053 ms
Elapsed time : 65.184547 ms
Elapsed time : 65.722916 ms
Elapsed time : 60.618786 ms
Elapsed time : 54.937569 ms
Elapsed time : 53.751263 ms
Press any key to continue . . .
**使用svd
函数的Matlab代码*:
%% SVD on gpu
A = rand(64, 64) + eye(64);
tic
[~, ~, ~] = svd(A);
t = toc;
fprintf('CPU time: %f ms\n', t*1000);
d_A = gpuArray(A);
tic
[~, ~, ~] = svd(d_A);
t = toc;
fprintf('GPU time: %f ms\n', t*1000);
%% Output
% >> CPU time: 0.947754 ms
% >> GPU time: 2.168100 ms
Matlab 是否使用了一些更快的算法?还是我只是犯了一些错误?我真的需要一个好的 implementation/algorithm 用于 SVD,我可以在 CUDA
.
更新:使用 1000 x 1000 矩阵时的执行时间
C++:
3655 ms (Double Precision)
2970 ms (Single Precision)
Matlab:
CPU time: 280.641123 ms
GPU time: 646.033498 ms
SVD 算法不能很好地并行化是一个已知问题。您会发现您需要非常大的数组才能看到双精度的好处。对于 GPU 的单精度,您可能会获得更好的结果。如果您只请求一个输出,您也会得到更好的结果,因为单独计算奇异值使用的算法要快得多。
这也高度依赖于您的 GPU 质量。如果您使用的是 GeForce GTX 等显卡,对于像 SVD 这样的算法,GPU 在双精度方面确实看不到太多好处。
从根本上说,GPU 核心的性能远低于现代 CPU 核心,它们通过非常广泛的并行性弥补了这一点。 SVD 算法过于依赖串行因式分解迭代。或许您可以通过重新思考代数来解决您的问题,这样您就不需要每次都计算完全因式分解。