调优的数学库是从您的HPC系统中提取终极性能的一种简单可靠的方法。但是,对于寿命长的应用程序或需要在各种平台上运行的应用程序,为每个供应商或库版本改编库调用可能是维护的噩梦。
可以自动生成对调优数学库的调用的编译器为您提供了两全其美:简单的可移植性和终极性能。在这篇文章中,我将向您展示如何在GPU上无缝加速许多标准Fortran数组内部函数和语言构造。Nvfortran编译器通过将Fortran语句映射到NVIDIA cuTENSOR库中的可用函数来自动实现此加速。NVIDIA cuTENSOR库是一种首创的GPU加速张量线性代数库,提供张量压缩、缩减和元素级运算。
下面是标准的Fortran数组内部函数如何映射到GPU加速的数学库。在最简单的级别上,只需要两条Fortran语句就可以利用cuTENSOR库提供的出色性能:
使用cutensorex预定义模块的第一条语句包括重载Fortran内部过程、数组表达式和重载赋值形式的cuTENSOR库接口。接口被写入以仅映射位于GPU设备存储器中的数组。在这篇文章的后面,我将从openacc和CUDA Fortran程序员的角度讨论这意味着什么。在定义了这些接口之后,包含matmul()内部调用的第二个语句将自动映射到cuTENSOR函数调用。
这些接口通过识别和匹配几个可以映射到单个cuTENSOR内核调用的常用模式来实现延迟执行。在所有情况下,都会调用多个cuTENSOR函数来设置cuTENSOR需要的句柄、描述符数据结构和工作缓冲区。
但是,GPU上只启动了一个内核。出于性能原因,映射整个语句(包括对左侧数组的赋值)非常重要。您不希望编译器为右侧操作的输入或结果(中间或最终)创建临时数组,这在Fortran中很常见。
CuTENSOR库包含一般的排列和压缩操作。排列的结果可以任选地通过基本函数进行运算,并且可以任选地缩放。
Nvfortran编译器可以识别各种Fortran转换内部函数和与常规数组语法结合使用的基本内部函数,并将其映射到cuTENSOR功能。以下是一些比较直白的翻译:
D=转置(A)d=func(转置(A))d=alpha*func(转置(A)和d=RESHAPE(a,Shape=[...])d=RESHAPE(a,Shape=[...],order=[...])d=func(RESHAPE(a,...))d=alpha*FUNC(RESHAPE(a,...))d=spend(a,dim=k,ncopy=n)d=func(a,d,...)d=func(a,dim=k,ncopy=n)d=func(a,dm=k,ncopy=n)d=func(a,dim=k,ncopy=n。
Matmul()的输入也可以在cuTENSOR中进行置换,并且结果可以缩放和累加。这会导致几种可能的组合,例如以下语句:
C=matmul(a,b)c=c+matmul(a,b)c=c-matmul(a,b)c=c+α*matmul(a,b)d=α*matmul(a,b)+β*c=matmul(转置(A),b)c=matmul(重塑(a,形状=[...],顺序=[...]),b)c=matmul(a,转置(b。
当您使用cutensorex模块中包含的随机数生成功能时,利用cuTENSOR和NVIDIA张量核心与下面的代码示例一样简单:
程序主程序使用cutensorex*整数,参数::ni=5120,nj=5120,nk=5120,nx=10%.real(8),allocatable,Dimension(:,:)::a,b,d*;**调用CPU_TIME(T1)**Do NT=1,nTimes*d=d+matmul(a,b)*END:**调用CPU_TIME(T2)*Floops=2.0*ni*nj*nk*Floops=flops*nTimes*Print*,";Times";,T2,T1,T2-T1*Floops=2.0*ni*Nj*nk*,";;";,T2,T1,T2-T1*t2*nk*fops=#34;,t2,t1,t2-t1.。,flops/(t2-t1)/1.e9结束程序。
Matmul()内部调用被映射到尽可能无缝使用张量核的cuTENSOR调用。
当我前面提到cutensorex接口只将GPU设备数组上的操作映射到cuTENSOR调用时,您可能会问这个程序是如何使用cuTENSOR的。答案在于程序是如何编译的:
在这里,我将该程序编译为openacc程序,并利用openacc托管内存模式,在该模式下,所有可分配的数组都分配在CUDA统一内存中。通过添加-cuda(也支持CUDA Fortran扩展),这些数组实质上是CUDA Fortran管理的数组。CUDA Fortran通用接口匹配的一条规则是,当主机和设备接口都存在时,优先使用设备接口作为托管的实际参数。
当声明、分配和使用在同一程序单元中时,nvfortran编译器提供了一些快捷方式。通常,最好使用openacc指令指示编译器传递设备地址,如下面的代码示例所示:
!$acc HOST_DATA USE_DEVICE(a,b,d)$acc end HOST_DATA NT=1,n乘以$acc END HOST_DATA!$acc end host_data=d+matmul(a,b)!$acc end host_data。
对于CUDA Fortran用户来说,cutensorex模块和Fortran转换内部函数成为获得高性能和完全可移植代码的快速途径。使用!@CUF语句添加代码行,这些代码行由nvfortran CUDA Fortran编译器解释和编译,或被标准Fortran编译器作为注释忽略:
Program Main!@CUF Use cutensorex!@CUF Use cudafor个整数,参数::ni=5120,nj=5120,nk=5120,nTimes=10。实数(8),可分配,维度(:,:)::a,b,d!@CUF Attributes(Device)::a,b,d*分配(a(ni,nk),b(nk,nj),d。新泽西州))可以调用RANDOM_NUMBER(A);可以调用RANDOM_NUMBER(B);可以调用RANDOM_NUMBER(B),可以调用RANDOM_NUMBER(B)。CUTESOR_TIME(T1)=1,n×FLOPS DO NT=1,nTimes_TIME=d+matmul(a,b);*END_DO将调用CPU_TIME(T2)**FLOPS=2.0*ni*nj*nk*Floops=flops*nTimes*fprint*,";Times";,t2,t2;。,flops/(t2-t1)/1.e9结束程序。
在第6行,我用device属性声明了数组,这会将它们放在GPU设备内存中。但是,也可以使用Managed属性声明它们。可以使用以下命令编译和链接此程序:
下面来看一下性能,从前面示例中使用的实数(8)(双精度)数据开始。您可以通过以下几种方式测量矩阵乘法性能:
要获得最佳的线程化CPU性能,请使用基本线性代数子程序(BLAS)库例程DGEMM。对于前面的操作,等效的DGEMM调用是以下命令:
要了解调优后的库可以通过简单的实现提供什么,请使用下面的openacc循环结构在GPU上运行。循环结构不使用特殊的平铺或硬件指令。
。
表1显示了在基于双插槽AMD EPYC 7742罗马CPU的服务器的一个NUMA节点、单个NVIDIA V100和单个NVIDIA A100 GPU上实现的实际(8)性能。
您不仅可以在V100和A100 GPU上使用matmul()内部函数实现自动GPU加速,而且在A100上,matmul()到cuTENSOR调用的映射可以让您自动使用FP64张量核。
可以使用实数(4)(单精度)数据并调用SGEMM而不是DGEMM来执行相同的运行集。此外,CUDA 11.0cuTENSOR Fortran包装器可以利用A100 TF32数据类型和张量核心。表2显示了这些运行的性能。
为什么要止步于此呢?Nvfortran编译器支持使用REAL(2)数据类型的16位浮点格式(FP16)。您可以在前面的测试中更改数组的类型,也可以以半精度运行计时。
在V100上引入了半精度数据的张量核运算,然后在A100 GPU上进行了扩展,以支持TF32和全双精度DP64张量核。虽然nvfortran在V100和A100上支持REAL(2)和张量内核,但它还不支持在CPU上完全和优化的REAL(2),标准的BLAS库也不支持。在这种情况下,比较GPU加速版本的性能才有意义(表3)。
虽然A100的性能令人印象深刻,代码完全可移植,但它明显低于TF32和FP16的峰值。有固定的开销:在每次调用时,您都要创建和销毁cuTENSOR张量描述符,并创建收缩计划。您还必须查询和管理收缩中使用的工作空间需求,这最终可能会调用cudaMalloc和cudaFree。如果FP64的开销为5-10%,那么对于这种规模的问题,TF32的开销接近25%,FP16的开销约为35%。
对于需要终极性能的开发人员,nvfortran确实在Fortran cutensor模块(也在HPC SDK中提供)中直接支持到C cuTENSOR API的Fortran接口。您可以自己管理张量描述符、计划和工作区。
在这篇文章中,我展示了一些简单的程序,以及可以在GPU上自动加速的Fortran内部调用和代码模式的类型。他们甚至可以通过cuTENSOR自动利用张量芯。使用几乎完全符合Fortran标准且完全可移植到其他编译器和系统的程序,您可以在NVIDIA GPU上实现矩阵乘法、矩阵转置、基本数组内部运算以及数组语法的多种组合的近乎峰值的性能。
无法预测使用这些新功能可能会做什么或实现什么。我期待看到您的反馈和结果。NVIDIA继续添加更多功能,使您能够使用标准Fortran结构以最高性能编程NVIDIA GPU。