CUDA系列:编程实践3

102 阅读4分钟

cuda程序demo

#include <stdio.h>  // C头文件

__global__ void hello_from_gpu() // __global__ 关键词表示gpu核函数
{
    printf("Hello World from the the GPU\n");
}


int main(void)
{
    hello_from_gpu<<<1, 1>>>();  // 1*1 个线程
    cudaDeviceSynchronize();  // 同步代码,cpu、gpu协同

    return 0;
}

编译命令,nvcc等价于g++

nvcc test.cu -o test

核函数

kernel函数GPU进行并行执行(自发并行执行,硬件支持) 核函数与C++函数区别:

(1)限定词 global
(2)返回值void

注意事项:

(1)核函数只能访问GPU内存(即设备显存)
(2)核函数不支持变长参数
(3)核函数不支持静态变量
(4)核函数不能使用函数指针
(5)核函数具有异步性(解释:CPU与GPU异构,CPU发起核函数调用,必须显式调用同步函数)

CUDA线程模型

  • 01 线程模型结构 grid 网格(核函数启动产生的所有线程)、block线程块(一组线程)。线程是GPU编程最小单位。 配置线程 <<<grid_size, block_size>>>

  • 02 线程组织管理 一维线程模型
    线程在核函数中有一个唯一标识。该标识由参数<<<grid_size, block_size>>>确定。对于一维线程模型:gridDim.x等于grid_size,blockDim.x等于block_size。

线程索引(一维) blockIdx.x 指定一个线程在网格中线程块的索引值; threadIdx.x 指定一个线程在线程块中的线程索引值; 对于一维线程模型,线程唯一标识:

Idx = threadIdx.x + blockIdx.x * blockDim.x

线程索引(多维) dim3类型变量:gridDim、blockDim

定义网格、线程块:

dim3 gridDim(3,2,1) dim3 blockDim(3,2,1)

线程全局索引计算

grid 可以是一维、二维、三维,block可以是一维、二维、三维,共有9种组合。

一维网格 一维线程块

dim3 gridDim(4); dim3 blockDim(8);

计算方式: int id = blockIdx.x * blockDim.x + threadIdx.x;

二维网格 二维线程块

dim3 gridDim(2, 2); dim3 blockDim(4, 4);

计算方式:

int bid = blockId.x + blockId.y * gridDim.x; // 计算线程块索引 int tid = threadIdx.y * blockDim.x + threadIdx.x; // 计算线程索引 int id = bid * (blockDim.x * blockDim.y) + tid; // 线程块索引+线程索引 -> 线程在grid中唯一索引

三维网格 三维线程块

dim3 gridDim(2, 2, 2); dim3 blockDim(4, 4, 2);

int bid = blockId.x + blockId.y * gridDim.x + blockIdx.z * gridDim.x * gridDim.y int tid = threadIdx.z * blockDim.x * blockDim.y + threadIdx.z * blockDim.x + threadIdx.x int id = bid * (blockDim.x * blockDim.y * blockDim.z) + tid

GPU架构与计算能力

计算能力:标识计算能力版本号;X.Y

CUDA12.2 文档:

主计算能力架构
sm_80,sm_86,sm_87Ampere GPU arch
sm_90,sm_90aHopper support

注意:

GPU架构Tesla 与 型号Tesla表示的含义不同,型号Tesla设备的架构可以是MaxWell、Tesla,这里容易迷惑。

GPU性能:计算能力与性能没有关系,性能(浮点运算峰值、显存容量、带宽)

GPU型号计算能力显存容量带宽浮点运算峰值
Tesla V1007.032GB900GB/s7(14) TFLOPS
GeForce RTX 20707.58GB448GB/s0.2(6.5) TFLOPS

CUDA矩阵加法程序

CUDA程序基本框架

#include <头文件>

__global__ void 函数名(参数...)
{
    核函数实现
}


int main(void)
{
    设置GPU设备
    分配Host、Device内存
    初始化Host中数据
    数据从Host复制到Device
    调用Kernel,在Device中执行计算
    计算结果数据从Device传给Host
    释放Host、Device内存
}

获取Device数量

int iDeviceCount = 0; cudaGetDeviceCount(&iDeviceCount);

设置Device

int iDev = 0; cudaSetDevice(iDev);

__host__​__device__​cudaError_t cudaGetDeviceCount ( int* count )  // 在host、device都可执行
__host__​cudaError_t cudaSetDevice ( int  device ) // 只能在host执行

内存管理 内存分配、数据拷贝、内存初始化、内存释放

标准C内存管理函数CUDA内存管理函数
malloccudaMalloc
memcpycudaMemcpy
memsetcudaMemset
freecudaFree
// 内存分配
float *fpHost_A;
cudaMalloc((float **)&fpHost_A, nBytes);  // 传入双重指针

// copy
cudaMemcpy(Device_A, Host_A, nBytes, cudaMemcpyHostToDevice )

// init
cudaMemset(fpDevice_A, 0, nBytes);

// free
cudaFree(pDevice_A)

device、kernel、host function

  • device function(设备函数)

只能在GPU设备上执行,只能被核函数或其他设备函数调用,__device__修饰

  • kernel function(设备函数)

由主机调用,在GPU设备执行;global__修饰,不能使用__device、__host__修饰符

  • host function(设备函数)

主机端C++函数__host__可省略,可使用__device__、__host__同时修饰减少冗余,编译器分别编译函数用于主机、设备

事件event计时

cuda 事件可为Host代码、Device代码计时。

cudaEvent start, stop;
cudaEventCreate(&start, __FILE__, __LINE__);
cudaEventCreate(&stop, __FILE__, __LINE__);
cudaEventQuery(start); 

多维数组线程模型

数据存储方式:先存储第一行,再第二行...C/C++ 以行为主,Matlab是以列为主。

二维网格、二维线程块 线程与二维矩阵映射关系:
ix 表示全局内存中x维度索引值,iy表示y维度索引值

ix = blockIdx.x * blockDim.x + threadIdx.x; iy = blockIdx.y * blockDim.y + threadIdx.y;

idx = iy * nx + ix //线程全局索引

二维网格、一维线程块

ix = blockIdx.x * blockDim.x + threadIdx.x; iy = threadIdx.y;

idx = iy * nx + ix //线程全局索引