CPU和GPU的SVD速度

时间:2014-11-07 08:38:37

标签: matlab matrix cuda svd arrayfire

我正在svd中对Matlab R2014a进行测试,似乎没有CPUGPU加速。我使用GTX 460卡和Core 2 duo E8500

这是我的代码:

%test SVD
n=10000;
%host
Mh= rand(n,1000);
tic
%[Uh,Sh,Vh]= svd(Mh);
svd(Mh);
toc
%device
Md = gpuArray.rand(n,1000);
tic
%[Ud,Sd,Vd]= svd(Md);
svd(Md);
toc

此外,运行时间与运行不同,但CPUGPU版本大致相同。为什么没有加速?

以下是一些测试

for i=1:10
    clear;
    m= 10000;
    n= 100;
    %host
    Mh= rand(m,n);
    tic
    [Uh,Sh,Vh]= svd(Mh);
    toc
    %device
    Md = gpuArray.rand(m,n);
    tic
    [Ud,Sd,Vd]= svd(Md);
    toc
end

>> test_gpu_svd
Elapsed time is 43.124130 seconds.
Elapsed time is 43.842277 seconds.
Elapsed time is 42.993283 seconds.
Elapsed time is 44.293410 seconds.
Elapsed time is 42.924541 seconds.
Elapsed time is 43.730343 seconds.
Elapsed time is 43.125938 seconds.
Elapsed time is 43.645095 seconds.
Elapsed time is 43.492129 seconds.
Elapsed time is 43.459277 seconds.
Elapsed time is 43.327012 seconds.
Elapsed time is 44.040959 seconds.
Elapsed time is 43.242291 seconds.
Elapsed time is 43.390881 seconds.
Elapsed time is 43.275379 seconds.
Elapsed time is 43.408705 seconds.
Elapsed time is 43.320387 seconds.
Elapsed time is 44.232156 seconds.
Elapsed time is 42.984002 seconds.
Elapsed time is 43.702430 seconds.


for i=1:10
    clear;
    m= 10000;
    n= 100;
    %host
    Mh= rand(m,n,'single');
    tic
    [Uh,Sh,Vh]= svd(Mh);
    toc
    %device
    Md = gpuArray.rand(m,n,'single');
    tic
    [Ud,Sd,Vd]= svd(Md);
    toc
end

>> test_gpu_svd
Elapsed time is 21.140301 seconds.
Elapsed time is 21.334361 seconds.
Elapsed time is 21.275991 seconds.
Elapsed time is 21.582602 seconds.
Elapsed time is 21.093408 seconds.
Elapsed time is 21.305413 seconds.
Elapsed time is 21.482931 seconds.
Elapsed time is 21.327842 seconds.
Elapsed time is 21.120969 seconds.
Elapsed time is 21.701752 seconds.
Elapsed time is 21.117268 seconds.
Elapsed time is 21.384318 seconds.
Elapsed time is 21.359225 seconds.
Elapsed time is 21.911570 seconds.
Elapsed time is 21.086259 seconds.
Elapsed time is 21.263040 seconds.
Elapsed time is 21.472175 seconds.
Elapsed time is 21.561370 seconds.
Elapsed time is 21.330314 seconds.
Elapsed time is 21.546260 seconds.

4 个答案:

答案 0 :(得分:9)

一般来说,SVD是一种难以兼容的例行程序。您可以使用高端特斯拉卡查看 here ,加速速度不是很令人印象深刻。

您有一张GTX460卡 - Fermi architecture 。该卡针对游戏(单精度计算)而非HPC(双精度计算)进行了优化。 单精度/双精度吞吐率为12.因此该卡具有873 GFLOPS SP / 72 GFLOPS DP 。检查 here

因此,如果Md数组使用双精度元素,那么对它的计算将会相当慢。此外,在调用CPU例程时,很有可能会利用所有CPU内核,从而降低在GPU上运行例程的可能增益。另外,在GPU运行中,您需要花时间将缓冲区传输到设备

根据Divakar的建议,您可以使用Md = single(Md)将数组转换为单精度并再次运行基准测试。您可以尝试使用更大的数据集大小来查看是否有更改。我不希望你的GPU上有这个例程。

更新1:

发布结果后,我看到DP / SP时间比为2.在CPU端,这是正常的,因为在SSE寄存器中可以减少2倍double个值。然而,GPU侧只有2的比率意味着gpu代码没有充分利用SM内核 - 因为理论比率为12.换句话说,我会期望更好的SP性能优化代码,与DP相比。似乎情况并非如此。

答案 1 :(得分:5)

正如VAndrei所说,SVD是一种难以并行化的算法。

您的主要问题是矩阵的大小。随着基质尺寸的增加,SVD的性能迅速下降。因此,您的主要目标应该是减小矩阵的大小。 这可以使用高斯正规方程来完成(这基本上是在最小二乘意义上减少超定线性系统)。

这可以通过简单地将转置乘以矩阵来完成:

MhReduced = Mh' * Mh;

这会将矩阵缩小为cols * cols的大小(如果cols是Mh的列数)。然后你只需拨打[U,S,V] = svd(MhReduced);

注意:使用此方法可能会产生符号相反的奇异向量(如果您正在比较这些方法,则非常重要)。

如果你的matix条件良好,这应该没有问题。然而,在病态矩阵的情况下,该方法可能无法产生可用的结果,而由于SVD的鲁棒性,直接应用SVD仍然可以产生可用的结果。

这应该会极大地提高你的表现,至少在矩阵足够大的情况下。另一个优点是您可以使用更大的矩阵。您可能根本不必使用GPU(因为任何一个矩阵都太大,以至于复制到GPU的成本太高,或者在缩小后矩阵太小以至于GPU的加速赢了。足够大了。

另请注意,如果使用返回值,则会丢失大量性能。如果您只对SVD计算的性能感兴趣,请不要使用任何返回值。如果您只对"解决方案向量"感兴趣,只需获取V(并访问最后一列):[~,~, V] = svd(Mh);

编辑:

我查看了您的示例代码,但我不确定它是什么,您正在计算。我也意识到我很难理解我对A'*A的所作所为,所以我将详细解释。

给定具有A*x=b的线性系统,A表示系数矩阵 m行和n cols,x解向量和b常量向量(均为m行),解决方案可按如下方式计算:

  • 如果A是正方形(m=n):x = A^-1 * b
  • 如果A不是方形(m!=n, m > n):

    A * x = b

    A' * A * x = A' * b

    x =(A' * A)^ - 1 * A' * b

A" = (A'*A)^-1 * A'通常称为伪逆。然而,该计算确实会对矩阵的条件数产生负面影响。该问题的解决方案是使用奇异值分解(SVD)。 如果USV = svd(A)表示SVD的结果,则伪逆由VS"U'给出,S"通过取S的非零元素的倒数形成。 所以A" = VS"U'

x = A"*b

然而,由于SVD相当昂贵,特别是对于大型矩阵。如果矩阵A条件良好且不一定需要非常精确的结果(我们说的是1e-13或1e-14),可以使用通过(A'*A)^-1 * A计算伪逆的更快的方法。

如果您的案例实际上是A*x=0,只需使用SVD并从V读取最后一列向量,这就是解决方案。

如果您使用SVD不是为了解决线性系统而是为了U和S的结果(如您的示例所示),我不确定我发布的内容会对您有所帮助。

来源: 123

以下是一些示例代码供您测试。使用大型矩阵对其进行测试,您将看到使用(A'*A)^-1 * A'比替代方案快得多。

clear all

nbRows = 30000;
nbCols = 100;
% Matrix A
A = rand(nbRows,nbCols);

% Vector b
b = rand(nbRows,1);

% A*x=b

% Solve for x, using SVD
% [U,S,V]=svd(A,0);
% x= V*((U'*b)./diag(S))
tic
[U1,S1,V1]=svd(A,0);
x1= V1*((U1'*b)./diag(S1));
toc

tic
[U1,S1,V1]=svd(A,0);
x2 = V1*inv(S1)*U1'*b;
toc

% Solve for x, using manual pseudo-inverse
% A*x=b
% A'*A*x = A'*b
% x = (A'*A)^-1 * A'*b
tic
x3 = inv(A'*A) * A'*b;
toc

% Solve for x, let Matlab decide how (most likely SVD)
tic
x4 = A\b;
toc

答案 2 :(得分:1)

问题

首先,我使用以下代码在Matlab2016b中复制了您的问题:

clear all
close all
clc

Nrows = 2500;
Ncols = 2500;

NumTests = 10;

h_A = rand(Nrows, Ncols);
d_A = gpuArray.rand(Nrows, Ncols);

timingCPU = 0;
timingGPU = 0;

for k = 1 : NumTests
    % --- Host
    tic
    [h_U, h_S, h_V] = svd(h_A);
%     h_S = svd(h_A);
    timingCPU = timingCPU + toc;

    % --- Device
    tic
    [d_U, d_S, d_V] = svd(d_A);
%     d_S = svd(d_A);
    timingGPU = timingGPU + toc;
end

fprintf('Timing CPU = %f; Timing GPU = %f\n', timingCPU / NumTests, timingGPU / NumTests);

通过上述代码,可以仅计算奇异值或计算包括奇异向量的完整SVD。也可以比较CPU和GPU版本的SVD代码的不同行为。

时间表见下表(s; Intel Core i7-6700K CPU @ 4.00GHz16288 MBMax threads(8)GTX 960的时间安排:

              Sing. values only | Full SVD         | Sing. val. only | Full
                                |                  |                 |
Matrix size   CPU      GPU      | CPU       GPU    |                 |
                                |                  |                 |
 200 x  200   0.0021    0.043   |  0.0051    0.024 |   0.098         |  0.15
1000 x 1000   0.0915    0.3     |  0.169     0.458 |   0.5           |  2.3
2500 x 2500   3.35      2.13    |  4.62      3.97  |   2.9           |  23
5000 x 5000   5.2      13.1     | 26.6      73.8   |  16.1           | 161

第一个4列指的是svd例程的CPU和GPU Matlab版本之间的比较,当它用于仅计算奇异值或完整的SVD时。可以看出,GPU版本可能比GPU版本慢得多。在上面的一些答案中已经指出了动机:并行化SVD计算存在固有的困难。

使用cuSOLVER?

此时,显而易见的问题是:我们可以通过cuSOLVER获得一些加速吗?实际上,我们可以使用mexFiles来使cuSOLVER例程在Matlab下运行。不幸的是,cuSOLVER的情况更糟糕,因为它可以从上表的最后两列推断出来。这些列分别使用cusolverDnSgesvd报告Singular values calculation only with CUDAParallel implementation for multiple SVDs using CUDA处的代码的时序,仅用于奇异值计算和完整SVD计算。可以看出,cuSOLVER的{​​{1}}表现甚至比Matlab更糟,如果考虑到它处理单精度,而Matlab具有双精度。

cusolverDnCgesvd performance vs MKL进一步解释了这种行为的动机,cusolverDnSgesvd图书馆经理Joe Eaton说

  

我理解这里的困惑。我们确实提供了一个体面的加速   cuSOLVERLUQR因素分解,这就是我们想要说的   对于LDL^t也是如此。我们SVD的目的是提供密集的和   稀疏直接求解器首次作为cuSOLVER工具包的一部分;   我们必须从某个地方开始。由于不再支持CUDA,我们觉得   迫切需要将一些功能交到开发人员手中   在CULA。由于CUDA 7.0目前在CUDA主机x86上运行的次数较多,   在没有CPUs的情况下,cuSOLVER填补了需求。话虽如此,我们可以   使用MKL做得更好,但必须等待下一个SVD   发布,优先事项和时间表已经紧张。

使用其他图书馆

此时,其他可能性正在使用其他库,如

  1. CUDA;
  2. CULA;
  3. MAGMA
  4. ArrayFire不是免费提供的,所以我没有尝试过。

    我遇到了CULA依赖项的一些安装问题,所以我没有进一步调查这一点(免责声明:我希望有更多时间,我能够解决这些问题)。

    然后我最终使用了MAGMA

    使用ArrayFire,我有完整ArrayFire计算的以下时间:

    SVD

    可以看出,时机略高,但现在可与CPU相媲美。

    以下是 200 x 200 0.036 1000 x 1000 0.2 2500 x 2500 4.5 5000 x 5000 29 代码:

    ArrayFire

答案 3 :(得分:0)

我试图在配备GTX 460的笔记本电脑上并行化SVD超过一个月,这也是我本科毕业论文的一部分,我做了很多实验,后来我发现MATLAB非常快,并且优于我的代码,顺便说一句,我使用Jacobi的一面,我还没有看到任何一篇论文比MATLAB的svd更快地揭示算法。在GPU上,如果你没有使用优雅的模型,内存复制的时间成本可能非常高,我建议你阅读更多关于CUDA的内容。   如果您需要任何帮助,请与我联系。

相关问题