3-1 CUDA执行模型概述
3.1 CUDA执行模型概述
前面使用了不同的block和grid来配置核函数,可以看出性能是有差异的,但是为什么会存在差异,如何选择最合适的参数呢?这一章就是解释这些内容。
在第2章里,已经介绍了CUDA编程模型中两个主要的抽象概念:内存层次结构和线程层次结构。在本章会重点介绍指令吞吐量,在第4章和第5章里会介绍更多的关于高效内存访问的内容。
3.1.1 GPU架构概述
GPU架构是围绕一个流式多处理器(SM)的可扩展阵列搭建的。可以通过复制这种架构的构建块来实现GPU的硬件并行。也就一堆的SM堆出来的。

上图3-1说明了Fermi SM的关键组件:
- CUDA核心
- 共享内存/一级缓存
- 寄存器文件
- 加载/存储单元
- 特殊功能单元
- 线程束调度器
GPU中的每一个SM都能支持数百个线程并发执行
每个GPU通常有多个SM,所以在一个GPU上并发执行数千个线程是有可能的。
当启动一个内核网格时,它的线程块被分布在了可用的SM上来执行。线程块一旦被调度到一个SM上,其中的线程只会在那个指定的SM上并发执行。
多个线程块可能会被分配到同一个SM上,而且是根据SM资源的可用性进行调度的。
- CUDA采用单指令多线程(SIMT)架构来管理和执行线程,每32个线程为一组,被称为线程束(warp)。
- 线程束中的所有线程同时执行相同的指令。每个线程都有自己的指令地址计数器和寄存器状态,利用自身的数据执行当前的指令。
- 每个SM都将分配给它的线程块划分到包含32个线程的线程束中,然后在可用的硬件资源上调度执行。
- 一个线程块只能在一个SM上被调度。一旦线程块在一个SM上被调度,就会保存在该SM上直到执行完成。在同一时间,一个SM可以容纳多个线程块。
在SM中,共享内存和寄存器是非常重要的资源。共享内存被分配在SM上的常驻线程块中,寄存器在线程中被分配。线程块中的线程通过这些资源可以进行相互的合作和通信。
尽管线程块里的所有线程都可以逻辑地并行运行,但是并不是所有线程都可以同时在物理层面执行。因此,线程块里的不同线程可能会以不同的速度前进。
SM:GPU架构的核心
SM是GPU架构的核心。寄存器和共享内存是SM中的稀缺资源。CUDA将这些资源分配到SM中的所有常驻线程里。因此,这些有限的资源限制了在SM上活跃的线程束数量,活跃的线程束数量对应于SM上的并行量。
3.1.2 Fermi架构
Fermi架构是第一个完整的GPU计算架构。

- Fermi的特征是多达512个加速器核心,这被称为CUDA核心。
- 每个CUDA核心都有一个全流水线的整数算术逻辑单元(ALU)和一个浮点运算单元(FPU),在这里每个时钟周期执行一个整数或是浮点数指令。
- CUDA核心被组织到16个SM中,每一个SM含有32个CUDA核心。
- Fermi架构有6个384位的GDDR5 DRAM存储器接口,支持多达6GB的全局机载内存,这是许多应用程序关键的计算资源。
- 主机接口通过PCIe总线将GPU与CPU相连。GigaThread引擎(图示左侧第三部分)是一个全局调度器,用来分配线程块到SM线程束调度器上。
- Fermi架构包含一个耦合的768 KB的二级缓存,被16个SM所共享。
在上图中在图3-3中,一个垂直矩形条表示一个SM,包含了以下内容:
- 执行单元(CUDA核心)
- 调度线程束的调度器和调度单元
- 共享内存、寄存器文件和一级缓存
每个SM有两个线程束调度器和两个指令调度单元。当一个线程块被指定给一个SM时,线程块中的所有线程被分成了线程束。两个线程束调度器选择两个线程束,再把一个指令从线程束中发送到一个组上。每个线程束在同一时间执行同一指令,同一个块内的线程束互相切换是没有时间消耗的

从表格“CUDA_Occupancy_Calculator.xls”中可以看到具体的值如下:
Physical Limits for GPU Compute Capability: | 2.0 |
---|---|
Threads per Warp | 32 |
Max Warps per Multiprocessor | 48 |
Max Thread Blocks per Multiprocessor | 8 |
Max Threads per Multiprocessor | 1536 |
Maximum Thread Block Size | 1024 |
Registers per Multiprocessor | 32768 |
Max Registers per Thread Block | 32768 |
Max Registers per Thread | 63 |
Shared Memory per Multiprocessor (bytes) | 49152 |
Max Shared Memory per Block | 49152 |
Register allocation unit size | #N/A |
Register allocation granularity | warp |
Shared Memory allocation unit size | 128 |
Warp allocation granularity | 2 |
Shared Memory Per Block (bytes) (CUDA runtime use) | 0 |
3.1.3 Kepler架构
kepler架构的最突出的一个特点就是内核可以启动内核了,这使得我们可以使用GPU完成简单的递归操作,流程如下。
3.1.4 配置文件驱动优化
CUDA提供了两个主要的性能分析工具:nvvp,独立的可视化分析器;nvprof,命令行分析器。
nvvp是可视化分析器,它可以可视化并优化CUDA程序的性能。
nvprof在命令行上收集和显示分析数据。
主要使用nvprof来提高内核性能。本书还介绍了如何选择合适的计数器和指标,并使用命令行中的nvprof来收集分析数据,以便用于设计优化策略。你还将会学习如何使用不同的计数器和指标,从多个角度分析内核。
有3种常见的限制内核性能的因素:
- 存储带宽
- 计算资源
- 指令和内存延迟
附录:
GPU硬件架构
SP(CUDA Core)
SP(streaming processor):最基本的处理单元,也称为CUDA core。最后具体的指令和任务都是在SP上处理的。GPU进行并行计算,也就是很多个SP同时做处理。下面这个图是CUDA Core的结构:
包括控制单元Dispatch Port、Operand Collector,以及浮点计算单元FP Unit、整数计算单元Int Unit,另外还包括计算结果队列。当然还有Compare、Logic、Branch等。相当于微型CPU。
SM
SM(streaming multiprocessor): 多个SP加上其他的一些资源组成一个SM,也叫GPU大核,其他资源如:warp scheduler,register,shared memory等。SM可以看做GPU的心脏(对比CPU核心),register和shared memory是SM的稀缺资源。CUDA将这些资源分配给所有驻留在SM中的threads。因此,这些有限的资源就使每个SM中active warps有非常严格的限制,也就限制了并行能力。如下图是一个SM的基本组成,其中每个绿色小块代表一个SP。

每个SM包含的SP数量依据GPU架构而不同,Fermi架构GF100是32个,GF10X是48个,Kepler架构都是192个,Maxwell都是128个。当一个kernel启动后,thread会被分配到很多SM中执行。大量的thread可能会被分配到不同的SM,但是同一个block中的thread必然在同一个SM中并行执行。
要注意的是CUDA Core是Single Precision的,也就是计算float单精度的。双精度Double Precision是那个黄色的模块。所以上面一个SM里边有32个DP Unit,有64个CUDA Core,所以单精度双精度单元数量比是2:1。LD/ST 是load store unit,用来内存操作的。SFU是Special function unit,用来做cuda的intrinsic function的,类似于__cos()这种。
无论是图形渲染还是cuda编程,最基本的程序并行结构称为thread,这是程序员可以控制的最细粒度的并行单位。每一个thread在运算单元上就对应一个sp,所以新闻里常常会笼统的把sp数量等同于thread的并行数量,从而量化不同GPU的性能。多个thread组合起来称为一个block,数量是程序员可以设定的。在同一个block内的thread之间可以相互通信,因为他们可以共用同一个SM内的shared memory(共享储内存),每一个thread还拥有各自独占的register(寄存器)和local memory(本地储存器),这几种储存器都是整个GPU中距离运算单元距离最近,速度最快的储存器资源。但是跨block的线程通信不能通过SM内部的储存器,只能通过距离很远,访问时间长达几百个周期的global memory(全局内存,就是指显存)来实现,这个速度实在太慢了,所以cuda程序会尽量避免使用global memory。
Warp
多处理器multiprocessor以32个并行线程(称为warp)为一组来创建、管理、调度和执行线程。
warp中所有threads并行的执行相同的指令。由SM的硬件warp scheduler负责调度。目前每个warp包含32个threads。从一个执行上下文切换到另一个执行上下文没有任何成本,并且在每次指令发出时,warp调度器warp scheduler都会选择一个具有准备执行下一条指令的线程的warp (warp的活动线程the active threads of the warp),并向这些线程发出指令。
参与当前指令的warp线程称为活动active线程,而不在当前指令上的线程称为非活动线程inactive (disabled)。
所以在分配grid和blocksize时的基本概念是
- 保证block中thread数目是32的倍数。这是因为同一个block必须在一个SM内,而SM的Warp调度是32个线程一组进行的。
- 避免block太小:每个blcok最少128或256个thread。
- 根据kernel需要的资源调整block,多做实验来挖掘最佳配置。
- 保证block的数目远大于SM的数目。
Gitalk 加载中 ...