找回密码
 立即注册
查看: 199|回复: 0

图形处理器GPU

[复制链接]
发表于 2023-3-23 12:18 | 显示全部楼层 |阅读模式
GPU 全称是Graphic Processing Unit,即图形处理器,其最主要的功能是进行各种图形相关的数据的运算,图形的特点是每个像素处理所需的计算比较单一,但包含的像素点数量巨大,且对计算速度的要求很高。GPU就是针对图形处理的这一特性而设计的。
CPU包含的功能模块较多,大部分晶体管用在控制电路和Cache上,只有少部分晶体管用来完成运算工作,所以CPU擅长进行复杂的逻辑运算。GPU的控制相对简单,也不需要很大的Cache,所以可以将大部分晶体管用于各种专用计算电路和流水线设计,使其拥有强大的运算能力。如图所示,CPU拥有强大的控制单元(Control Unit),运算单元(ALU)和缓存(Cache),所以CPU可以进行较为复杂的调度任务和逻辑运算,更多的Cache则可以缓存更多的中间结果以提升后续计算的效率。GPU中的控制单元,运算单元和缓存都相对要弱很多,但运算单元的数量却更多。



CPU与GPU逻辑结构对比

传统的GPU都是基于SIMT(单指令多线程)架构设计的,调度执行模式也是按照SIMT模式实现的,所以GPU的逻辑控制单元基本上只处理简单的分发合并等操作,计算单元则可以批量处理大量的简单任务。GPU并不是一个完整的运行平台,它需要CPU来协助完成任务调度和逻辑控制等任务,而GPU则用来并行处理无数小型的计算任务,如图形渲染和训练神经网络模型等,所以GPU可以看成是CPU的协处理器。我们常说的GPU并行计算其实是指CPU+GPU的异构计算架构平台,GPU通过PCI-E总线连接与CPU协同工作,CPU称为主机端(host),GPU称为设备端(device)。
早期GPU的应用被局限在图形渲染上,随着GPU可编程能力的不断提高,GPU被用于更多的通用计算任务上,即 GPGPU (General-purpose computing on graphics processing units)。GPGPU通常也采用CPU+GPU的异构计算模式,由CPU负责复杂的任务调度和逻辑控制等不适合并行处理的计算,而由GPU负责密集的数据并行计算。这种模式可以利用GPU 强大的并行计算能力弥补CPU计算能力的不足,在生产制造和性价比上有明显的优势。但早期的GPGPU开发只能使用有限的图形学API编程,开发人员需要将计算数据打包成纹理数据,并将计算任务转化为纹理渲染过程,然后利用图形学API执行(如Direct3D、OpenGL等),因而限制了其应用的领域,也极大的增加了开发难度。
2007年6月,NVIDIA为GPU增加一个易用的编程接口Compute Unified Device Architecture (CUDA),即统一计算架构。CUDA为GPU作为数据并行计算提供了一种通用并行计算软硬件体系结构,包含CUDA指令集和GPU并行计算引擎,使开发人员可以充分利用GPU中的计算核心进行大量数据的并行计算。
CUDA为开发人员有效利用GPU强大的并行计算能力提供了条件,CUDA采用了比较容易掌握的类C语言进行软件开发,不需要借助于图形学API,开发人员也不必重新学习新的编程语法,只需要掌握并行算法和GPU架构方面的知识,就可以开发出高性能的GPU计算程序。CUDA被广泛应用于科学研究、天文计算、动力学模拟仿真、图像处理等领域,并获得了几倍、几十倍,甚至百倍以上的加速。
深度学习算法需要处理海量数据,对并行计算能力有较高的要求,因此GPU在AI训练过程中被广泛使用。以NVIDIA A100为例,其具备6192个核心,单精度浮点 (TF32) 计算能力达到19.5TFLOPS,借助Tensor Core单精度浮点运算能力156TFLOPS,若使用FP16/FP32混合精度,性能可进一步提升 312TFLOPS。
计算机架构

现代计算机主要使用2个芯片组来完成功能和设备的拓展:北桥(Northbridge)芯片和南桥(Southbridge)芯片。北桥芯片主要负责对接CPU通讯、内存控制器、PCI-E控制器、集成显卡等具有很高传输速率的模块,因此北桥也被称为MCH (Memory Controller Hub)芯片。南桥主要用来管理磁盘控制器、网络端口、扩展卡槽、音频模块、I/O接口、PCI总线、硬盘接口、USB 端口等中低速的组件,因此南桥芯片称为 ICH (I/O Controller Hub)。



现代计算机基本架构概述

PCI Express (peripheral component interconnect express)简称 PCI-E,是一种高速串行计算机扩展总线标准。PCI-E 从2003年的1.0版本开始到现在的6.0经历了数次更新,速度也从2.5GT/s提升到了65GT/s。由于所有的GPU设备都是通过PCI-E总线与处理器相连,所以PCI-E总线的带宽直接决定了GPU的扩展性和数据吞吐量。
PCI-E与其上一代PCI (Peripheral Component Interconnect, 外围设备互联)总线的区别在于PCI-E提供了一个固定的带宽。在PCI总线中,一次只能有一个设备使用总线,并占用总线全部带宽。因此,PCI卡越多每个卡能分配的可用带宽越少。PCI-E总线通过引入高速的串行链路组合(即PCI-E Lane)来解决这个问题,其Lane数一般可为X1、X4、X8、X16。GPU一般插入X16 Lane的PCI-E插槽来获取最大数据传输带宽。以PCI-E 2.0的X16链路为例,其提供了5GB/s的全双总线带宽,数据的传入和传出可同时进行,并共享全部的5GB/s的带宽。
在多CPU和GPU系统中,访问内存和GPU要通过北桥,访问硬盘、网络等则需要经过北桥和南桥。而对于深度学习模型训练而言,大量数据需要在硬盘、内存、CPU和GPU之间复制和通讯,所以PCl-E带宽和I/O带宽往往是整个应用系统性能的瓶颈。为了进一步提升CPU与内存,以及CPU与高速外设之间数据传输能力,Intel从第一代Core i7开始,将内存控制器整合到CPU中,而后又将PCI-E控制器也整合到CPU当中。Intel沙桥(Sandy Bridge)设计是迈向全面的集成的重要一步,它将北桥和CPU核心部分彻底融合在一起,结合固态硬盘(SSD)可以提供更高的数据读写性能。
对于一个典型的深度学习训练任务,通常需要处理一个很大的数据集,而这些数据都需要从外设中进行读取,在集群系统中则需要通过以太网进行数据读写。如果按照传统的硬盘和以太网通过PCI总线连接到南桥,再通过北桥与内存和CPU通讯构成的延迟是很大的。因此,在InfiniBand专用高速互联设备和10GB以太网卡通常连接到PCI-E总线上,在CUDA 4.0引入GPU直连(GPU-Direct)技术后,可以实现GPU与GPU或GPU与InfiniBand卡之间直接通讯,不再需要通过CPU转发。
GPU硬件结构

GPU的硬件结构与CPU有本质的差异,如图所示,GPU设备更像一个小型计算机集群,而每个SM都是一个微型的计算节点。以NVIDIA GPU 为例,其主要包括以下几个模块(不同的GPU架构会有差异):

  • CPU的命令通过PCI-E总线接口将指令和数据传输到GPU。
  • Giga Thread Engine,负责将线程分配给不同SM。
  • GPC (Graphics Processing Cluster,图形处理簇), 每个GPC对应一个光栅化引擎和多个TPC。
  • TPC (Texture Processor Cluster,纹理处理簇),每个TPC包含Poly Morph Engine和多个SM。
  • SM (Streaming Multiprocessors,流多处理器),包含多个SP(或称CUDA Core)负责线程执行。
  • Memory Controller为内存控制器,负责访问显存。
  • Memcoy为GPU显存。
  • High Speed Hub为高速集线器,负责GPU间的内存访问。
  • NVLink为GPU间的高速互联接口。



GPU硬件结构简图

GPU的计算是并行处理的,SM (Streaming Multiprocessor)是GPU中实际负责完成计算过程的功能单元,是一种通用的处理器组件,每个SM中包含几十或者上百个执行单元,主要负责GPU指令执行和数据计算。执行单元在NVIDIA Tesla架构称为SP (Streaming Processor,流处理器),在Fermi架构中称为CUDA core。不同执行单元上的任务由Warp Scheduler以线程束(Warp)为单位进行调度和分配,所有执行单元共享同一个Shared Memory和Instruction Unit。
GPU之所以可以进行大规模并行计算,是因为GPU的中包含右大量的执行单元,并通过单指令多线程(Single Instruction Multiple Threads,SIMT)实现多个执行单元同时做计算。每个执行单元相当于一个微型的CPU,其内部包括承接控制单元指令的Dispatch Port和Operand Collector,以及跟计算相关的浮点计算单元FP Unit、整数计算单元Int Unit和SFU等,另外在NVIDIA Turing和Ampere架构中SM还包含负责深度学习相关运算的Tensor cores和负责处理光线追踪运算的RT core。



NVIDIA Tesla架构(左)与Turing架构(右)中SM示例图

以NVIDIA Turing架构SM为例,每个SM被分成多个处理块(Processing Block),每个Block中包含一个独立的Warp Scheduler,一个独立的Dispatch Unit,一个Register File,多个INT32处理单元,多个FP32处理单元和多个TENSOR CORES单元等,主要的功能介绍如下:

  • Warp Scheduler:实现线程束(Warp)的快速切换,包括上下文切换,运行指令的切换等。
  • Dispatch Unit:指令派发单元  。
  • Registers File:每个SM包含了数千个以上的寄存器单元  。
  • INT32:整数处理单元。
  • FP32:单精度浮点数处理单元。
  • TENSOR CORES:为Tensor和Matrix计算设计的专用处理器核心。
  • SFU (Special Function Unit):用于实现一些特殊函数如三角函数,指数函数等的计算。
  • RT Core (Ray Tracing Core):基于硬件的光线追踪加速组件。
程序员编写的Shader(或kernel)需要在SM上完成计算,每个SM包含许多运算单元,在SM中计算任务以线程束(Warp)来调度,每个线程束包含32个线程。GPU在获取指令和数据后,由Warp Scheduler按顺序分发指令给Warp执行,每个Warp中所有线程同一时刻执行完全相同的指令,当某个Warp处于阻塞状态时,Scheduler会进行Warp之间的切换以避免GPU等待。每个Warp都需要用到一定的寄存器来进行数据的存储,所以SM所能同时支持的Warp的数目受到SM中寄存器数目的限制。GPU执行的一般步骤包括:

  • CPU将程序编码成GPU可读的格式和指令,然后将指令插入到Pushbuffer(推送缓存),此阶段容易产生很多性能上的瓶颈,所以结合API和调用编排技巧以充分利用GPU的性能非常重要。
  • Pushbuffer上缓存了足够多的工作任务后发送给GPU进行处理,GPU的主接口(Host Interface)获取到处理的指令之后对索引缓冲中的序列进行处理,批量化打包后分发给多个GPC。
  • 在GPC中,获取到数据后就分配到SM中进行任务处理。不同架构的GPU中GPC包含的SM数量不一样,例如在Maxwell架构中一个GPC有4个SM,而在Turing架构中一个GPC包含了6个TPC (Texture Processor Cluster,纹理处理簇),每个TPC又包含2个SM。
  • SM接收到任务后,Warp Scheduler会根据顺序将Warp发射到执行单元执行,Warp指令可能会一次性完成,也可能会耗费几个周期才能完成。对于一些比较耗时的处理指令(比如内存加载),待等待指令执行结束时Warp Scheduler会切换一个Warp执行。GPU通过快速的切换活动的Warp来隐藏内存读写延迟,所有被调度的线程都存储在寄存器堆(register-file)中,确保Warp切换足够快速。
  • SM的任务的执行是以lock-step的方式锁步执行的,即一个Warp中所有的32个线程是同步执行的,且所有线程执行相同指令。当程序中存在if-else分支时,由于Warp需要同步执行相同指令,因此没办法做到部分线程执行if分支,而另外一部分执行else分支。GPU采取遮掩(mask out)的方式来处理分支,GPU会先执行if分支,对不满足if条件分支的线程会被遮掩,在执行完if分治后再执行else分支,遮掩不满足else分支的线程。被遮掩的代码同样是要消耗相同的指令周期,因此,如果一个程序里有太多的if-else分支会降低GPU性能。为了获得更高的计算性能,应尽量避免Warp中存在大量分支。对于无法避免的分支可以尽可能将数据进行分割,确保同一个Warp里所有线程都执行相同分支指令。
  • 在GPU图形渲染过程中,还会利用光栅引擎(Raster Engine)和多形体引擎(Poly Morph Engine)来提升图形渲染速度和图像渲染画质。光栅引擎主要负责根据三角形序列获取顶点数据(Vertex Fetch),执行边缘和三角形设定(Edge Triangle Setup)、光栅化(Rasterization)、Z轴压缩(Z-Culling)等任务。多形体引擎则要主要负责顶点拾取(Vertex Fetch)、细分曲面(Tessellation)、视口转换(Viewport Transform)、属性设定(Attribute Setup)、流输出(Stream Output)等方面的处理任务。
GPU存储模式

存储架构

GPU在开始工作前,需要把指令和数据从CPU上存放到GPU的存储区,在CPU/GPU异构计算系统有非统一内存访问架构(Non-uniform memory access, NUMA)和统一内存访问架构(Uniform Memory Access, UMA)两种不同的存储结构。



CPU/GPU存储架构

独立显卡一般采用NUMA存储架构,GPU有独立的存储器,并通过PCI-E总线与CPU相连,所以GPU不能直接读写系统内存,使用时需要把数据从内存中经总线放到GPU显存中后才能使用。NVIDIA在CUDA 6.0中增加了虚拟内存的机制,将系统内存和GPU显存的虚拟地址统一,实现了数据的按需迁移。集成显卡则一般采用UMA存储架构,CPU与CPU共享系统内存空间,但CPU和GPU所使用的内存区域并不相同,系统会在物理内存中画出一块专有区域给GPU管理。一般GPU会对内存布局做了专门的优化来提升GPU处理性能,所以CPU到GPU的数据通信需要进行数据拷贝来优化排布,在一些CPU和GPU内存统一布局的芯片上可以实现数据传输零拷贝(zero-Copy),比如苹果M系列芯片。
GPU使用独立的显存空间的优势是可以依据GPU计算特性对数据排布进行特定的优化,所以即便在UMA架构中CPU和GPU 共用的是同一块物理内存,但依然需要通过数据的拷贝的方式来优化数据排布。如果CPU和GPU直接使用相同的数据,可能因为数据排布未做优化反而会降低了性能,所以是否拷贝数据还是零拷贝读写需要综合考虑数据排布对计算性能的影响和数据拷贝时间。
存储类型

GPU中有多种存储数据的结构,不同类型存储有不同的特性,因此适用于不同的应用场景:  

  • 全局内存(Global Memory),即显存,全局共享能被所有线程访问,是空间最大但延迟最高的存储单元。
  • 局部内存(Local Memory),全局内存中的一部分,每个线程私有且只对其负责的线程可见,访问速度很慢但带宽占用低。在线程申请的变量超过可用的寄存器大小时,编译器会自动将一部数据放置到局部内存里面。
  • 常量内存(Constant Memory),通常用于存储一些常量数据,属于全局内存一部分(大小一般64KB),部分GPU会有专门的Constant Cache,当同一个Warp中的所有线程都需要使用同一个常量数据时,支持将单个值广播到Warp中的每个线程。
  • 纹理内存(Texture Memory),其数据在全局内存上,但其有专用的只读Cache,主要用于图形图像的存储,其针对图形图像数据访问提供了特殊的优化策略,所以当一个Warp中的线程读取相邻位置的数据时可以提供更优的访问性能。
  • 共享内存(Shared Memory),在SM上的内存空间(On Chip Memory,片上内存),每个Processing Block核心内的所有线程共享,访问速度很快。
  • 缓存(Cache),L1 Cache是片上缓存(On-Chip),每个Processing Block核心都有独立的L1 Cache,访问速度很快,L2 Cache是所有的核心共享的,属于片外缓存,访问速度较L1 Cache要慢。
  • 寄存器(Register),寄存器存储每个线程的私有变量,其生命周期与此线程的生命周期一致,访问速度最快,其数量在不同的架构设计中存在比较大的差异。



GPU不同存储示例图

Shared Memory和L1 Cache是同一个硬件单元,Shared Memory 是可以由开发者控制的片上内存,而 L1 缓存是GPU控制的,开发者无法访问。同CPU一样,GPU Cache之间或Cache与显存之间的数据通讯也是以Cache Line为单位的,Cache Line大小一般为128bit (CPU一般为64bit)。与CPU Cahce一样,缓存命中与否对性能的影响很大,Texture Memory也是通过特殊的优化来提高纹理数据的命中率进而提升读取性能的。
同一个Processing Block中的线程共享一块Shared Memory,不同线程可以访问同一个Block内的其他线程的数据。Shared Memory被划分为多个可以同时访问的且大小相等的Memory Bank。Bank数量一般与Warp大小或者CUDA core数量对应,每个Bank中包含多个Cache Line。Bank中对数据的访问是以每个Bank独立操作的,一次访问可以获取Bank数量个地址数据,并将这些数据映射到不同的Bank中的Cache Line上。

  • 同一个Warp中的不同线程,访问不同的Bank上的数据可以并行执行,可以最大化利用带宽,性能最佳。
  • 同一个Warp内的所有线程都访问一个Bank中的同一个Cache Line,系统会通过广播机制同步到其他线程,这些请求会一次完成,也不会与性能影响。
  • 不同线程访问一个Bank中的不同Cache Line时,但是由于一个Bank同时只能给一个线程,所以这些请求会串行执行,这个会阻碍GPU的并行性,产生明显的性能下降。
  • 不同的线程对同一个Cache Line有写操作时,也必须要阻塞等待上一个线程写完才能执行后面的读取或者写入操作。
GPU计算模式

GPU是典型的SIMT执行模式,是线程级并行(Thread Level Parallelism, TLP)。在GPU中,计算任务为大量的可以并行处理的子任务,每个任务映射为一个可执行的线程,再由硬件动态调度和执行这些线程。这些任务线程以线程网格(Grid)的形式组织,每个线程网格包含多个线程块(Block),每个线程块又由多个线程(Thread)组成。对应的,GPU的硬件设计也采用分层组织的方式划分为多个SM,每个SM内部又包含多个Core,线程被分配到不同的Core上被执行。
一个GPU程序包含大量可并行执行的子任务,当程序被启动时,每个子任务会对应一个线程,这些线程会被分配到不同的SM中进行运算。一个GPU程序启动的所有线程组成一个Grid,每个Grid又包含多个Block,各个Block并行执行无法通信,也没有执行顺序。Block是GPU程序最小执行单元,同一个Block中的Threads可以进行数据共享和通信,所以一个Block必须被分配到一个SM中,Block中的所有Thread都在这个SM中并行执行,但一个SM中可以有多个Block在等待执行。



Grid, Block, Thread之间的关系

在CUDA中,一个Kernel启动的所有线程组成一个Grid,每个Grid由多个形状相同的Block执行,这些Block被组织成一维、二维或三维的Grid,Grid中的每个Block都可以被Kernel中唯一的索引标识,这个索引可以通过内置的blockIdx变量访问,对应的索引也可以是一维、二维或三维的。Block则由多个线程组成,同样可以有一维、二维或者三维,每个线程可以通过threadIdx变量访问,对应的索引也是一维、二维或三维的。
Block中的线程在被执行的时候,又会被分成更小的线程束(Warp)。在NVIDIA GPU中,一个Warp由32个线程组成。Warp是SM调度和运行的最小单元,同一个Warp中所有Thread并行执行相同的指令,同一个Block中的多个Warp则由Warp Schedule负责调度和在不同Warp之间进行上下文和运行指令的快速切换。当一个Block进入SM运行前,首先会为其分配Shared Memory,Register等资源,此时该Block被称为Activate Block,它包含的所有Warp共享这些资源。SM中的Warp Scheduler每个周期会从这些Warp中选择一个发射到计算单元或SFU,LD/ST等单元执行相关操作,而在等待指令或者加载数据的Warp则会被切出以空出资源给其他Warp使用。
GPU中可以创建的Grid、Block和Thread的最大数量和GPU设计硬件设计有关,以NVIDIA计算能力2.0 GPU为例,Grid和Block xyz三个维度的最大值分别为Grid = (65535, 65535, 65535), Block = (1024, 1024, 64)。所以理论上,一个Kernel能生成的最大线程数为65535 x 65535 x 65535 x 1024 x 1024 x 64 = 1.89万亿亿个线程。对于特定的GPU架构,一个SM所能支持Warp数量是有限制的,受到SM上的寄存器数量和Shared Memory等资源的限制,一个SM上能同时保持的Activate Block数量和Activate Warp也是有限的。



NVIDIA不同GPU计算能力

以Fermi GF110架构为例,每个Block中最大线程数1024,对于1维Grid最大线程数为65535 x 1024 = 6.71百万个线程,对于二维Grid最大线程数为65535 x 65535 x 1024 = 4.39万亿个线程,所以对于百万以上数据的并行计算,将Grid定义为二维可以防止线程数不足。GF110中一个SM上有128K个Register,每个线程最大可以占用64个Register,所以其最少能供给128K/64=2048个线程,共64个Active Warp。另外,影响Active Warp的数量还受Shared Memory大小的影响,单个Block占用的Shared Memory资源越多,那么一个SM上能支持的Active Block的数量就越少,同时一个SM所能支持的Block数量也有上限,所以即使Block占用的资源很少,那么它的数量也无法做到无限大。因为Block中包含的最大线程数也是确定,所以它也影响SM能支持Active Warp数量。
在GPU中Giga Thread Engine会把Block分配到多个SM中,每个SM可以容纳多个Block,当SM中资源不足时Giga Thread Engine不会再向该SM分配新Block。在SM中,Block又被划分为多个Warp,每个Warp有就绪、运行、阻塞三个状态,当遇到I/O等待或等待指令时Warp进入阻塞态,当计算核心、内存等计算资源空闲时阻塞Warp转为就绪态,在每个周期内Warp Scheduler会选择一个就绪的Warp送入执行单元中执行。由于每Warp包含32个线程,如果Block中的线程数量不能被32整除,那么多出来的线程被补齐到32个线程,补充的线程处于不活跃状态。不活跃线程仍然消耗SM的硬件资源,所以在写代码时要尽量保证Block中的线程是32的倍数,以避免不必要的计算资源浪费。



GPU线程调度示例图

在NVIDIA Fermi架构GPU中有两个Warp Scheduler,而Kepler架构GPU中有四个Warp Scheduler,所以同一时刻可以有两或四个Warp在SM中同时运行。进行Warp调度的目的是为了通过Warp的不断切换来充分隐藏内存访问和指令的延迟,及时空出资源给其他Warp执行来提升系统的整体效率。在单元执行中的LD/ST单元用于读取内存,INT32或FP32单元用于执行计算,在Warp中内存读写指令采用异步执行的方式,当Warp在读写内存等待期间,及时切换到其他等待执行的Warp执行,通过不断切换来提升计算效率。
整个GPU的算力等于单个SM的算力乘以SM的数量,而单个SM的算力等于GPU主频乘以计算单元数量再乘以单个时钟周期的计算次数。以Tesla P100为例,GPU主频是1329MHz,包含56个CUDA核心,FP32执行单元数量为64,每个时钟周期能执行两条FP32计算,所以P100 FP32 算力等于1329MHz x 64 x 56 x 2 = 9.526TFLOPs。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-3 02:55 , Processed in 0.151854 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表