CudaSteps/capter2/ReadMe.md

236 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CUDA 中的线程组织
CUDA 虽然支持 C++ 但支持得并不充分,导致 C++ 代码中有很多 C 代码的风格。
CUDA 采用 nvcc 作为编译器,支持 C++ 代码nvcc 在编译 CUDA 程序时,
会将纯粹的 c++ 代码交给 c++ 编译器,自己负责编译剩下的 cu 代码。
------
## C++ 的 Hello World 程序
>> g++ hello.cpp -o ./bin/hello.exe
>> ./bin/hello
msvc: hello world!
------
## CUDA 的 Hello World 程序
### 使用 nvcc 编译纯粹 c++ 代码
>> nvcc -o ./bin/hello_cu.exe hello.cu
>> ./bin/hello_cu.exe
nvcc: hello world!
在该程序中其实并未使用 GPU。
### 使用 核函数 的 CUDA 程序
一个利用了 GPU 的 CUDA 程序既有主机代码,又有设备代码(在设备中执行的代码)。
主机对设备的调用是通过 **核函数kernel function** 实现的。
int main()
{
主机代码
核函数的调用
主机代码
return 0
}
核函数与 c++ 函数的区别:
1. 必须加 `__global__` 限定;
2. 返回类型必须是空类型 `void`
__global__ void hell_from__gpu()
{
// 核函数不支持 c++ 的 iostream。
printf("gpu: hello world!\n");
}
调用核函数的方式:
hello_from_gpu<<<1, 1>>>
主机在调用一个核函数时,必须指明在设备中指派多少线程。核函数中的线程常组织为若干线程块:
1. 三括号中第一个数字是线程块的个数number of thread block
2. 三括号中第二个数字是每个线程块中的线程数number of thread in per block
一个核函数的全部线程块构成一个网格grid线程块的个数称为网格大小grid size
每个线程块中含有相同数目的线程该数目称为线程块大小block size
所以,核函数的总的线程数即 网格大小*线程块大小:
hello_from_gpu<<<grid size, block size>>>
调用核函数后,调用 CUDA 运行时 API 函数,同步主机和设备:
cudaDeviceSynchronize();
核函数中调用输出函数,输出流是先存放在缓冲区的,而缓冲区不会自动刷新。
------
## CUDA 的线程组织
核函数的总线程数必须至少等于计算核心数时才有可能充分利用 GPU 的全部计算资源。
hello_from_gpu<<<2, 4>>>
网格大小是2线程块大小是4总线程数即8。核函数中代码的执行方式是 “单指令-多线程”,
即每个线程执行同一串代码。
从开普勒架构开始,最大允许的线程块大小是 2^10 (1024),最大允许的网格大小是 2^31 - 1一维网格
线程总数可以由两个参数确定:
1. gridDim.x, 即网格大小;
2. blockDim.x, 即线程块大小;
每个线程的身份可以由两个参数确定:
1. blockIdx.x, 即一个线程在一个网格中的线程块索引,[0, gridDm.x);
2. threadIdx.x, 即一个线程在一个线程块中的线程索引,[0, blockDim.x);
网格和线程块都可以拓展为三维结构(各轴默认为 1
1. 三维网格 grid_size(gridDim.x, gridDim.y, gridDim.z);
2. 三维线程块 block_size(blockDim.x, blockDim.y, blockDim.z);
相应的,每个线程的身份参数:
1. 线程块ID (blockIdx.x, blockIdx.y, blockIdx.z);
2. 线程ID (threadIdx.x, threadIdx.y, threadIdx.z);
多维网格线程在线程块上的 ID
tid = threadIdx.z * (blockDim.x * blockDim.y) // 当前线程块上前面的所有线程数
+ threadIdx.y * (blockDim.x) // 当前线程块上当前面上前面行的所有线程数
+ threadIdx.x // 当前线程块上当前面上当前行的线程数
多维网格线程块在网格上的 ID:
bid = blockIdx.z * (gridDim.x * gridDim.y)
+ blockIdx.y * (gridDim.x)
+ blockIdx.x
一个线程块中的线程还可以细分为不同的 **线程束thread warp**,即同一个线程块中
相邻的 warp_size 个线程(一般为 32
对于从开普勒架构到图灵架构的 GPU网格大小在 x, y, z 方向的最大允许值为 2^31 - 1, 2^16 - 1, 2^16 -1
线程块大小在 x, y, z 方向的最大允许值为 1024 1024 64同时要求一个线程块最多有 1024 个线程。
------
## CUDA 的头文件
CUDA 头文件的后缀一般是 “.cuh”同时同时可以包含c/cpp 的头文件 “.h”、“.hpp”采用 nvcc 编译器会自动包含必要的 cuda 头文件,
<cuda.h>, <cuda_runtime.h>同时前者也包含了c++头文件 <stdlib.h>
------
## 使用 nvcc 编译 CUDA 程序
nvcc 会先将全部源代码分离为 主机代码 和 设备代码;主机代码完整的支持 c++ 语法,而设备代码只部分支持。
nvcc 会先将设备代码编译为 PTXparrallel thread execution伪汇编代码再将其编译为二进制 cubin目标代码。
在编译为 PTX 代码时,需要选项 `-arch=compute_XY` 指定一个虚拟架构的计算能力;在编译为 cubin 代码时,
需要选项 `-code=sm_ZW` 指定一个真实架构的计算能力,以确定可执行文件能够使用的 GPU。
真实架构的计算能力必须大于等于虚拟架构的计算能力,例如:
-arch=compute_35 -code=sm_60 (right)
-arch=compute_60 -code=sm_35 (wrong)
如果希望编译出来的文件能在更多的GPU上运行则可以同时指定多组计算能力例如
-gencode arch=compute_35, code=sm_35
-gencode arch=compute_50, code=sm_50
-gencode arch=compute_60, code=sm_60
此时编译出来的可执行文件将包含3个二进制版本称为 **胖二进制文件fatbinary**
同时nvcc 有一种称为 **实时编译just-in-time compilation**机制可以在运行可执行文件时从其中保留的PTX
代码中临时编译出一个 cubin 目标代码。因此, 需要通过选项 `-gencode arch=compute_XY, code=compute_XY`
指定所保留 PTX 代码的虚拟架构, 例如:
-gencode arch=compute_35, code=sm_35
-gencode arch=compute_50, code=sm_50
-gencode arch=compute_60, code=sm_60
-gencode arch=compute_70, code=compute_70
于此同时nvcc 编译有一个简化的编译选项 `-arch=sim_XY`,其等价于:
-gencode arch=compute_XY, code=sm_XY
-gencode arch=compute_XY, code=compute_XY
关于 nvcc 编译器的更多资料: [nvcc](https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html)。
------
## 显卡架构和计算能力
1. 费米 Fermicuda 3.2~cuda 8
SM20 or SM_20, compute_30
GeForce 400, 500, 600, GT-630.
CUDA 10 以后就完全不支持了。
2. 开普勒 Keplercuda 5~cuda 10
SM30 or SM_30, compute_30
GeForce 700, GT-730
支持了统一内存模型编程
SM35 or SM_35, compute_35
Tesla K40.
支持动态并行化。
SM37 or SM_37, compute_37
Tesla K80.
增加了一些寄存器。
CUDA 11 以后就完全不支持了。
2. 麦克斯韦 MaxwellCUDA 6~CUDA 11
SM50 or SM_50, compute_50
Tesla/Quadro M 系列
SM52 or SM_52, compute_52
Quadro M6000 , GeForce 900, GTX-970, GTX-980, GTX Titan X
SM53 or SM_53, compute_53
Tegra (Jetson) TX1 / Tegra X1, Drive CX, Drive PX, Jetson Nano
cuda 11 以后彻底不支持
4. 帕斯卡 Pascal (CUDA 8 ~今)
SM60 or SM_60, compute_60
Quadro GP100, Tesla P100, DGX-1 (Generic Pascal)
SM61 or SM_61, compute_61
GTX 1080, GTX 1070, GTX 1060, GTX 1050, GTX 1030, Titan Xp, Tesla P40, Tesla P4, Discrete GPU on the NVIDIA Drive PX2
SM62 or SM_62, compute_62
Integrated GPU on the NVIDIA Drive PX2, Tegra (Jetson) TX2
5. 伏特 Volta (CUDA 9 ~今)
SM70 or SM_70, compute_70
DGX-1 with Volta, Tesla V100, GTX 1180 (GV104), Titan V, Quadro GV100
SM72 or SM_72, compute_72
Jetson AGX Xavier, Drive AGX Pegasus, Xavier NX
6. 图灵Turing (CUDA 10 ~今)
SM75 or SM_75, compute_75
GTX/RTX Turing GTX 1660 Ti, RTX 2060, RTX 2070, RTX 2080, Titan RTX, Quadro RTX 4000, Quadro RTX 5000, Quadro RTX 6000, Quadro RTX 8000, Quadro T1000/T2000, Tesla T4
7. 安培Ampere (CUDA 11 ~今)
SM80 or SM_80, compute_80
NVIDIA A100 (不再用特斯拉Tesla做名字了 GA100), NVIDIA DGX-A100
SM86 or SM_86, compute_86 (from CUDA 11.1 onwards)
Tesla GA10x, RTX Ampere RTX 3080, GA102 RTX 3090, RTX A6000, RTX A40
8. 哈珀Hopper (CUDA 12 计划中)
SM90 or SM_90, compute_90
NVIDIA H100 (GH100)