将Tensor核心引入标准Fortran

调优的数学库是从HPC系统提取最终性能的一种简单而可靠的方法。但是,对于寿命长的应用程序或需要在各种平台上运行的应用程序,为每个供应商或库版本调整库调用可能是维护的噩梦。

可以自动生成对调优数学库的调用的编译器为提供了两全其美的优势:易于移植和终极性能。在本文中,将展示如何在GPU上无缝加速许多标准Fortran数组内在函数和语言构造。nvfortran编译器通过将Fortran语句映射到NVIDIA cuTENSOR库中的可用功能来自动启用此加速,该库是NVIDIA cuTENSOR的先河,提供GPU加速的张量线性代数库,提供张量收缩,缩小和逐元素运算。

轻松过渡到NVIDIA GPU

以下是标准Fortran数组内在函数如何映射到GPU加速的数学库。在最简单的级别上,只需要两个Fortran语句即可利用cuTENSOR库提供的出色性能:

使用cutensorex
...
c = matmul(a,b)

使用cutensorex预定义模块的第一条语句以重载的Fortran内在过程,数组表达式和重载的赋值形式包含cuTENSOR库的接口。编写接口以仅映射位于GPU设备内存中的阵列。在本文的稍后部分,将从OpenACC和CUDA Fortran程序员的角度讨论这意味着什么。定义了这些接口后,包含matmul()内部调用的第二条语句将自动映射到cuTENSOR函数调用。

这些接口通过识别并匹配可以映射到单个cuTENSOR内核调用的几种常用模式来实现延迟执行。在所有情况下,都将调用多个cuTENSOR函数来设置cuTENSOR所需的句柄,描述符数据结构和工作缓冲区。

但是,只有一个内核被启动到GPU。出于性能考虑,将整个语句(包括分配给左侧数组)映射起来很重要。不希望编译器为右侧操作的输入或结果(中间或最终)创建Fortran中常见的临时数组。

支持的标准Fortran操作

cuTENSOR库包含常规的排列和收缩操作。置换的结果可以可选地通过元素函数来操作,并且可选地缩放。

nvfortran编译器可以识别和映射与通用数组语法结合使用的各种Fortran转换内在函数和基本内在函数,并将其映射到cuTENSOR功能。一些更直接的翻译包括:

d =转置(a)
d =函数(transpose(a))
d = alpha * func(转置(a)
 
d =重塑(a,shape = [...])
d =重塑(a,shape = [...],order = [...])
d =函数(reshape(a,...))
d = alpha * func(reshape(a,...))
 
d =点差(a,dim = k,ncopies = n)
d =函数(spread(a,dim = k,ncopies = n))
d = alpha * func(spread(a,dim = k,ncopies = n))

matmul()也可以在cuTENSOR中排列to的输入,并且可以缩放和累加结果。这导致几种可能的组合,例如以下语句:

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(转置(a),b)
c = matmul(reshape(a,shape = [...],order = [...]),b)
c = matmul(a,转置(b))
c = matmul(a,reshape(b,shape = [...],order = [...]))

使用标准Fortran中的NVIDIA Tensor Core

当使用cutensorex模块中包含的用于随机数生成的功能时,利用cuTENSOR和NVIDIA Tensor Core就像下面的代码示例一样容易:

程序主体
      使用cutensorex
      整数,参数:: ni = 5120,nj = 5120,nk = 5120,ntimes = 10
      实数(8),可分配,尺寸(:,:) :: a,b,d
      分配(a(ni,nk),b(nk,nj),d(ni,nj))
      呼叫random_number(a)
      呼叫random_number(b)
      d = 0.0d0
 
      打印*,“裁切器”
      呼叫cpu_time(t1)
      做nt = 1,ntimes
        d = d + matmul(a,b)
      做完
      呼叫cpu_time(t2)
 
      拖鞋= 2.0 * ni * nj * nk
      翻牌=翻牌* n次
      打印*,“ times”,t2,t1,t2-t1
      打印*,“ GFlops”,flops /(t2-t1)/1.e9
      结束程序

matmul()固有调用映射到cuTENSOR调用,无缝地使用张量核尽可能。将在本文后面显示一些性能结果。

使用nvfortran编译程序

可能会问, cutensorex接口仅将GPU设备阵列上的操作映射到cuTENSOR调用时,该程序如何使用cuTENSOR 。答案在于程序的编译方式:

%nvfortran -acc -gpu =托管-cuda -cudalib main.f90

在这里,将程序编译为OpenACC程序,并利用OpenACC托管内存模式,在该模式下,所有可分配阵列都分配在CUDA统一内存中。由于增加了-cuda,使CUDA Fortran扩展为好,数组实质上CUDA Fortran语言-管理型阵列。CUDA Fortran通用接口匹配的一条规则是,当主机和设备接口同时存在时,优选使用设备接口作为托管实际参数。

当声明,分配和使用位于同一程序单元中时,nvfortran编译器提供一些快捷方式。通常,最好使用OpenACC指令来指示编译器传递设备地址,如以下代码示例所示:

!$ acc host_data use_device(a,b,d)
      做nt = 1,ntimes
        d = d + matmul(a,b)
      做完
!$ acc结束host_data

在这种情况下,-cuda不需要编译器选项。

使用CUDA Fortran中的cuTENSOR

对于CUDA Fortran用户而言,cutensorex模块和Fortran转换内在函数成为获取高性能和完全可移植代码的快速途径。使用!@cuf前哨添加由nvfortran CUDA Fortran编译器解释和编译的代码行,或由标准Fortran编译器作为注释忽略的代码行:

      程序主体
!@cuf使用cutensorex
!@cuf使用cudafor
      整数,参数:: ni = 5120,nj = 5120,nk = 5120,ntimes = 10
      实数(8),可分配,尺寸(:,:) :: a,b,d
!@cuf属性(设备):: a,b,d
      分配(a(ni,nk),b(nk,nj),d(ni,nj))
      呼叫random_number(a)
      呼叫random_number(b)
      d = 0.0d0
 
      打印*,“裁切器”
      呼叫cpu_time(t1)
      做nt = 1,ntimes
        d = d + matmul(a,b)
      做完
      呼叫cpu_time(t2)
 
      拖鞋= 2.0 * ni * nj * nk
      翻牌=翻牌* n次
      打印*,“ times”,t2,t1,t2-t1
      打印*,“ GFlops”,flops /(t2-t1)/1.e9
      结束程序

在第6行,使用设备属性声明了数组,将其放置在GPU设备内存中。但是,也可以使用托管属性来声明它。可以编译该程序并将其与以下命令链接:

%nvfortran -Mcudalib main.cuf

实测(8)数据的性能

下面是从前面示例中使用的real(8)(双精度)数据开始的性能观察。可以通过以下几种方法来衡量矩阵乘法性能:

  • 单线程CPU实现
  • 多线程或多核CPU实现
  • 天真编码矩阵乘法使用指令分载
  • matmul()映射到cuTENSOR内在

为了获得最佳的线程CPU性能,请使用基本的线性代数子程序(BLAS)库例程DGEMM。以下命令与先前操作的等效DGEMM调用:

呼叫dgemm('n','n',ni,nj,nk,1.0d0,a,ni,b,nk,1.0d0,d,ni)

为了了解经过调整的库可以通过天真的实现提供什么,使用以下OpenACC循环结构在GPU上运行。循环结构不使用特殊的拼贴或硬件指令。

$ acc内核
      做j = 1,nj
         是否等于1,ni
            做k = 1,nk
               d(i,j)= d(i,j)+ a(i,k)* b(k,j)
            做完
         做完
      做完
$ acc结束内核

表1显示了real(8)在基于双插槽AMD EPYC 7742 Rome CPU的服务器,单个NVIDIA V100和单个NVIDIA A100 GPU的一个NUMA节点上获得的性能。

表1.双插槽AMD EPYC 7742 Rome基于CPU的服务器,单个V100和单个A100 GPU在一个NUMA节点上的real(8)性能。

不仅可以使用matmul()固有函数在V100和A100 GPU上获得自动GPU加速,而且在A100上matmul()到cuTENSOR调用的映射可以使自动使用FP64 Tensor Core。

在real(4)和real(2)数据上测得的性能

可以使用real(4)(单精度)数据并调用SGEMM而不是DGEMM来执行同一组运行。此外,CUDA 11.0 cuTENSOR Fortran包装器可以利用A100 TF32数据类型和Tensor Core。表2显示了这些运行的性能。

表2.双插槽AMD EPYC 7742 Rome基于CPU的服务器,单个V100和单个A100 GPU的一个NUMA节点上的real(4)性能。

为什么停在那里?nvfortran编译器使用real(2)数据类型支持16位浮点格式(FP16)。可以在较早的测试中更改数组的类型,并以半精度运行计时。

Tensor Core操作是在V100上引入的,用于半精度数据,然后在A100 GPU上进行了扩展,以支持TF32和完整的双精度DP64 Tensor Core。尽管nvfortran支持real(2)V100和A100上的Tensor Core,但它不支持real(2)CPU上的完整和优化,标准BLAS库也不支持。在这种情况下,只有比较GPU加速版本的性能才有意义(表3)。

表3.单个V100和单个A100 GPU的real(2)性能。

尽管A100的性能令人印象深刻且代码可完全移植,但它远低于TF32和FP16的峰值。有固定的开销:在每个调用中,创建并销毁cuTENSOR张量描述符并创建收缩计划。还必须查询和管理收缩中使用的工作空间要求,这最终可能会调用cudaMalloccudaFree。如果FP64的开销为5 – 10%,则对于这种大小的问题,TF32的开销将接近25%,FP16的开销将接近35%。

对于需要最终性能的开发人员,nvfortran确实支持HPC SDK中也提供的Fortran cutensor模块中与C cuTENSOR API的Fortran接口。可以自己管理张量描述符,计划和工作区。

结论

在本文中,展示了一些简单的程序以及可以在GPU上自动加速的Fortran内部调用和代码模式的类型。甚至可以通过cuTENSOR自动利用Tensor Core。使用几乎完全是Fortran标准的程序并完全可移植到其编译器和系统的程序,可以在NVIDIA GPU上实现矩阵乘法,矩阵转置,元素数组内在函数以及数组语法的多种组合的近峰性能。

无法预测使用这些新功能可能会做什么。期待着的反馈和结果。NVIDIA继续添加更多功能,使可以使用标准的Fortran结构以最高的性能对NVIDIA GPU进行编程。

将Tensor核心引入标准Fortran的更多相关文章

  1. A100 Tensor核心可加速HPC

    A100 Tensor核心可加速HPC HPC应用程序的性能需求正在迅速增长.众多科学研究领域的许多应用程序都依赖于双精度(FP64)计算. 为了满足HPC计算快速增长的计算需求,A100 GPU支持 ...

  2. python 生成list的所有的子集 (不使用递归且不引入标准库)

    不使用递归且不引入标准库,单纯用两个for循环即可得出一个list的所有子集 L = [1, 2, 3, 4] List = [[]] for i in range(len(L)):          ...

  3. css的核心内容 标准流、盒子模型、浮动、定位等分析

    1.块级元素:如:<div></div>2.行内元素:如:<span></span>从效果中看块级元素与行内元素的区别: 通过CSS的设置把行内元素转换 ...

  4. c++标准库多线程入门

    从c++ 11开始,语言核心和标准库开始引入了对多线程的原生支持.如下所示: int doSth(char c) { default_random_engine dre(c); uniform_int ...

  5. 什么是 C 和 C ++ 标准库?

    简要介绍编写C/C ++应用程序的领域,标准库的作用以及它是如何在各种操作系统中实现的. 我已经接触C++一段时间了,一开始就让我感到疑惑的是其内部结构:我所使用的内核函数和类从何而来? 谁发明了它们 ...

  6. [转]什么是 C 和 C ++ 标准库?

    转载地址:https://www.cnblogs.com/findumars/p/9000371.html 简要介绍编写C/C ++应用程序的领域,标准库的作用以及它是如何在各种操作系统中实现的.我已 ...

  7. CUDA 9中张量核(Tensor Cores)编程

    CUDA 9中张量核(Tensor Cores)编程 Programming Tensor Cores in CUDA 9 一.概述 新的Volta GPU架构的一个重要特点是它的Tensor核,使T ...

  8. 用NVIDIA Tensor Cores和TensorFlow 2加速医学图像分割

    用NVIDIA Tensor Cores和TensorFlow 2加速医学图像分割 Accelerating Medical Image Segmentation with NVIDIA Tensor ...

  9. UNIX环境高级编程笔记之标准I/O库

    一.总结 文件I/O一章讲了不带缓冲的I/O,本章讲的是带缓冲的I/O.不带缓冲针对的是内核的系统调用,而带缓冲针对的是用户空间的标准库函数,是基于带缓冲的I/O实现的.不带缓冲的I/O通过文件描述符 ...

随机推荐

  1. 简谈Mysql悲观锁和乐观锁

    悲观锁: 其实理解起来非常简单,当数据被外界修改持保守态度,包括自身系统当前的其他事务,以及来自外部系统的事务处理,因此,在整个数据处理过程中,将数据处于锁定状态.悲观锁的实现,往往依靠数据库提供的锁 ...

  2. 从苏宁电器到卡巴斯基(后传)第05篇:聊聊我对WannaCry产生的感慨

    这几天看到网上对WannaCry勒索病毒讨论得沸沸扬扬,不免有些感触. 其实该病毒的这次爆发,完全可以类比N年前"熊猫烧香"爆发的情况.也就是国内杀软纷纷歇菜,让本来就没什么技术含 ...

  3. adbi学习:java hook实现机制

    adbi的java hook实现代码ddi不在之前下载的文件中,下载地址:https://github.com/crmulliner/ddi,具体的编译看readme里面很详细的介绍了.注意ddi代码 ...

  4. CVE-2012-0003:Microsoft Windows Media Player winmm.dll MIDI 文件堆溢出漏洞调试分析

    0x01 蜘蛛漏洞攻击包 前言:2012 年 2月,地下黑产中流行着一款国产名为蜘蛛漏洞的攻击包 -- "Zhi-Zhu Exploit Pack",该工具包含 5 个漏洞,都是在 ...

  5. Win64 驱动内核编程-3.内核里使用内存

    内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...

  6. AsSystemRum 系统提权工具 实现思路及其源码

    名字:     AsSystemRun 功能:     用system权限启动一个进程. 开发语言:   C++,C# 作者:     Ack-Code 开发时间: 2016.9.15 实现原理: w ...

  7. Wampserver-删除虚拟主机

    对hosts操作 到目录C:\Windows\System32\drivers\etc中修改hosts 比如你想删除iwh2.com 选中这2行进行删除,保存退出 对httpd-vhosts操作 到目 ...

  8. 为什么数字被int格式化后依旧可以用%s占位(勉强已答)

    为什么数字被int格式化后依旧可以用%s占位 答:可以看作str(obj)

  9. Linux单设备多路USB串口的实现方法介绍

    某设备需要提供多路USB串口的功能给主机端使用,比如一路用作业务1通信功能,一路用作业务2通信功能,一路用作debug抓log用途,诸如此类.如下图所示. 要实现上述设备功能,可以参考如下步骤. 1) ...

  10. 4.启动虚拟机 设置CentOS7

    启动虚拟机 CentOS设置 1.点击箭头方向即可启动我们的VMware 2.设置语言 在第一步设置完成后,我们一直等待,即可来到语言设置界面 此处我们设置[中文] 3.设置安装信息 将下面带有[感叹 ...