Cuda start
Cuda
基础知识
结构
在硬件上,一个 GPU 分为多个 SM,每个 SM 包含本地寄存器文件、统一数据缓存(L1 缓存和共享内存)和若干个执行计算的单元
执行一个 GPU 程序先会从 CPU 启动 Kernel (核函数),每一个函数称为一个 Grid,分为不同的 Block,每个 Block 执行多个线程 Thread,一个 Grid 使用的 Block 和每个 Block 中含有的 Thread 可以通过编程自定义 <<<Block,ThreadPerBlock>>>,Block 之间无法同步和共享内存(无法确定执行顺序),Cluster 将不同 Block 组织起来使其可以通信和访问共享内存
GPU 的执行单位为 Warp (线程束),它将线程分为每 32 为一组,每组执行完全相同的代码(无分支,对于不同分支屏蔽多余分支只执行一个分支);将 Block 分到多个 SM 中进行计算(一个 SM 可以有多个 Block,但是一个 Block 只能分配到同一个 SM 中进行计算),同一 Cluster 的 Block被分配到同一 GPC 中
Tile and SIMT
SIMT模型中,程序员按“线程”思考和编程,需要手动管理每个线程的数据访问和控制流,因此会出现Warp Divergence等问题。
Tile Programming模型中,程序员按“数据块(tile)”思考和编程,整个block作为一个整体执行统一控制流,编译器自动把tile操作映射到线程,因此不再存在Warp Divergence的概念。
在 Tile 编程中,只需关注数据在数据块中如何运算,由编译器负责线程的组织。在这种编程方式下有两种数据类型,一个为数组,一个为 Tile (瓦片),瓦片不可变(对瓦片的操作只会产生新的瓦片),每个维度必须是 2 的幂并且编译期可知(不可动态);数组和瓦片之间的数据可以进行移动,将数组分为多个小块,自带边界策略将超过边界的内容自动填充为 0
Tile 和 SIMT 可在同一程序中同时出现
GPU内存
为了将一个线程块调度到 SM 上,每个线程所需的寄存器总数乘以线程块中的线程数必须小于或等于 SM 中可用的寄存器数量
每个流式多处理器(SM)都包含一个 L1 缓存,该缓存是统一数据缓存的一部分。更大的 L2 缓存则由 GPU 内所有 SM 共享
CPU 内存只能由 CPU 代码访问,而 GPU 内存只能由运行在 GPU 上的内核访问。CUDA API 用于在 CPU 和 GPU 之间复制内存,被用来在正确的时间将数据显式复制到正确的内存位置
Cubin
一个 Cuda 二进制文件包含 CPU 机器码和一个 fatbin 容器,这个容器中包含针对不同版本的 SM 的 cubin 和 RTX,当该二进制程序运行时会从 fatbin 中选择最适合 GPU 的二进制文件 cubin,或者使用 RTX JIT
GPU编程(C++)
头文件
内核函数
__global__ void (function name)(argv){ |
内核函数的启动可以由三尖括号
__global__ void vecAdd(float* a,float*b ,float* c){ |
内核函数启动后,默认情况下 CPU 不会等待 GPU 执行完毕
使用
cudaDeviceSynchronize()让CPU等待GPU执行完毕
__syncthreads()使块内线程等待直到所有线程都到达该点
线程块
可以通过变量设置块数和每块线程数
dim3 grid(16,16); // block:256 |
启动配置中未指定的维度将默认为 1
为了精确控制线程的执行,我们具有一些内建变量
- threadIdx
- blockDim
- blockIdx
- gridDim
这些都是三分量向量(dim3)
对于已知总线程数和每块线程数,要确定块数,可以使用
>
>int thread = 256;
>int block - cuda::ceil_div(totalThread,thread);
内存分配
float* A = nullptr; |
为了提升性能
float* A = nullptr; |
cudaMemcpyDefault会在以下类型中选择
cudaMemcpyHostToDevicecudaMemcpyDeviceToHostcudaMemcpyDeviceToDevice
错误处理
每个 CUDA API 都会返回一个枚举类型 cudaError_t 的值,在生产应用中,最佳实践是始终检查并管理
cudaGetLastError 返回当前错误状态,然后将其重置为 cudaSuccess
cudaPeekAtLastError 返回错误状态而不重置它
实际上,使用三重尖括号符号启动内核不会返回 cudaError_t
最好手动检查
vecAdd<<<blocks, threads>>>(devA, devB, devC); |
cudaError_t类型的值cudaErrorNotReady(可能由cudaStreamQuery和cudaEventQuery返回)不被视为错误,也不会被cudaPeekAtLastError或cudaGetLastError报告。
CUDA_LOG_FILE=cudaLog.txt 的设置会让错误记录在日记中
$ env CUDA_LOG_FILE=cudaLog.txt ./test |
Device and Host
对于函数
__global__说明内核函数入口点__device__说明只能由内核函数调用__hots__说明只能由CPU调用
可以同时使用 __device__ 和 __host__
对于变量
__device____constant____managed____shared__- 在
__device__或__global__函数内部声明且未使用说明符则优先分配寄存器
Cluster
块级同步
cluster.sync() |
可访问分布式共享内存
函数声明
__global__ void __cluster_dims__(2, 1, 1) cluster_kernel(float *input, float* output) |
编译宏
CUDA_ARCH
__host__ __device__ |
GPU编程(python)
与常规 python 文件相比
需要添加包
from numba import cuda |
在由 GPU 运行的函数前加上一段标识
|
调用函数时使用 [,] 代替C++中的 <<<,>>>
一样具有索引
- cuda.threadIdx.[xyz]
- cuda.blockDim.[xyz]
- cuda.blockIdx.[xyz]
- cuda.gridDim.[xyz]
可以仿照在C++常见的计算线程索引的方式
idx = cuda.threadIdx.x+cuda.blockIdx.x*cuda.blockDim.x也可以使用一个简写语法
idx = cuda.grid(1)






