調(diào)優(yōu)數(shù)學(xué)庫(kù)是從HPC系統(tǒng)中提取最終性能的一種簡(jiǎn)單而可靠的方法。 然而,對(duì)于長(zhǎng)期存在的應(yīng)用程序或需要在各種平臺(tái)上運(yùn)行的應(yīng)用程序來(lái)說(shuō),為每個(gè)供應(yīng)商或庫(kù)版本調(diào)整庫(kù)調(diào)用可能是一個(gè)維護(hù)噩夢(mèng)。
一個(gè)編譯器可以自動(dòng)生成對(duì)調(diào)優(yōu)的數(shù)學(xué)庫(kù)的調(diào)用,這給您提供了兩個(gè)世界中最好的:易于移植和最終性能。 在這篇文章中,我展示了如何無(wú)縫地加速GPU上的許多標(biāo)準(zhǔn)Fortran數(shù)組intrinsic和語(yǔ)言構(gòu)造。 nvfortran編譯器通過(guò)將Fortran語(yǔ)句映射到NVIDIA cu TEN SOR庫(kù)中可用的函數(shù)來(lái)自動(dòng)實(shí)現(xiàn)這種加速,這是一種第一種GPU加速的張量線性代數(shù)庫(kù),提供張量收縮、約簡(jiǎn)和元素操作。
一個(gè)簡(jiǎn)單的上車到NVIDIA GPU
下面是標(biāo)準(zhǔn)Fortran數(shù)組內(nèi)在函數(shù)如何映射到GPU加速的數(shù)學(xué)庫(kù)。 在最簡(jiǎn)單的層次上,只需要兩個(gè)Fortran語(yǔ)句就可以利用cut TEN SOR庫(kù)提供的出色性能:
use 卡頓索克斯 ... c = matmul(a,b)
使用的第一個(gè)語(yǔ)句 卡頓索克斯 預(yù)定義模塊以重載Fortran內(nèi)部過(guò)程、數(shù)組表達(dá)式和重載賦值的形式包含cuTENSOR庫(kù)的接口。這些接口僅用于映射位于GPU設(shè)備內(nèi)存中的陣列。在本文后面,我將從OpenACC和CUDA Fortran程序員的角度討論這意味著什么。定義了這些接口后,第二條語(yǔ)句包含 馬修() 內(nèi)在調(diào)用自動(dòng)映射到cuTEN SOR函數(shù)調(diào)用。
接口通過(guò)識(shí)別和匹配幾種常用模式來(lái)實(shí)現(xiàn)延遲執(zhí)行,這些模式可以映射到單個(gè)cu TEN SOR內(nèi)核調(diào)用。 在所有情況下,調(diào)用多個(gè)cu TEN SOR函數(shù)來(lái)設(shè)置cu TEN SOR所需的句柄、描述符數(shù)據(jù)結(jié)構(gòu)和工作緩沖區(qū)。
然而,只有一個(gè)內(nèi)核被啟動(dòng)到GPU上。 由于性能原因,必須映射整個(gè)語(yǔ)句,包括左側(cè)數(shù)組的賦值。 您不希望編譯器為右側(cè)操作的輸入或結(jié)果(中間或最終)創(chuàng)建臨時(shí)數(shù)組,這在Fortran中很常見(jiàn)。
支持標(biāo)準(zhǔn)Fortran操作
cut TEN SOR庫(kù)包含一般的置換和收縮操作。 置換的結(jié)果可以選擇由元素函數(shù)操作,也可以選擇縮放。
nvfortran編譯器可以識(shí)別和映射各種Fortran轉(zhuǎn)換intrinsic和元素intrinsic函數(shù),這些函數(shù)與通用數(shù)組語(yǔ)法相結(jié)合,用于cut TEN SOR功能。 一些比較直接的翻譯包括以下內(nèi)容:
d = transpose(a) d = func(transpose(a)) d = alpha * func(transpose(a) d = reshape(a,shape=[...]) d = reshape(a,shape=[...],order=[...]) d = func(reshape(a,...)) d = alpha * func(reshape(a,...)) d = spread(a,dim=k,ncopies=n) d = func(spread(a,dim=k,ncopies=n)) d = alpha * func(spread(a,dim=k,ncopies=n))
的投入 馬修() 也可以在CuTEN SOR中置換,結(jié)果可以縮放和累積。 這導(dǎo)致了幾種可能的組合,例如以下陳述:
c = matmul(a,b) c = c + matmul(a,b) c = c - matmul(a,b) c = c + alpha * matmul(a,b) d = alpha * matmul(a,b) + beta * c c = matmul(transpose(a),b) c = matmul(reshape(a,shape=[...],order=[...]),b) c = matmul(a,transpose(b)) c = matmul(a,reshape(b,shape=[...],order=[...]))
使用來(lái)自標(biāo)準(zhǔn)Fortran的NVIDIA TensorCores
利用cuTEN SOR和NVIDIA TensorCores可以像下面的代碼示例一樣容易,當(dāng)您使用包含在其中的隨機(jī)數(shù)生成特性時(shí) 卡頓索克斯 模塊:
program main use 卡頓索克斯 integer, parameter :: ni=5120, nj=5120, nk=5120, ntimes=10 真實(shí)的(8), allocatable, dimension(:,:) :: a, b, d allocate(a(ni,nk),b(nk,nj),d(ni,nj)) call random_number(a) call random_number(b) d = 0.0d0 print *,"cutensor" call cpu_time(t1) do nt = 1, ntimes d = d + matmul(a,b) end do call cpu_time(t2) flops = 2.0*ni*nj*nk flops = flops*ntimes print *,"times",t2,t1,t2-t1 print *,"GFlops",flops/(t2-t1)/1.e9 end program
The 馬修() 內(nèi)在調(diào)用映射到cuTENSOR調(diào)用,在可能的情況下無(wú)縫地使用Tensor Cores。我將在本文后面展示一些性能結(jié)果。
用nvfortran編譯程序
你可能會(huì)問(wèn)這個(gè)程序是如何使用cuTEN SOR的,當(dāng)我早些時(shí)候說(shuō)的 cutensorex 接口只將GPU設(shè)備陣列上的操作映射到CuTEN SOR調(diào)用。 答案在于程序是如何編譯的:
% nvfortran -acc -gpu=managed -cuda -cudalib main.f90
在這里,我將程序編譯為Open ACC程序,并利用OpenACC管理內(nèi)存模式,其中所有可分配數(shù)組都在CUDA統(tǒng)一內(nèi)存中分配。 加上了 -cuda 這也支持CUDAFortran擴(kuò)展,數(shù)組本質(zhì)上是CUDAFortran– 托管數(shù)組。 CUDA Fortran通用接口匹配的一個(gè)規(guī)則是,當(dāng)主機(jī)和設(shè)備接口都存在時(shí),對(duì)于托管的實(shí)際參數(shù)更喜歡設(shè)備接口。
當(dāng)聲明、分配和使用在同一個(gè)程序單元中時(shí),nvfortran編譯器提供了一些快捷方式。 一般來(lái)說(shuō),最好使用OpenACC指令來(lái)指示編譯器傳遞設(shè)備地址,如下面的代碼示例:
!$acc host_data use_device(a, b, d) do nt = 1, ntimes d = d + matmul(a,b) end do !$acc end host_data
在這種情況下 -cuda 不需要編譯器選項(xiàng)。
使用CUDAFortran的CuTEN SOR
對(duì)于CUDAFortran用戶,the cutensorex 模塊和Fortran轉(zhuǎn)換本質(zhì)成為高性能和完全可移植代碼的快速路徑。 使用這個(gè) !@cuf 哨兵添加由nvfortranCUDAFortran編譯器解釋和編譯的代碼行,或被標(biāo)準(zhǔn)Fortran編譯器忽略為注釋:
program main !@cuf use cutensorex !@cuf use cudafor integer, parameter :: ni=5120, nj=5120, nk=5120, ntimes=10 real(8), allocatable, dimension(:,:) :: a, b, d !@cuf attributes(device) :: a, b, d allocate(a(ni,nk),b(nk,nj),d(ni,nj)) call random_number(a) call random_number(b) d = 0.0d0 print *,"cutensor" call cpu_time(t1) do nt = 1, ntimes d = d + matmul(a,b) end do call cpu_time(t2) flops = 2.0*ni*nj*nk flops = flops*ntimes print *,"times",t2,t1,t2-t1 print *,"GFlops",flops/(t2-t1)/1.e9 end program
在第6行,我用設(shè)備屬性聲明了數(shù)組,它將它們放在GPU設(shè)備內(nèi)存中。 但是,它們也可以用托管屬性來(lái)聲明。 本程序可編譯并鏈接如下命令:
% nvfortran -Mcudalib main.cuf
在真實(shí)(8)數(shù)據(jù)上測(cè)量的性能
下面是性能,從前面示例中使用的真實(shí)(8)(雙精度)數(shù)據(jù)開(kāi)始。 你可以用幾種方式來(lái)衡量矩陣乘性能:
單線程CPU實(shí)現(xiàn)
多線程或多核CPU實(shí)現(xiàn)
樸素編碼矩陣乘用指令卸載
The 馬修() 內(nèi)在映射到CuTEN SOR
To get the best threaded-CPU performance, use the basic linear algebra subprogram (BLAS) library routine DGEMM. The equivalent DGEMM call to the earlier operation is the following command:
call dgemm('n','n',ni,nj,nk,1.0d0,a,ni,b,nk,1.0d0,d,ni)
為了了解調(diào)優(yōu)庫(kù)在天真的實(shí)現(xiàn)中可以提供什么,請(qǐng)使用下面的Open ACC循環(huán)結(jié)構(gòu)在GPU上運(yùn)行。 回路結(jié)構(gòu)采用無(wú)特殊平鋪或硬件指令。
!$acc kernels do j = 1, nj do i = 1, ni do k = 1, nk d(i,j) = d(i,j) + a(i,k) * b(k,j) end do end do end do !$acc end kernels
實(shí)施/處理器 | TFLOP |
NVFORTRAN單CPU核上的Matmul | 0.010 |
在64個(gè)CPU核心上的MKLDGEMM | 1.674 |
天真開(kāi)放ACC在V100 | 0.235 |
天真開(kāi)放ACC在A100 | 0.447 |
NVFORTRAN Matmul on V100 | 6.866 |
A100上的NVFORTRAN Matmul | 一十七點(diǎn)六六 |
您不僅得到自動(dòng)GPU加速在V100和A100GPU使用 馬修() 內(nèi)在的,但在A100上的映射 馬修() 對(duì)于cuTensor調(diào)用,您可以自動(dòng)使用FP64TensorCores。
在真實(shí)(4)和真實(shí)(2)數(shù)據(jù)上測(cè)量的性能
您可以使用相同的運(yùn)行集執(zhí)行 真實(shí)的(4) (單一精度)數(shù)據(jù)和調(diào)用SGEMM而不是DGEMM。 此外,CUDA11.0cut Tensor Fortran包裝器可以利用A100TF32數(shù)據(jù)類型和TensorCores。 表2顯示了這些運(yùn)行的性能。
實(shí)施/處理器 | TFLOP |
NVFORTRAN單CPU核上的Matmul | 0.025 |
在64個(gè)CPU核心上的MKLSGEMM | 3.017 |
天真開(kāi)放ACC在V100 | 0.460 |
天真開(kāi)放ACC在A100 | 0.946 |
NVFORTRAN Matmul on V100 | 一十點(diǎn)七零六 |
A100上的NVFORTRAN Matmul | 一十四點(diǎn)六二一 |
NVFORTRAN Matmul on A100 using TF32 | 六十點(diǎn)三五八 |
為什么停在那里? nvfortran編譯器支持16位浮點(diǎn)格式(FP16 真實(shí)的(2) 數(shù)據(jù)類型。 您可以在前面的測(cè)試中更改數(shù)組的類型,并在半精度上運(yùn)行時(shí)間。
在V100上引入了半精度數(shù)據(jù)的TensorCore操作,然后在A100GPU上擴(kuò)展,以支持TF32和全雙精度DP64TensorCores。 而nvfortran支持 真實(shí)的(2) 而TensorCores在V100和A100上,它不支持完整和優(yōu)化 真實(shí)的(2) 在CPU上,標(biāo)準(zhǔn)的BLAS庫(kù)也沒(méi)有。 在這種情況下,比較GPU加速版本的性能是有意義的(表3)。
實(shí)施/處理器 | TFLOP |
天真開(kāi)放ACC在V100 | 0.490 |
天真開(kāi)放ACC在A100 | 2.058 |
NVFORTRAN Matmul on V100 | 六十八點(diǎn)二四二 |
A100上的NVFORTRAN Matmul | 九十二點(diǎn)八一 |
雖然A100的性能令人印象深刻,代碼是完全可移植的,但對(duì)于TF32和FP16來(lái)說(shuō),它明顯低于峰值。 有固定的開(kāi)銷:在每次調(diào)用時(shí),創(chuàng)建和銷毀cutTEN SOR張量描述符并創(chuàng)建收縮計(jì)劃。 您還必須查詢和管理收縮中使用的工作區(qū)需求,這最終可能會(huì)調(diào)用 古達(dá)·馬洛克 and 無(wú)庫(kù)達(dá) 。 如果開(kāi)銷是5– 對(duì)于FP64,這變得更接近25%的TF32和大約35%的FP16,對(duì)于這個(gè)大小的問(wèn)題。
對(duì)于需要最終性能的開(kāi)發(fā)人員,nvfortran確實(shí)直接支持Fortran接口到FortranCutensor模塊中的CcuTEN SORAPI,也是在HPCSDK中提供的。 您可以自己管理張量描述符、計(jì)劃和工作區(qū)。
結(jié)局推論
在這篇文章中,我展示了一些簡(jiǎn)單的程序和Fortran intrinsic調(diào)用的類型以及可以在GPU上自動(dòng)加速的代碼模式。 他們甚至可以通過(guò)cuTEN SOR自動(dòng)利用TensorCores。 使用幾乎完全標(biāo)準(zhǔn)的Fortran和完全可移植到其他編譯器和系統(tǒng)的程序,您可以在NVIDIA GPU上實(shí)現(xiàn)矩陣乘法、矩陣轉(zhuǎn)置、元素?cái)?shù)組本質(zhì)和數(shù)組語(yǔ)法的多個(gè)組合上的近峰性能。
不可能預(yù)測(cè)你可以用這些新特性做些什么或?qū)崿F(xiàn)什么。 我期待著看到你的反饋和結(jié)果。 NVIDIA繼續(xù)添加更多的特性,允許您使用標(biāo)準(zhǔn)Fortran結(jié)構(gòu)以最大性能編程NVIDIA GPU。
關(guān)于作者
關(guān)于布倫特·萊克
Brent Leback管理NVIDIA HPC編譯器客戶支持和高級(jí)服務(wù),并與HPC社區(qū)一起移植和優(yōu)化GPU計(jì)算應(yīng)用程序。 他是CUDAFortran編程語(yǔ)言的共同創(chuàng)造者,并繼續(xù)積極參與新的CUDAFortran功能的設(shè)計(jì)。 他是開(kāi)放ACC GPU黑客馬拉松的常客,也是CUDA Fortran的專家。
審核編輯 黃昊宇
發(fā)布評(píng)論請(qǐng)先 登錄
求Labview 下調(diào)用Fortran dll的詳細(xì)教程
Fortran
時(shí)間域控制系統(tǒng)分析及拉普拉斯變換Fortran程序解答
Arm Fortran編譯器開(kāi)發(fā)人員和參考指南
Arm Fortran編譯器22.1版開(kāi)發(fā)人員和參考指南
Fortran函數(shù)大全
獲得英特爾Fortran編譯器的支持
FORTRAN教程Fortran語(yǔ)言程序設(shè)計(jì)詳細(xì)課件資料免費(fèi)下載

評(píng)論