第1 章GPU 硬件与CUDA 程序开发工具 1.1 GPU 硬件简介 GPU是英文graphicsprocessingunit的首字母缩写,意为图形处理器。GPU也常被称为显卡(graphicscard)。与它对应的一个概念是CPU,即centralprocessingunit(中央处理器)的首字母缩写。 从十多年前起,GPU的浮点数运算峰值就比同时期的CPU高一个量级;GPU的内存带宽峰值也比同时期的CPU高一个量级。CPU和GPU的显著区别如下:一块典型的CPU拥有少数几个快速的计算核心,而一块典型的GPU拥有几百到几千个不那么快速的计算核心。CPU中有更多的晶体管用于数据缓存和流程控制,但GPU中有更多的晶体管用于算术逻辑单元。所以,GPU是靠众多的计算核心来获得相对较高的计算性能的。图1.1形象地说明了(非集成)GPU和CPU在硬件架构上的显著区别。 图1.1CPU和非集成GPU的硬件架构示意图 (a) CPU; (b) GPU CPU和GPU中都有DRAM(dynamicrandomaccessmemory,动态随机存取存储器),它们一般由PCIe总线(peripheralcomponentinterconnectexpressbus)连接。 GPU计算不是指单独的GPU计算,而是指CPU+GPU的异构(heterogen-eous)计算。一块单独的GPU是无法独立地完成所有计算任务的,它必须在CPU CUDA 编程:基础与实践 | 的调度下才能完成特定任务。在由CPU和GPU构成的异构计算平台中,通常将起控制作用的CPU称为主机(host),将起加速作用的GPU称为设备(device)。主机和(非集成)设备都有自己的DRAM,它们之间一般由PCIe总线连接,如图1.1所示。 本书中说的GPU都是指英伟达(Nvidia)公司推出的GPU,因为CUDA编程目前只支持该公司的GPU。以下几个系列的GPU都支持CUDA编程。 (1)Tesla系列:其中的内存为纠错内存(error-correctingcodememory,ECC内存),稳定性好,主要用于高性能、高强度的科学计算。 (2)Quadro系列:支持高速OpenGL(opengraphicslibrary)渲染,主要用于专业绘图设计。 (3)GeForce系列:主要用于游戏与娱乐,但也常用于科学计算。GeForce系列的GPU没有纠错内存,用于科学计算时具有一定的风险。然而,GeForce系列的GPU价格相对低廉、性价比高,用于学习CUDA编程是没有任何问题的。即使是便携式计算机中GeForce系列的GPU也可以用来学习CUDA编程。 (4)Jetson系列:嵌入式设备中的GPU。作者对此无使用经验,本书也不专门讨论。 每一款GPU都有一个用以表示其“计算能力”(computecapability)的版本号。该版本号可以写为形如X.Y的形式。其中,X表示主版本号,Y表示次版本号。版本号决定了GPU硬件所支持的功能,可为应用程序在运行时判断硬件特征提供依据。初学者往往误以为GPU的计算能力越高,性能就越高,但后面我们会看到,计算能力和性能没有简单的正比关系。 版本号越大的GPU架构(architecture)越新。主版本号与GPU的核心架构相关联。英伟达公司选择用著名科学家(到目前为止,大部分是物理学家)的姓氏作为GPU核心架构的代号,见表1.1。在主版本号相同时,具有较大次版本号的GPU的架构稍有更新。例如,同属于开普勒(Kepler)架构的TeslaK40和TeslaK80这两款GPU有相同的主版本号(X=3),但有不同的次版本号,它们的计算能力分别是3.5和3.7。注意:特斯拉(Tesla)既是第一代GPU架构的代号,也是科学计算系列GPU的统称,其具体含义要根据上下文确定。另外,计算能力为7.5的架构虽然和伏特(Volta)架构具有同样的主版本号(X=7),但它一般被看作一个新的主要架构,代号为图灵(Turing)。表1.2列出了不同架构的各种GPU的名称。 特斯拉架构和费米(Fermi)架构的GPU已不再受到最近几个CUDA版本的支持。本书将忽略任何特定于这两个架构的硬件功能。可以预见,开普勒架构的GPU也将很快(如一两年后)不受最新版CUDA的支持。为简洁起见,本书有时也将忽略某些开普勒架构的特征。为简单起见,我们在表1.2中忽略了一类被称 第1章GPU硬件与CUDA程序开发工具 | 3 为Titan的GPU。读者可以在如下网站查询任何一款支持CUDA的GPU的信息:http://developer.nvidia.com/cuda-gpus。 表1.1各个GPU主计算能力的架构代号与发布年份 主计算能力 架构代号 发布年份 X = 1 特斯拉(Tesla) 2006 X = 2 费米(Fermi) 2010 X = 3 开普勒(Kepler) 2012 X = 5 麦克斯韦(Maxwell) 2014 X = 6 帕斯卡(Pascal) 2016 X = 7 伏特(Volta) 2017 X.Y=7.5 图灵(Turing) 2018 表1.2当前常用的各种GPU的名称 架构 Tesla系列 Quadro 系列 GeForce系列 Jetson 系列 开普勒 TeslaK系列 Quadro K 系列 GeForce600/700系列 Tegra K1 麦克斯韦 TeslaM系列 Quadro M 系列 GeForce900系列 Tegra X1 帕斯卡 TeslaP系列 Quadro P 系列 GeForce1000系列 Tegra X2 伏特 TeslaV系列 无 无 AGXXavier 图灵 TeslaT系列 QuadroRTX系列 GeForce2000系列 AGXXavier 注:特斯拉架构和费米架构的GPU已经不再受到最新CUDA的支持,故没有列出。 计算能力并不等价于计算性能。例如,GeForceRTX2000系列的计算能力高于TeslaV100,但后者在很多方面性能更高(售价也高得多)。 表征计算性能的一个重要参数是浮点数运算峰值,即每秒最多能执行的浮点数运算次数(.oating-pointoperationspersecond,FLOPS)。GPU的浮点数运算峰值在1012 FLOPS,即teraFLOPS(简写为TFLOPS)的量级。浮点数运算峰值有单精度和双精度之分。对Tesla系列的GPU来说,双精度浮点数运算峰值一般是单精度浮点数运算峰值的1/2左右(对计算能力为3.5和3.7的GPU来说,是1/3左右)。对GeForce系列的GPU来说,双精度浮点数运算峰值一般是单精度浮点数运算峰值的1/32左右。另一个影响计算性能的参数是GPU中的内存带宽 (memorybandwidth)。GPU中的内存常称为显存。显存容量也是制约应用程序性能的一个因素。如果一个应用程序需要的显存数量超过了一个GPU的显存容量,则在不使用统一内存(见第12章)的情况下程序就无法正确运行。表1.3列出了作者目前能够使用的几款GPU的主要性能指标。在浮点数运算峰值一栏中,括号前和括号中的数字分别对应双精度和单精度的情形。 CUDA 编程:基础与实践 | 表1.3若干GPU的主要性能指标 GPU 型号 计算能力 显存容量/GB 显存带宽/(GB/s) 浮点数运算峰值/TFLOPS Tesla K40 3.5 12 288 1.4 (4.3) Tesla P100 6.0 16 732 4.7 (9.3) Tesla V100 7.0 32 900 7 (14) GeForceRTX2070 7.5 8 448 0.2 (6.5) GeForceRTX2080ti 7.5 11 616 0.4 (13) 1.2 CUDA 程序开发工具 以下几种软件开发工具都可以用来进行GPU编程。 (1) CUDA 。这是本书的主题。 (2)OpenCL。这是一个更为通用的为各种异构平台编写并行程序的框架,也是AMD(AdvancedMicroDevices)公司的GPU的主要程序开发工具。本书不涉及OpenCL编程,对此感兴趣的读者可参考《OpenCL异构并行计算:原理、机制与优化实践》(刘文志,陈轶,吴长江,北京:机械工业出版社)。 (3)OpenACC。这是一个由多个公司共同开发的异构并行编程标准。本书也不涉及OpenACC编程,对此感兴趣的读者可参考《OpenACC并行编程实战》(何沧平,北京:机械工业出版社)。 CUDA编程语言最初主要是基于C语言的,但目前越来越多地支持C++语言。另外,还有基于Fortran的CUDAFortran版本及由其他编程语言包装的CUDA版本,但本书只涉及基于C++的CUDA编程。我们称基于C++的CUDA编程语言为CUDAC++。对Fortran版本感兴趣的读者可以参考网站https://www.pgroup.com/中的资源。用户可以免费下载支持CUDAFortran编程的PGI开发工具套装的社区版本(communityedition)。对应的还有收费的专业版本(professionaledition)。PGI是高性能计算编译器公司PortlandGroup,Inc.的简称,已被英伟达公司收购。 CUDA提供了两层API(applicationprogramminginterface,应用程序编程接口)供程序员使用,即CUDA驱动(driver)API和CUDA运行时(runtime)API。其中,CUDA驱动API是更加底层的API,它为程序员提供了更为灵活的编程接口;CUDA运行时API是在CUDA驱动API的基础上构建的一个更为高级的API,更容易使用。这两种API在性能上几乎没有差别。从程序的可读性来看,使用CUDA运行时API是更好的选择。在其他编程语言中使用CUDA时,驱动API很多时候是必需的。因为作者没有使用驱动API的经验,故本书只涉及CUDA运 第1章GPU硬件与CUDA程序开发工具 | 5 行时API。 图1.2展示了CUDA开发环境的主要组件。开发的应用程序是以主机(CPU)为出发点的。应用程序可以调用CUDA运行时API、CUDA驱动API及一些已有的CUDA库。所有这些调用都将利用设备(GPU)的硬件资源。对CUDA运行时API的介绍是本书大部分章节的重点内容;第14章将介绍若干常用的CUDA库。 图1.2CUDA编程开发环境概览 CUDA版本也由形如X.Y的两个数字表示,但它并不等同于GPU的计算能力。可以这样理解:CUDA版本是GPU软件开发平台的版本,而计算能力对应着GPU硬件架构的版本。 最早的CUDA1.0于2007年发布。当前(笔者交稿之日)最新的版本是CUDA 10.2。CUDA版本与GPU的最高计算能力都在逐年上升。虽然它们之间没有严格的对应关系,但一个具有较高计算能力的GPU通常需要一个较高的CUDA版本才能支持。最近的几个CUDA版本对GPU计算能力的支持情况见表1.4。一般来说,建议安装一个支持所用GPU的较新的CUDA工具箱。本书中的所有示例程序都可以在CUDA9.0~10.2中进行测试。目前最新版本的CUDA10.2有两个值得注意的地方。第一,它是最后一个支持MacOS的CUDA版本;第二,它将CUDAC改名为CUDAC++,用以强调CUDAC++是基于C++的扩展。 表1.4最近的几个CUDA版本对GPU计算能力的支持情况 CUDA 版本 所支持GPU的计算能力 架构 10.0~10.2 3.0~7.5 从开普勒到图灵 9.0~9.2 3.0~7.2 从开普勒到伏特 8.0 2.0~6.2 从费米到帕斯卡 7.0~7.5 2.0~5.3 从费米到麦克斯韦 CUDA 编程:基础与实践 | 1.3 CUDA 开发环境搭建示例 下面叙述作者最近在装有GeForceRTX2070的便携式计算机(以下简称计算机)中搭建CUDA开发环境的大致过程。因为作者的计算机中预装了Windows10操作系统,所以我们以Windows10操作系统为例进行讲解。因为Linux发行版有多种,故本书不列出在Linux中安装CUDA开发环境的步骤。读者可参阅英伟达公司的官方文档:https://docs.nvidia.com/cuda/cuda-installation-guide-linux。 我们说过,GPU计算实际上是CPU+GPU(主机+设备)的异构计算。在 CUDA C++ 程序中,既有运行于主机的代码,又有运行于设备的代码。其中,运行 于主机的代码需要由主机的C++编译器编译和链接。所以,除安装CUDA工具箱 外,还需要安装一个主机的C++编译器。在Windows中,最常用的C++编译器是 MicrosoftVisualC++(MSVC),它目前集成在VisualStudio中,所以我们首先安装 VisualStudio。作者安装了最高版本的VisualStudio201916.x。因为这是个人使用 的,故选择了免费的Community版本。下载地址为https://visualstudio.microsoft. com/free-developer-o.ers/。对于CUDAC++程序开发来说,只需要选择安装Desk- topdevelopmentwithC++即可。当然,读者也可以选择安装更多的组件。 关于CUDA,作者选择安装2019年8月发布的CUDAToolkit10.1update2。 首先,进入网址https://developer.nvidia.com/cuda-10.1-download-archive-update2。 然后,根据提示,做如下选择:OperatingSystem项选择Windows;Architecture项 选择x86_64;Version项选择操作系统版本,我们这里是10;InstallerType项可以 选择exe(network)或者exe(local),分别代表一边下载一边安装和下载完毕后安 装。最后,运行安装程序,根据提示一步一步安装即可。该版本的CUDA工具箱包 含一个对应版本的Nvidiadriver,故不需要再单独安装Nvidiadriver。 安装好VisualStudio和CUDA后,进入如下目录(读者如果找不到C盘下的ProgramData目录,可能是因为没有选择显示一些隐藏的文件): C:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.1\1_Utilities \deviceQuery。用VisualStudio2019打开文件deviceQuery_vs2019.sln,编译(构建)、运行即可。若输出内容的最后部分为Result=PASS,则说明已经搭建好Windows中的CUDA开发环境。若有疑问,请参阅英伟达公司的官方文档:https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows。 在上面的测试中,我们是直接用VisualStudio打开一个已有的解决方案(solution),然后直接构建并运行。本书不介绍VisualStudio的使用,而是选择用命令行解释器编译与运行程序。这里的命令行解释器指的是Linux中的termi- 第1章GPU硬件与CUDA程序开发工具7 | nal或者Windows中的commandprompt程序。在Windows中使用MSVC作为C++程序的编译器时,需要单独设置相应的环境变量,或者从Windows的“开始”(start)菜单中找到VisualStudio2019文件夹,然后单击其中的“x64NativeToolsCommandPromptforVS2019”,从而打开一个加载了MSVC环境变量的命令行解释器。在本书的某些章节,需要有管理员的权限来使用nvprof性能分析器。此时,可以右击“x64NativeToolsCommandPromptforVS2019”,在弹出的快捷菜单中选择“更多”→ “以管理员身份运行”。 用命令行解释器编译与运行CUDA程序的方式在Windows和Linux系统几乎没有区别,但为了简洁起见,本书后面主要以Linux开发环境为例进行讲解。虽然如此,Windows和Linux中的CUDA编程功能还是稍有差别。我们将在后续章节中适当的地方指出这些差别。 1.4 用nvidia-smi检查与设置设备 可以通过nvidia-smi(Nvidia’ssystemmanagementinterface)程序检查与设置设备。它包含在CUDA开发工具套装内。该程序最基本的用法就是在命令行解释器中使用不带任何参数的命令nvidia-smi。在作者的计算机中使用该命令,得到如下文本形式的输出: +-----------------------------------------------------------------------------+ | NVIDIA-SMI 426.00 Driver Version: 426.00 CUDA Version: 10.1 | |-------------------------------+----------------------+----------------------+ | GPU Name TCC/WDDM | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. | |===============================+======================+======================| | 0 GeForce RTX 207... WDDM | 00000000:01:00.0 Off | N/A | | N/A 38C P8 12W / N/A | 161MiB / 8192MiB | 0% Default | +-------------------------------+----------------------+----------------------+ +-----------------------------------------------------------------------------+ | Processes: GPU Memory | | GPU PID Type Process name Usage | |=============================================================================| | No running processes found | +-----------------------------------------------------------------------------+ 从中可以看出一些比较有用的信息: (1)第一行可以看到Nvidiadriver的版本及CUDA工具箱的版本。 (2)作者所用计算机中有一型号为GeForceRTX2070的GPU。该GPU的设备号是0。该计算机仅有一个GPU。如果有多个GPU,会将各个GPU从0开始 CUDA 编程:基础与实践 | 编号。如果读者的系统中有多个GPU,而且只需要使用某个特定的GPU(如两个之中更强大的那个),则可以通过设置环境变量CUDA_VISIBLE_DEVICES的值在运行CUDA程序之前选定一个GPU。假如读者的系统中有编号为0和1的两个GPU,而读者想在1号GPU运行CUDA程序,可以用如下命令设置环境变量: $ export CUDA_VISIBLE_DEVICES=1 这样设置的环境变量在当前shellsession及其子进程中有效。 (3)该GPU处于WDDM(Windowsdisplaydrivermodel)模式。另一个可能的模式是TCC(Teslacomputecluster),但它仅在Tesla、Quadro和Titan系列的GPU中可选。可用如下方式选择(在Windows中需要用管理员身份打开CommandPrompt并去掉命令中的sudo): $sudonvidia-smi-gGPU_ID-dm0#设置为WDDM模式 $sudonvidia-smi-gGPU_ID-dm1#设置为TCC模式 这里,GPU_ID是GPU的编号。 (4)该GPU当前的温度为38℃。GPU在满负荷运行时,温度会高一些。 (5)这是GeForce系列的GPU,没有ECC内存,故Uncorr.ECC为N/A,代表不适用(notapplicable)或者不存在(notavailable)。 (6)ComputeM.指计算模式(computemode)。该GPU的计算模式是Default。在默认模式中,同一个GPU中允许存在多个计算进程,但每个计算进程对应程序的运行速度一般来说会降低。还有一种模式为E.Process,指的是独占进程模式 (exclusiveprocessmode),但不适用于处于WDDM模式的GPU。在独占进程模式下,只能运行一个计算进程独占该GPU。可以用如下命令设置计算模式(在Windows中需要用管理员身份打开CommandPrompt并去掉命令中的sudo): $sudonvidia-smi-iGPU_ID-c0#默认模式$sudonvidia-smi-iGPU_ID-c1#独占进程模式这里,-iGPU_ID的意思是希望该设置仅仅作用于编号为GPU_ID的GPU;如 果去掉该选项,该设置将会作用于系统中所有的GPU。 关于nvidia-smi程序的更多介绍,请参考如下官方文档:https://developer.nvidia.com/nvidia-system-management-interface。 1.5 其他学习资料 本书将循序渐进地带领读者学习CUDAC++编程的基础知识。虽然作者力求本书知识全面、内容系统,但读者在阅读本书的过程中同时参考一些其他的学习资料也是有好处的。 第1章GPU硬件与CUDA程序开发工具 | 9 任何关于CUDA编程的书籍都不可能替代官方提供的手册等资料。以下是几个重要的官方文档,请读者在有一定的基础之后务必查阅。限于作者水平,本书难免存在不当之处。当读者觉得本书中的个别论断与官方资料有冲突时,当以官方资料为标准(官方手册的网址为https://docs.nvidia.com/cuda)。在这个网站,包括但不限于以下几个方面的文档: (1)安装指南(installationguides)。读者遇到与CUDA安装有关的问题时,应该仔细阅读此处的文档。 (2)编程指南(programmingguides)。该部分有很多重要的文档:1)最重要的文档是《CUDAC++ProgrammingGuide》,网址为https://docs.nvidia.com/cuda/cuda-c-programming-guide。2)另一个值得一看的文档是《CUDAC++BestPracticesGuide》,网址为https: //docs.nvidia.com/cuda/cuda-c-best-practices-guide。3)针对最近的几个GPU架构进行优化的指南,网址为 ① https://docs.nvidia.com/cuda/kepler-tuning-guide 。 ② https://docs.nvidia.com/cuda/maxwell-tuning-guide 。 ③ https://docs.nvidia.com/cuda/pascal-tuning-guide 。 ④ https://docs.nvidia.com/cuda/volta-tuning-guide 。 ⑤ https://docs.nvidia.com/cuda/turing-tuning-guide 。 这几个简短的文档可以帮助有经验的用户迅速了解一个新的架构。 (3)CUDAAPI手册(CUDAAPIreferences)。这里有1)CUDA运行时API的手册:https://docs.nvidia.com/cuda/cuda-runtime- api。2)CUDA驱动API的手册:https://docs.nvidia.com/cuda/cuda-driver-api。 3)CUDA数学函数库API的手册:https://docs.nvidia.com/cuda/cuda-math- api。 4)其他若干CUDA库的手册。 为明确起见,在撰写本书时,作者参考的是与CUDA10.2对应的官方手册。