内容参考自:General-Purpose Graphics Processor Architecture
GPU 硬件通过很宽的 SIMD 硬件挖掘 GPU 程序中的数据级并行。但其 SIMD 硬件并不直接暴露给程序员,程序员使用 SIMT 执行模型:准备好大量的标量线程组给 GPU ,每个线程有自己独立的执行路径,这些标量线程组被 NVIDIA 称为 warps (AMD 称为 wavefronts), 在 GPU 上通过 SIMD 硬件实现,每个线程内部是带锁的
执行模型
对于 discrete GPU ,程序首先执行 CPU 部分代码:
- 分配 GPU 计算需要的显存空间 (cudaMalloc)
- 将输入数据初始化到 GPU 显存中 (cudaMemcpy)
- 进入 GPU 计算 kernel
通过(<<<nblock, thread_per_block>>>)配置线程数(每个 block 将在一个 SM 上运行, 因此 block 数不能超过 GPU 中的 SM 数)
NVIDIA 引入 Unified Memory 之后,可以透明地进行 CPU 和 GPU 之间的数据传输,其他架构需要程序员手动管理 GPU 显存的分配和传输
GPU 计算 kernel
GPU 计算 kernel 一般由数千个线程组成,这些线程在 kernel 中的组织结构:
- 1 个 grid 由 thread blocks (in NVIDIA) / cooperative thread array, CTA (in AMD) 组成
- thread blocks / CTA 由 wraps / wavefronts 组成
在程序中 kernel 由一个函数指定( CUDA 中通过 __global__ 关键字指定)
NVIDIA 每个 wrap 由 32 个线程组成 (AMD 每个 wavefront 由 64 个线程组成)
thread blocks / CTA
- 每个 thread blocks / CTA 内的线程之间可以通过计算核的 SPM 高效通信
- 每个计算核有一个SPM, 供计算核上的所有的 thread block/CTA 使用
- SPM 一般很小, 每个计算核的 SPM 一般在 16-64 KB,并作为不同的内存空间提供给程序员
- 在 CUDA 中通过
__shared__指定将内存分配到 SPM - SPM 功能类似于软件管理的 cache, 但计算核中还有硬件管理的 cache
- NVIDIA 称为 shared memory
- AMD GCN 架构将该 SPM 称为 local data store, LDS
除此之外,所有 compute core 还会共享一个 global data store, GDS
- 每个 thread blocks / CTA 内的线程可以使用硬件支持的 barrier 指令进行高效同步
- 不同 CTA 之间的线程可以进行通信,但需要通过全局地址空间同步,代价更高
线程 thread
- 每个线程执行相同的代码
- 这些指令的操作数均为标量操作数
- 但是遵循不同的控制流。
- 每个线程有自己的 id
- CUDA 中 grid, block, thread 这三个层级都有相应的 id
- 每个线程在其所属的 grid, block 中有唯一的非负坐标 (x,y,z)
- thread in block:
threadIdx.x,threadIdx.y,threadIdx.z - max thread id in block:
blockDim.x,blockDim.y,blockDim.z - block in grid:
blockIdx.x,blockIdx.y,blockIdx.z
- thread in block:
GPU ISA
NVIDIA
GPU 程序编译过程:
- 编译: CUDA C 代码 → PTX 汇编代码
(nvcc 编译器) - 汇编: PTX 汇编代码 → SASS 二进制代码
(ptxas 汇编器)
Parallel Thread Execution ISA, PTX
- 类似于 RISC ISA, NVIDIA 有完整的文档公开
- GPGPU-sim 基于 PTX 开发
- 与 RISC ISA 不同的地方在于:
- PTX 可以使用无限的寄存器号,但其编译出的 SASS 只能使用有限的寄存器号
- kernel 参数通过存储常量内存传递,SASS中的非访存指令可以访问这些常量内存,而PTX中的参数则被分配到各自独立的“参数”地址空间中
Streaming ASSembler, SASS
NVIDIA 未提供完整的文档,只提供了指令 opcode,未提到操作数域和指令语义
- 无法进行自定义修改
- 不具有后向兼容性
例: NVIDIA 从 Kepler 架构开始不再需要使用计分板进行显式的依赖检查
有一些工作是针对 SASS 的反汇编: decuda
NVIDIA 也提供反汇编工具: cuobjdump
AMD GCN
有完整的硬件级 ISA spec
GPU 程序编译过程:
- 编译: OpenCL 代码 → HSAIL 汇编代码
- 汇编: PTX 汇编代码 → SASS 二进制代码
(ptxas 汇编器)
与 NVIDIA 的区别:AMD GCN 的标量和向量指令是分离的, 标量指令前缀 s_, 向量指令前缀 v_
AMD GCN 架构中每个 SIMT Core 包含一个标量计算单元和 4 个向量计算单元
- 向量指令
- 在向量计算单元上执行,每个 wavefront 中的每个独立的线程计算不同的 32-bit 值
- 使用 exec 寄存器进行向量指令的谓词执行
- 标量指令:在标量计算单元上执行,每个 wavefront 中的所有线程计算共享的一个 32-bit 值
- 和控制流处理相关
- 潜在好处:frequently certain portions of a computation in a SIMT program will compute the same result independent of thread ID (不是很理解?)
为了实现长延迟操作的数据依赖性解析,AMD GCN 架构包含了
S_WAITCNT指令:
每个 wavefront 有三个计数器:vector memory count, local/global data store count, register export count
这些计数器分别表示特定类型未完成操作的数量。编译器或程序员会插入S_WAITCNT指令,使 wavefront 等待直到未完成操作的数量降至指定阈值以下