第八节:利用CUDA函数库
Rob Farber 是西北太平洋国家实验室(Pacific Northwest National Laboratory)的高级科研人员。他在多个国家级的实验室进行大型并行运算的研究,并且是几个新创企业的合伙人。大家可以发邮件到rmfarber@gmail.com与他沟通和交流。
在关于CUDA(Compute Unified Device Architecture,即计算统一设备架构的简称)的系列文章的第七节,我探讨了下一代的CUDA硬件。在本小节,我的关注重点略有转移,从硬件转到了软件,注重于CUDA函数库。
优化库提供了一种快捷的方法改进应用程序的性能。当为大型遗产项目建立端口时,函数库可能是建立新平台的唯一真实有效的优化方式,因为代码改变会要求大量的验证工作。实际上,函数库很方便,可以极大地促进代码的发展和应用程序的性能,但是不能盲目地利用代码。以GPU为基础的函数库特别要求你仔细考虑数据布局以及如何使用函数库.否则,你就可能得到极不理想的性能。
Basic Linear Algebra Subprograms (基本线性代数子程序BLAS)包实际上是基本线性代数运算(如向量和矩阵相乘)的编程界面。NVIDIA 利用其GPU函数库(也就是CUBLAS)对这个界面提供支持。CUFFT是NVIDIA支持的另外一个函数库,基于GPU执行快速傅立叶变换(Fast Fourier Transform ,FFT), 这是在科学和信号处理应用程序中最常用的一个方法。它仿照了FFTW高度优化,用于广泛目的处理器上的,颇受用户喜欢的FFT包。
BLAS 根据以下三个不同等级构架:
等级1 算法式的样例,包括提取两个向量的内部产品,或用常量乘子增加向量;
等级2 算法式为矩阵向量乘或单右侧三角解方程组;
等级3 算法式包括密集矩阵向量乘
如果我们假定向量为长度N或矩阵N*N, 然后等级1算法式,等级2算法式和等级3算法式的浮点运算的数分别为O(N), O(N2),和O(N3)。(Big-O标识是描述输入大小如何影响算法消费计算资源的(如时间或内存)一个便捷的方法)。
应用程序使用CUBLAS函数库的基本模式就是:在GPU内存空间创建矩阵和向量目标,用数据填满目标,调用一系列CUBLAS函数,最后,将结果从GPU内存空间转移到主机。为此,CUBLAS提供帮助函数,以创建和摧毁GPU空间里的目标,将数据写入这些目标,或从这些目标检索数据。CUBLAS使用行为主存储和基于1的索引,以获得最大的FORTRAN兼容性。C和C++ 应用程序需要使用宏观或行内行数以促进对CUBLAS创建的目标的访问。
当使用CUBLAS(和BLAS例程)时,数据移动是一个中心考虑点。根据不同等级,BLAS数据移动为O(N), O(N2), 和 O(N2),这样,每个移动的数据项的浮动点运算数为O(1), O(1), 和O(N)。这意味着确定数据在GPU上的位置非常重要,我将会更详细地讨论这个问题。
因为GPU的线程处理器仅在GPU本地数据上运行,主机的内存空间上的向量或矩阵的BLAS运算需要数据转移操作。相对于图形处理器的浮点能力来说,这些数据转移比较昂贵(GPU执行浮点的速度可比移动数据快)。只要有可能,就要尽量避免数据转移,它们也可能给成为获得高性能的瓶颈。
为了描述清楚,考虑下把一个向量从主系统转移到GPU所需花费,用一个常量相乘,然后将结果返回给主机。这个“应用程序”要求从GPU来回移动4N字节数据(N是向量内浮点的数量)以进行N次相乘(常量的向量倍)。
这个“应用程序”执行的最好结果是:用8除以传输带宽(所要求的字节数,以将32位浮点移动离开或进入GPU)。“幸运”的话,假定主机和GPU之间是4GB/秒的传输速度,这样的一个应用程序会得到a = GFLOP (每秒十亿浮点运算billion floating-pointoperations per second)浮点性能。这正好低于最经济的低端NVIDA GPU的浮点性能。充分利用PCIebus的全双工能力,分批多次操作可加强这一性能。
底线:如果要等级1和等级2BLAS应用程序发挥最好的性能,要求在GPU上保存尽可能多的性能。如果不可能,至少要尝试分批次尽可能进行库调用。
等级3BLAS应用程序可获得非常有效的性能。表1说明了不同构架上的SGEMM基准(如,HP xw8600配备Xeon E5440,速率2.83GHz,运行CUDA2.0的C870和C1060)。值得注意的是:基准执行的操作次序和执行快速矩阵幂的计算金融应用程序的操作次序一致――因此,它反应了真实世界的性能能力。所有的结果都报道在GELOP里。CUBLAS命名内的"Thunking"是一个界面,会自动分配内存,复制数据到GPU,运行运算,复制回结果,然后释放内存。这样,它就成为了BLAS的替代品,但是因为数据移动关系,它有着明显的性能实质。在标识为"GPUonly"的栏目,应用程序开发人员管理数据分配/释放和拷贝。在本例中,内存被分配一次,数据被拷贝,大批SGEMM被调用,然后复制回结果。移动数据的花费很小。结果包括8和10系列上的Tesla卡上的几个N*N矩阵尺寸和一个四核CPU。请注意,相比thunking界面,对小尺寸矩阵来说,CPU要运行得快一些,但是如果应用程序开发人员更贴近地管理数据移动,GPU执行效率会更好一些。
N |
CPU 单线程 |
CPU 2 核心 |
CPU 4 核心 |
C870 Thunking |
C870 GPU 仅 |
C1060 Thunking |
C1060 GPU 仅 |
512 |
14.92 |
29.84 |
51.14 |
44.75 |
171.29 |
67.13 |
245.12 |
768 |
14.62 |
28.32 |
53.31 |
75.51 |
172.86 |
90.62 |
332.18 |
1024 |
14.62 |
28.08 |
54.73 |
85.92 |
173.7 |
113.04 |
317.68 |
2048 |
20.48 |
40.06 |
76.71 |
121.01 |
166.72 |
197.5 |
354.96 |
表1
对备用NVIDIA库的这一代可以有所改进。例如,在论文LU, QR and Cholesky Factorizations using Vector Capabilities of GPUs,Vasily Volkov和James Demmel声称用他们的矩阵-矩阵乘法例行程序,可达到G80系列GPU的顶级性能。他们的论文里,详细探讨了GPU内存系统,以对特定的改进进行设计。他们声称他们的方法比CUBLAS1.1执行要快约60%。这些改进被添加入了CUBLAS2.0版本,以用来产生上列表格内所示的结果。NVIDIA一直没有停止过对硬件和库代码的改进。在GPU上用BLAS可获得优异性能。例如,FORTRAN程序员应该参考FLAGON, 它是要给开放源库项目,执行一个添加GPU“设备描述符的中间件调用层,以帮助优化性能。在他们的Supercomputing 2007 poster,Nail A. Gumerov, Ramani Duraiswami,和William D. Dorland声称,当使用迭代解算器,给分散数据配备径向基函数时,使用CUFFT和一些优秀的性能结果,可以在Intel QX6700系列CPU代码基础上提速25倍。
在各类文章里,也有很多样例,GPU库提供了一些方便,但是没有带来性能优势(有时候甚至会损坏性能)。正如之前讨论的,数据移动的成本会是决定性的因素。例如,参考CUFFT vs. FFTW,以获得对此的出色界定。
总而言之,NVIDIA CUFFT和CUBLAS库为线性代数导引和快速傅立叶提供了一个便捷和广为接受的界面。有了它们,大型项目可以使用图形处理器。如果注意最小化或者消除数据移动,可以获得出色的性能。分批多次进行库调用可增加性能。对CUDA启动的设备的特点予以关注,可以获得改进。因为NVIDIA正在持续改进硬件和库代码,让我们期待持续的性能进步吧。