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

評論