CUDA编程:概述

CUDA编程

  • GPU的架构
  • 每个GPU由N个SM组成,1个SM分为2个SMP,1个SMP有16个DPUnit和32个CudaCore以及一些特殊函数处理模块
    • 比如说, RTX 2080Ti,具有68个SM,总共有 68 x 64 个CudaCore
    • SM全称(Streaming Multiprocessor)
    • WarpSize一般为32(这个大部分都这个数)
      • 最小调度单位,也就是最小调度单位是一个SMP
      • 如果启动线程数小于32,他依旧启动32线程,仅仅是其他线程处于非激活状态deactive
    • 内存,分为GlobalMemory、SharedMemory、缓存、寄存器等
      • 理解内存的排布,是优化编程的基础
      • 距离Core越近的内存,速度越快
    • 架构名称
      • 不同的架构,具有不同的显卡硬件设置,比如说游戏级更注重渲染,工业级更注重科学计算或者是长期运行(所以散热等更注重)。
      • 比如说,工业级,会增加例如TensorCore、DLA、FP16、INT8、INT4等NN的支持。而这些,游戏级别大部分没有
      • 架构一般有:图灵(Turing、游戏级)、帕斯卡(PASCAL,游戏级)、开普勒(Kepler,游戏级)、麦克斯维尔(Maxwell,游戏级)、VOLTA(工业级)
  • 编程上
    • 线程的抽象形式,是使用gridDim和blockDim指定你需要启动的线程数
      • gridDim是用x、y、z三个维度表示,其取值具有最大上限,例如RTX2080Ti上是(deviceQuery可以查到):2147483647, 65535, 65535
      • blockDim是x、y、z三个维度表示,例如RTX2080Ti上是:1024, 1024, 64
      • 因此对于线程数量的计算,是可以认为是6个维度的张量来表示。线程数,则是6个维度乘积
      • 对于核函数中,position的计算,使用blockIdx和threadIdx做索引(共6个索引),得到当前线程在线程数中的绝对id
        • 计算形式是
          • 假设shape是 A x B x C x D x E x F
          • 假设index是 a x b x c x d x e x f
          • position = ((((a x B + b) x C + c) x D + d) x E + e) x F + f
        • 对于grid和block的例子,其shape为:
          • gridDim.z
          • gridDim.y
          • gridDim.x
          • blockDim.z
          • blockDim.y
          • blockDim.x
        • 对于grid和block的例子,其index为:
          • blockIdx.z
          • blockIdx.y
          • blockIdx.x
          • threadIdx.z
          • threadIdx.y
          • threadIdx.x
        • 对于常规使用时,通常只用到2个维度
          • gridDim = dim3(blocks)
            • dim3的构造函数是dim3(int x, int y=1, int z=1)
            • 所以gridDim.x = blocks,gridDim.y = 1, gridDim.z = 1
          • blockDim = dim3(threads)
            • dim3的构造函数是dim3(int x, int y=1, int z=1)
            • 所以blockDim.x = threads,blockDim.y = 1, blockDim.z = 1
          • 计算position时:
            • 其shape为:
              • gridDim.z = 1
              • gridDim.y = 1
              • gridDim.x = blocks
              • blockDim.z = 1
              • blockDim.y = 1
              • blockDim.x = threads
            • 其index为:
              • blockIdx.z = 0
              • blockIdx.y = 0
              • blockIdx.x = u
              • threadIdx.z = 0
              • threadIdx.y = 0
              • threadIdx.x = v
            • position = u * threads + v = blockIdx.x * blockDim.x + threadIdx.x
        • threads的数量,要求会给warp_size的倍数
          • 通常二维时,可以固定为512,如果显卡比较差,可以给小。视情况可以给大
          • blocks = ceil(jobs / (float)threads)
    • 修饰符
      • __global__ 修饰,指host可以调用的函数,通常咱们认为是cuda核函数,通过kernel<<>>(args)启动
        • 传值和传地址
        • 传值不需要考虑复制到设备,即时你的值是一坨结构体(struct)
        • 传地址,如果需要在核函数中访问地址数据,则必须把地址指向的数据复制到显卡上。否则操作异常
      • __device__ 修饰,指device调用的函数
      • __host__ 修饰,主机函数,这个默认写的函数就是这个
    • static,是C语言语法,编译的符号,只在当前cpp中有效。不会参与全局的链接。如果不加static,默认是全局都会进行链接
    • 内存布局
      • opencv加载的图像,颜色空间格式是RGB的,内存排布是BGRBGRBGR。如果按照维度看,则是HWC
      • 平时训练,可以使用BGR或者RGB,这个没有要求
      • 颜色空间格式,有很多种:RGB、YUV、HSV、HSL
    • cuda流,cudaStream_t
      • 实现cuda核以及cuda操作pipline的一个任务队列,可以理解为一个线程+任务队列
      • 如果使用nullptr,则是默认流
      • cudaMemcpy,是同步复制,他是由cudaMemcpyAsync(nullptr)和cudaDeviceSynchronize()实现
      • cudaMemcpyAsync,是异步复制
        • 实际时候用时,避免采用同步,尽可能全部异步。这样能够极大的提高显卡使用率
      • cudaError_t cudaMemcpy(dst, src, size, kind)
        • dst,是目标指针
        • src,是来源指针
        • size,是复制的大小
        • kind,是复制的方式,cudaMemcpyHostToDevice、DeviceToHost、HostToHost、DeviceToDevice
      • cudaError_t cudaMalloc(void** ptr, size_t size)
        • ptr,是主机的二级指针,分配后的 地址,通过二级指针修改
          • 你想要的是,分配后的地址
          • 你还想要的是,如果出错了,错误代码给我
          • cuda规定了,函数的定义形式,返回值是错误
          • void* ptr = nullptr;
            • 在栈上开辟了8个字节空间,储存了nullptr
            • &ptr = 取ptr在栈的位置,设为P
            • cudaMalloc(&ptr, 100); 传给 cudaMalloc的是,ptr在栈上的位置P
              • cudaMalloc(void** ptr2, size_t size)
                • ptr2 = ptr在栈上的位置,P
                • ptr2 = 引用栈的P位置上,8个字节。解释为void 。此时*ptr2 值是 nullpter
                • G = malloc(size) 分配size个大小空间,并得到地址,这个地址假设是GPU的地址G
                • ptr2 = G 把G复制到ptr2中。*ptr2又是个引用 P位置的8个字节
                •         G也是8个字节,所以直接G复制到P上8个字节位置做填充 
                  
                • ptr就被修改了
        • size,是分配的大小
      • cudaEvent_t
        • 加入事件到流中,根据需要进行等待,或者统计时间
    • 原子操作,atomicAdd
      • 实现globalMemory或者sharedMemory上的内存加指定值,并返回旧值
      • 用来解决多线程异步操作同一个变量的计数和索引问题
        • yolov5的后处理用到
    • 内存释放,cudaFree

你可能感兴趣的:(CUDA编程,硬件架构,CUDA编程)