

自动驾驶、智能机器人、新材料发现、医药科学、医学影像分析...人工智能时代的科学研究极度依赖算力支持。提供算力的硬件芯片厂商中,首屈一指当属英伟达。如果不聊几句GPU和英伟达,都不好意思跟别人说自己是做人工智能的,而在人工智能领域,获得英伟达的相关认证,也是业界备受瞩目的事情,这代表了行业上游对技术、架构、赛道的肯定。
那么,英伟达的GPU是如何加速计算呢?本系列将介绍GPU计算加速的一些基础知识:
GPU全名为Graphics Processing Unit,又称视觉处理器、图形显示卡。GPU负责渲染出2D、3D、VR效果,主要专注于计算机图形图像领域。
众所周知,由于存储器的发展慢于处理器,在 CPU 上发展出了多级高速缓存的结构,如上面左图所示。而在 GPU 中,也存在类似的多级高速缓存结构。只是相比 CPU,GPU 将更多的晶体管用于数值计算,而不是缓存和流控(Flow Control)。这源于两者不同的设计目标,CPU 的设计目标是并行执行几十个线程,而 GPU 的目标是要并行执行几千个线程。
可以在上面右图看到,GPU 的 Core 数量要远远多于 CPU,但是有得必有失,可以看到 GPU 的 Cache 和 Control 要远远少于 CPU,这使得 GPU 的单 Core 的自由度要远远低于 CPU,会受到诸多限制,而这个限制最终会由程序承担。这些限制也使得 GPU 编程与 CPU 多线程编程有着根本区别。
这其中最根本的一个区别可以在上右图中看出,每一行有多个 Core,却只有一个 Control,这代表着多个 Core 同一时刻只能执行同样的指令,这种模式也称为 SIMT (Single Instruction Multiple Threads). 这与现代 CPU 的 SIMD 倒是有些相似,但却有根本差别,本文在后面会深入细究。
从 GPU 的架构出发,我们会发现,因为 Cache 和 Control 的缺失,只有计算密集与数据并行的程序适合使用 GPU。
计算密集:数值计算的比例要远大于内存操作,因此内存访问的延时可以被计算掩盖,从而对 Cache 的需求相对 CPU 没那么大。
数据并行:大任务可以拆解为执行相同指令的小任务,因此对复杂流程控制的需求较低。
而深度学习恰好满足以上两点,即使存在比深度学习计算量更低且表达能力更强的模型,但如果不满足以上两点,都势必打不过 GPU 加持下的深度学习。
1962年麻省理工学院的博士伊凡•苏泽兰发表的论文以及他的画板程序奠定了计算机图形学的基础。在随后的近20年里,计算机图形学在不断发展,但当时的计算机却没有配备专门的图形处理芯片,图形处理任务都是CPU来完成的。
1984年,SGI公司推出了面向专业领域的高端图形工作站,才有了专门的图形处理硬件,俗称图形加速器。所开发的图形系统引入了许多经典的概念,比如顶点变换和纹理映射。在随后的10年里,SGI又不断研发出了一系列性能更好的图形工作站。但是,由于价格非常的昂贵,在消费级市场很难获得普及,用户非常小众化。而这段时期,在消费级领域,还没有专门的图形处理硬件推出,只有一些2D加速卡。
1995年,3dfx公司发布了消费级领域史上第一款3D图形加速卡voodoo,这也是第一款线D显卡。随后的几年,AMD公司和ATI(2006年被AMD公司收购)公司分别发布了自己的TNT系列与Rage系列显卡。它们已经从硬件上实现了Z缓存和双缓存,可以进行光栅化之类的操作,同时也实现了DirectX 6的特征集。CPU终于从繁重的像素填充任务中解脱出来。当然,由于当时的技术不成熟,顶点变换还是必须在CPU中完成,光栅化之后的像素操作也很有限。
1999年8月,NVIDIA公司发布了一款代号为NV10的图形芯片Geforce 256。Geforce 256是图形芯片领域开天辟地的产品,因为它是第一款提出GPU概念的产品。Geforce 256所采用的核心技术有“T&L”硬件、立方环境材质贴图和顶点混合、纹理压缩和凹凸映射贴图、双重纹理四像素256位渲染引擎等。“T&L”硬件的出现,让显示芯片具备了以前只有高端工作站才有的顶点变换能力,同时期的OpenGL和DirectX 7都提供了硬件顶点变换的编程接口。GPU的概念因此而出现。
2001年微软公司发布DirectX 8,在这一版本中包含了Shader Model(优化渲染引擎模式)1.0标准。遵循Shader Model的GPU可以具备顶点和像素的可编程性,从此微软开始引领图形硬件标准。同年,NVIDIA发布了Geforce3,ATI发布了Radeon 8500,这两种GPU都支持顶点编程,可以通过应用程序指定指令序列来处理顶点。不过遗憾的是,这一时期的GPU都不支持像素编程,只是提供了简单的配置功能。
2002年底,微软发布了DirectX9.0b,Shader Model更新到2.0版本,让shader成为了其标准配置。2003年,发布的OpenGL1.4中也正式提供了对GPU的编程接口规范。从2003年开始,NVIDIA和ATI发布的新产品都同时具备了可编程顶点处理和可编程像素处理器,具备了良好的可编程性。从此,开发人员可以根据自己的需要灵活的控制渲染过程,编程时无需再过度关注于GPU的其他硬件特性,重点关注可编程性即可。从此,GPU又多了一个可编程的属性,也叫做可编程图形处理单元。
2006年,包含与DirectX10的 Shader Model4.0发布,这一版本的Shader Model采用了统一渲染架构,不再提供单独的可编程顶点处理器和可编程像素处理器,而是使用统一的流处理器。传统的架构中,两类处理器很难实现负载均衡,工作效率不高。在统一渲染架构下,流处理器可以执行不同的渲染指令,工作效率大幅提升,同时便于GPU开始由单纯的渲染转向通用计算领域。Shader Model4.0还扩充了几何编程这一概念,主要用于快速处理几何图元创造新的多边形。这一时期,比较有代表性的GPU有NVIDIA的Geforce 9600和ATI的Radeon 3850。
直至2011年至今,以英伟达为主的CUDA、OpenCL1.2~2.0相关标准的通用计算大爆发,2012 年 NVIDIA 发布了 Kepler 架构、2014 年 NVIDIA 发布了 Maxwell 架构、2016年的Pascal 帕斯卡架构以 、2017年发布Volta 伏特架构 、2018年发布Turing 图灵架构、2020年发布Ampere安培架构系列,都是随着每一年从物理(半导体技术)、架构、驱动程序与开发环境的不断优化,科学家以及AI开发者认为GPU非常适合并行计算,可以加速现代科学计算,GPU也因此随即开启了人工智能时代的浪潮。
无论是CPU还是GPU,在进行计算时,都需要用核心(Core)来做算术逻辑运算,比如加减乘。核心中有ALU(逻辑运算单元)和寄存器等电路。在进行计算时,一个核心只能顺序执行某项任务。所以为了同时并行地处理更多任务,芯片公司开发出了多核架构,只要相互之间没有依赖,每个核心做自己的事情,多核之间互不干扰,就可以达到并行计算的效果,极大缩短计算时间。
个人桌面电脑CPU一般只有2到8个CPU核心,数据中心的服务器上也只有20到40个左右CPU核心,GPU却有上千个核心。与CPU的核心不同,GPU的核心只能专注于某些特定的任务。知乎上有人把CPU比作大学教授,把GPU比作一个学校几千个小学生:同样是做加减法,几千个小学生所能做的计算,远比几十个大学教授要多得多。俗话说,三个臭皮匠,顶一个诸葛亮。大学教授的知识结构和个人能力远强于小学生,能独立解决复杂问题,小学生的知识有限,只能进行简单的计算。目前来看GPU在处理简单计算任务上有更大的优势,但是主要还是靠人海战术,并不能像CPU那样可以独当一面,短时间内也无法替换掉CPU。如下图所示,在整个计算机系统中,CPU起到协调管理的作用,管理计算机的主存、硬盘、网络以及GPU等各类元件。
如果只关注CPU和GPU,那么计算结构将如下图所示。CPU主要从主存(Main Memory)中读写数据,并通过总线(Bus)与GPU交互。GPU除了有超多计算核心外,也有自己独立的存储,被称之为显存。一台服务器上可以安装多块GPU卡,但GPU卡的发热量极大,普通的空调系统难以给大量GPU卡降温,所以大型数据中心通常使用水冷散热,并且选址在温度较低的地方。
GPU核心在做计算时,只能直接从显存中读写数据,程序员需要在代码中指明哪些数据需要从内存和显存之间相互拷贝。这些数据传输都是在总线上,因此总线的传输速度和带宽成了部分计算任务的瓶颈。也因为这个瓶颈,很多计算任务并不适合放在GPU上,比如笔者这两年关注的推荐系统虽然也在使用深度学习,但因为输入是大规模稀疏特征,GPU加速获得的收益小于数据互相拷贝的时间损失。当前最新的总线技术是NVLink,IBM的Power CPU和英伟达的高端显卡可以通过NVLink直接通信。同时,单台机器上的多张英伟达显卡也可以使用NVLink相互通信,适合多GPU卡并行计算的场景。
Fermi 是 Nvidia 在 2010 年发布的架构,引入了很多今天也仍然不过时的概念,而比 Fermi 更早之前的架构,小编也已经找不到太多资料了,所以本文从 Fermi 开始,先来一张总览。
Thread Block: 一个 Thread Block 包含多个线程(比如几百个),多个 Blocks 之间的执行完全独立,硬件可以任意调度多个 Block 间的执行顺序,而 Block 内部的多个线程执行规则由程序员决定,程同时程序员可以决定一共有多少个 Blocks;
由于本文不是讲怎么写 CUDA,所以如果对 SM/Block 的解释仍然不明白,可以参考这一小节:
之前一直提到 Warp,但之前只说明了是 32 个线程,我们在这里终于开始详细说明,首先来看 Dual Warp Scheduler 的概览。
这里引入了一个核心的约束,任意时刻,一个 Warp 里的 Thread 都在执行同样的指令,对于程序员来说,观测不到一个 warp 里不同 thread 的不同执行情况。
可以看上图,当发生分支时,只会执行进入该分支的线程,如果进入该分支的线程少,则会发生资源浪费。
可以看到,首先 SM 改名成了 SMX,但是所代表的概念没有大变化,我们先看看 SMX 的内部:
可以看到,这次的 SM 改叫 SMM 了,Core 更多了,也更强大了,这里就不过多介绍了。
可以看到,还是一如既往地增加了很多 Cores, 我们细看 SM 内部:
随着单 GPU 的计算能力越来越难以应对深度学习对算力的需求,人们自然而然开始用多个 GPU 去解决问题。从单机多 GPU 到多机多 GPU,这当中对 GPU 互连的带宽的需求也越来越多。多机之间,采用 InfiniBand 和 100Gb Ethernet 去通信,在单机内,特别是从单机单 GPU 到达单机 8GPU 以后,PCIe 的带宽往往就成为了瓶颈。为了解决这个问题,NVIDIA 提供了 NVLink 用以单机内多 GPU 内的点到点通信,带宽达到了 160GB/s, 大约 5 倍于 PCIe 3 x 16. 下图是一个典型的单机 8 P100 拓扑。
和 Pascal 的改变类似,到了 Volta,直接拆了 4 个区块,每个区块多配了一个 L0 指令缓存,而 Shared Memory/Register File 这都没有变少,也就和 Pascal 的改变一样,单个线程可使用的资源更多了。单个区块还多个两个名为 Tensor Core 的单元,这就是这个版本的核心了。可以吐槽一下,这个版本又把 L1 和 Shared Memory 合并了。
在这个版本中,另一个重要更新是 NVLink, 简单来说就是更多更快。每个连接提供双向各自 25GB/s 的带宽,并且一个 GPU 可以接 6 个 NVLink,而不是 Pascal 时代的 4 个。一个典型的拓扑如下图:
从 Volta 开始,线程调度发生了变化,在 Pascal 以及之前的 GPU 上,每个 Warp 里的 32 个线程共享一个 Program Counter (简称 PC) ,并且使用一个 Active Mask 表示任意时刻哪些线程是可运行的,一个经典的运行如下:
直到第一个分支完整结束,才会执行另一个分支。这意味着同一个 warp 内不同分支失去了并发性,不同分支的线程互相无法发送信号或者交换数据,但同时,不同 warp 之间的线程又保留了并发性,这当中的线程并发存在着不一致,事实上如果程序员不注意这点,很可能导致死锁。
在 Volta 中解决了这个问题,同 warp 内的线程有独立的 PC 和栈,如下:
由于运行时仍然要符合 SIMT,所以存在一个调度优化器负责将可运行的线程分组,使用 SIMT 模式执行。经典运行如下:
上图可以注意到,Z 的执行并没有被合并,这是因为 Z 可能会产生一些被其他分支需要的数据,所以调度优化器只有在确定安全的情况下才会合并 Z,所以上图 Z 未合并只是一种情况,一般来说,调度优化器足够聪明可以发现安全的合并。程序员也可以通过一个 API 来强制合并,如下:
从 Volta 开始,提高了对多进程并发使用 GPU 的支持。在 Pascal 及之前,多个进程对单一 GPU 的使用是经典的时间片方式。从 Volta 开始,多个用不满 GPU 的进程可以在 GPU 上并行,如下图:
2018 年 NVIDIA 发布了 Turing 架构,个人认为是 Volta 的延伸版本,当然首先各种参数加强,不过我们这里就不提参数加强了。
比较重要是的增加了一个 RT Core,全名是 Ray Tracing Core, 顾名思义,这个是给游戏或者仿真用的,因为本人没有从事过这类工作,就不介绍了。
FP16 的问题在于表示范围不够大,在梯度计算时容易出现 underflow, 而且前后向计算也相对容易出现 overflow, 相对来说,在深度学习计算里,范围比精度要重要得多,于是有了 BF16,牺牲了精度,保持和 FP32 差不多的范围,在此前比较知名支持 BF16 的就是 TPU. 而 TF32 的设计,在于即汲取了 BF16 的好处,又保持了一定程度对主流 FP32 的兼容,FP32 只要截断就是 TF32 了。先截断成 TF32 计算,再转成 FP32, 对历史的工作几乎无影响,如下图:
另一个变化则是细粒度的结构化稀疏,深度学习模型压缩这个领域除了量化,稀疏也是一个大方向,只是稀疏化模型难以利用硬件加速,这个版本的 GPU 则为稀疏提供了一些支持,当前的主要目的则是应用于 Inference 场景。
首先说 NVIDIA 定义的稀疏矩阵,这里称为 2:4 的结构化稀疏,2:4 的意思是每 4 个元素当中有 2 个值非 0,如下图:
理想情况下具有和稠密 weight 一样的精确度,然后使用此稀疏化后的 weight 进行 Inference. 而这个版本的 TensorCore 支持一个 2:4 的结构化稀疏矩阵与另一个稠密矩阵直接相乘。
最后一个比较重要的特性就是 MIG(Multi-Instance GPU)了,虽然业界的计算规模确实越来越大,但也存在不少的任务因为其特性导致无法用满 GPU 导致资源浪费,所以存在需求在一个 GPU 上跑多个任务,在这之前有些云计算厂商会提供虚拟化方案。而在安培中,会为此需求提供支持,称为 MIG.
可能会有人有疑问,在 Volta 中引入的多进程支持不是解决了问题吗?举个例子,在 Volta 中,虽然多个进程可以并行,但是由于所有进程都可以访问所有的内存资源,可能存在一个进程把所有的 DRAM 带宽占满影响到其他进程的运行,而这些被影响的进程很可能有 Throughput/Latency 要求。所以我们需要更严格的隔离。
而在安培 MIG 中,每个 A100 可以被分为 7 个 GPU 实例被不同的任务使用。每个实例的 SMs 有独立的内存资源,可以保证每个任务有符合预期的稳定的 Throughput/Latency. 用户可以将这些虚拟的 GPU 实例当成真实的 GPU 使用。
Intel的CPU目前不支持NVLink,只能使用PCI-E技术,如下图所示。NVLink和PCI-E都是总线技术的一种。
由于CPU和GPU是分开的,在英伟达的设计理念里,CPU和主存被称为Host,GPU被称为Device。Host和Device概念会贯穿整个英伟达GPU编程。
以上结构也被称为异构计算:使用CPU+GPU组合来加速计算。世界上顶尖的数据中心和超级计算机均采用了异构计算架构。例如超越天河2号成为世界第一的超级计算机Summit使用了9216个IBM POWER9 CPU和27648个英伟达Tesla GPU。
英伟达能够在人工智能时代成功,除了他们在长期深耕显卡芯片领域,更重要的是他们率先提供了可编程的软件架构。2007年,英伟达发布了CUDA编程模型,软件开发人员从此可以使用CUDA在英伟达的GPU上进行并行编程。在此之前,GPU编程并不友好。CUDA简单到什么程度?有经验的程序员经过半天的培训,掌握一些基础概念后,能在半小时内将一份CPU程序修改成为GPU并行程序。
继CUDA之后,英伟达不断丰富其软件技术栈,提供了科学计算所必须的cuBLAS线性代数库,cuFFT快速傅里叶变换库等,当深度学习大潮到来时,英伟达提供了cuDNN深度神经网络加速库,目前常用的TensorFlow、PyTorch深度学习框架的底层大多基于cuDNN库。英伟达能在人工智能时代击败Intel、AMD等强大对手,很大一部分是因为它丰富的软件体系。这些软件工具库使研发人员专注于自己的研发领域,不用再去花大量时间学习GPU底层知识。CUDA对于GPU就像个人电脑上的Windows、手机上的安卓系统,一旦建立好生态,吸引了开发者,用户非常依赖这套软件生态体系。
GPU编程可以直接使用CUDA的C/C++版本进行编程,也可以使用其他语言包装好的库,比如Python可使用Numba库调用CUDA。慧维智能深耕多年动态医学影像AI技术,提供消化内镜辅助诊断产品內镜“帧探”。产品技术的解决方案也是基于英伟达CUDA技术;通过慧维智能资深医务团队收集、梳理海量“金标准”的数据,邀请高年资消化科医生精心标注数据集;AI技术帮助医生实现结肠疾病和结肠癌的早期诊断,并提供內镜手术中智能识别、标注、提醒和导航等功能。
慧维智能医疗科技有限公司成立于2019年6月,专业从事智能医疗产品的研发、生产与销售。我们的核心成员,均来自全球顶尖的科研机构和世界五百强企业。慧维智能以在“人工智能”和“边缘计算”领域的自主核心技术为驱动力,致力于为全球医疗机构提供“高水准、好体验”的医疗产品与服务,最大程度地帮助医生提高诊疗水平与效率。返回搜狐,查看更多