GPU体系结构
GPU发展简史:
GPU通用计算
基本思路:
- 专注于计算。以SIMD/SIMT的方式支持大规模并行计算,并非任何计算过程都可以迁移到GPU。
- 不支持复杂的逻辑,不单独使用。仅作为辅助CPU的加速计算设备。
GPU逻辑架构
Turing架构:每个GPU封装6个GPC,每个GPC封装6个TPC,每个TPC封装2个SM。
SM的架构
| TU102 | V100 |
|---|---|
| 分为4个block,96KB共享缓存 | 分为4个block,128KB共享缓存 |
| 每个block有2个FP64、16个FP32、16个INT32、2个Tensor、16384个32位寄存器 | 每个block有8个FP64、16个FP32、16个INT32、2个Tensor、16384个32位寄存器 |
| 1个RT用于Ray Tracing |
CUDA程序设计
2006年NVIDIA推出CUDA编程,使用SIMT(单指令多线程)模式。
CPU+GPU异构计算将可用于计算的硬件分为两部分:
- Host(主机):CPU。负责控制、指挥GPU工作。
- Device:GPU。负责协处理。

GPU计算过程:
- 将数据从CPU内存拷贝到GPU内存(显存)
- 加载GPU程序并执行,使用显存内数据以提升性能。
- 将结果数据从GPU内存拷贝回CPU内存。
线程创建与管理
|
|
__global__表示此函数在device上执行,自host调用。mykernel()在此没有进行任何计算。
示例:两个整数求和
kernel部分:
|
|
add()在device运行,所以a,b和c需要指向device内存。- 需要在GPU上分配内存空间。
main()部分:
|
|
GPU的优势是大规模并行:
|
|
这样add()不再只执行一次,而是并行执行了N次。
GPU向量加:并行执行add()可以实现向量加法。定义add()的每个调用为一个block,一组block定义为grid,各个调用通过blockIdx.x标识。
|
|
使用blockIdx.x作为数组的索引,每个block处理各自的数据。在device中,各个block可以并行执行。
|
|
CUDA线程
每个block可以拆分为单独并行的threads。将add()改为使用并行threads,不再使用并行blocks:
|
|
main()中只需修改:
|
|
Blocks+Threads
同时使用blocks和threads,每个线程需要全局唯一标识
|
|
组合使用时的add():
|
|
修改后的main():
|
|
向量的长度并不能保证恰好是
blockDim.x的整数倍,为了避免数组访问越界,可修改为:
1 2 3 4 5 6__global__ void add(int* a, int* b, int* c) { int index = threadIdx.x + blockIdx.x * blockDim.x; if (index < n) { c[index] = a[index] + b[index]; } }此时
main()函数修改为:
1add<< <(N+M-1)/M, M> >>(d_a, d_b, d_c);
线程层次与分组
每个kernel对应一个grid,每个grid对应一组block共享全局内存,每个block包含多个线程。

线程分组中,对应的维度必须能被整除,分组的大小不能超过1024
变量存储访问
函数与变量类型
| 函数类型 | 特点 |
|---|---|
__device__ |
在GPU中执行,由CPU调用 |
__global__ |
在GPU中执行,由CPU调用 |
__host__ |
在CPU中执行/调用,是默认函数类型 |
| 变量类型 | 特点 |
|---|---|
__device__ |
global memory space,grid中所有线程均可访问 |
__constant__ |
constant memory space,grid中所有线程均可访问 |
__shared__ |
space of a thread block,只能由block中的线程访问 |
| 内置向量类型 | char,short,int,long,long long,float,double:内置1,2,3,4维向量,结构体数据中含有x,y,z,w;dim用于表示维度 |
存储器层次
| 存储器 | 特点 |
|---|---|
| Register | 线程私有 |
| Local memory | 线程私有,KB级 |
| Shared memory | 速度快,10KB级;block线程中可访问 |
| Global memory | 容量大,速度慢,GB级 |
| Texture memory | GPU只读,KB级 |
| Constant memory | GPU只读,KB级 |

内存与显存分配
可分页内存:使用标准C/C++函数操作malloc()/new()。
分页锁定内存:
|
|
可分页内存转变为分页锁定内存:
1cudaHostRegister(void** ptr, size_t size, unsigend int flags)
显存分配:
|
|
内存映射
ZeroCopy
- host内存直接映射到GPU内存空间。
- 调用
cudaHostAlloc()分配时传入cudaHostAllocMapped标签。 - 使用
cudaHostRegister()注册时使用cudaHostRegisterMapped标签。 device可直接访问分页锁定内存,硬件在kernel访问显存时通过PCL总线传递,由硬件自动处理数据传输和计算的异步执行。
适用于计算密集型程序:数据访问量小、计算与数据传输重叠、不需要显式内存管理。
统一寻址
host与device使用同一个指针,在host内存和device显存之间根据访问需要自动迁移数据,同时保证host和device均可访问。应用程序并不知道访问时数据所在位置,需要进行显式同步。
统一寻址使得编程简化,数据访问性能提升。
共享与同步
Shared memory中的数据可以由同一个block中的线程访问。在block内部,threads可通过shared memory共享数据。
同步函数:
|
|
void __syncthreads();可以同步同一个block之内的全部线程,所有线程都必须运行到barrier。
示例:1D Stencil
对一维数组进行卷积处理:每个输出元素是输入元素一定“半径”内的数据和。
例如,若半径为3,则每个输出元素是7个输入元素的和。
在block内,每个thread处理一个输出元素,则每个block分配blockDim.x个元素。同时,输入数据会被读取多次。
性能优化
| 维度 | 优化方向 |
|---|---|
| 算法选择 | 算法并行性是首要因素,时间复杂度低未必合适 |
| 并行度 | GPU环境中线程切换效率很高,应使用尽可能的线程数 |
| 数据传输 | 次数尽可能少,数据传输量尽可能少,异步传输 |