GPU学习笔记

GPU计算

GPU需要与CPU协同工作,作为CPU的协处理器,形成一个异构计算平台。
CPU为主机端host,GPU为设备端device。
GPU与CPU间通过高速总线进行数据拷贝实现通信,如PCIe,NVlink。

CPU上的线程时重量级的,上下文开销大。
GPU由于存在多个核心,其线程是轻量级的。
CPU+GPU优势互补,CPU负责处理逻辑复杂的的串行程序,GPU重点处理数据密集型的并行计算程序。
目前CPU与GPU可以申请一块公共内存区域实现通信?

GPU架构

专业术语

  • SP:streaming processor(CUDA core)
  • SPA:SP array
  • TPC/GPC:Texture(Graphics)Processor Cluster,MIG的物理结构
  • SM:streaming multiprocessor,CUDA thread block的基本处理核心

SM

  • 包含32个CUDA core,4个SFU,最新的架构也包括了tensor core
  • warp:32个thread为一个warp,执行相同指令。
  • grid:网格,几百万几千万没问题
  • block:1-512/1024/2048个thread

NV A100的SM架构

Tesla P100 Tesla V100 A100
SMs 56 80 108
TPCs 28 40 54
FP32 core/SM 64 64 64
FP32 core/GPU 3584 5120 6912
FP64 core/SM 32 32 32
FP64 core/GPU 1792 2560 3456
INT32 core/SM NA 64 64
INT32 core/GPU NA 5120 6912
Tensorcore/SM NA 8 42
Tensorcore/GPU NA 640 432
GPU Boost Clock 1480MHz 1530MHz 1410MHz
  • SM数目增多,但是tensorcore数目是减少的,这点后续有时间研究一下。
    GPU学习笔记_第1张图片

GPU程序架构

从编程模型来看,层次从大到小为kernel,grid,block和thread:

  • 一个kernel所启动的所有线程称为一个grid;同一个grid内的线程共享内存空间。

对应的硬件资源分配为:

  • blocks被依次分配到SM中,同一个block只能在一个SM中,不会跨SM;
  • warp对应硬件执行中才有的概念;
线程结构 对应编程模型 对应硬件结构 层次 comment
grid kernel SM array level1 共享全局内存空间
block - SM level2 共享L1
warp - 32 threads level3 执行相同的指令
thread - - level4 每个thread有私有本地内存

GPU学习笔记_第2张图片

CUDA程序执行流程

  1. 分配host内存,并初始化;
  2. 分配device内存,并将host数据拷贝到device上;
  3. 调用CUDA核函数在device上执行运算;
  4. 将device上的运算结果拷贝到host;
  5. 释放device和host上分配的内存;

CUDA程序层次结构

grid和block都是dim3类型,支持1-dim/2dim/3-dim,默认值都为1。

//thread=3*2*5*3
dim3 grid(3,2)
dim3 block(5,3)
//thread=128*256
dim3 grid(128)
dim3 block(256)
//thread=100*120*16*16*1
dim3 grid(100,120)
dim3 block(16,16,1)

不同GPU最大支持的grid和block数目不同,grid数目较多。
一个线程需要两个内置的坐标变量(blockIdx, threadIdx)来唯一标识,通过内置变量可以准确定位线程的位置,

  • gridDim.x/y/z与blockDim.x/y/z分别为grid与block的维度信息;
  • blockIdx.x/y/z与threadIdx.x/y/z分别为
  • 其中blockIdx指明线程所在grid中的位置,是相对的;
  • threadIdx指明线程所在block中的位置,也是相对的;
  • 线程的全局编号也就是绝对的坐标需要以上四类数据的组合计算;
/* get thread id: 1D block and 2D grid */
#define get_tid() (blockDim.x * (blockIdx.x + blockIdx.y * gridDim.x) + threadIdx.x)

/* get block id: 2D grid */
#define get_bid() (blockIdx.x + blockIdx.y * gridDim.x)

// Kernel定义
__global__ void vec_add(double *x,double *y,double *z,int n)
{
	int i = get_tid();//user-defined macro/function
	if(i<n) z[i] = x[i] + y[i]
}

int main()
{
	int N=1000000; 
	// 1-dimension grid and block
	int bs = 256;
	int gs = (N+bs-1)/bs;
	//initialize x/y/z array;

	//kernel call GPU <<>>,10^6 threads
	vec_add<<<gs,bs>>>(x,y,z,N);
	
}

CUDA中通过函数限定词区分host与device:

__global__
// execute on device
// void return type
// 定义的kernel是异步的,host不会等待kernel执行完成就执行下一步
__device__
// 仅可从device中调用
//不可以和__global__同时使用
__host__
//仅可从host上调用,一般省略不写
//不可以和__global__同时用,但可以和__device__,此时会在device和host都编译。

GPU内存

存储层次

GPU学习笔记_第3张图片

寄存器是SM中的稀有资源,Fermi架构中每个线程最多63个寄存器,Kepler扩展到255个。

  • 如果一个线程使用更少的寄存器,那么就会有更多的常驻线程块,SM上并发的线程块越多,效率越高,性能和使用率也就越高。
  • 如果一个线程里面的变量太多,以至于寄存器不够用,这时就需要寄存器与内存反复读写,导致性能降低。

同步机制

从cuda编程角度,同步的实现,可以参考这个视频:https://www.bilibili.com/video/BV1s7411j7iG?spm_id_from=333.337.search-card.all.click

barrier

GPU学习笔记_第4张图片

实例:需要多少barriers


答案是3个

  • 保证初始化完毕;
  • 保证读优先;
  • 保证写完成;
    GPU学习笔记_第5张图片
    sync的两种体现:
  • thread block的sync为的最基本执行单元;
  • kernel间也必会有sync进行同步;
    GPU学习笔记_第6张图片
    这个例子可以想一下是否三个都需要sync保证?是否会发生thread的读写冲突?
    GPU学习笔记_第7张图片

你可能感兴趣的:(学习)