您的位置:首页 > 科技 > IT业 > 一文学会CUDA编程:深入了解CUDA编程与架构(一)

一文学会CUDA编程:深入了解CUDA编程与架构(一)

2024/11/18 21:48:38 来源:https://blog.csdn.net/laukal/article/details/140833238  浏览:    关键词:一文学会CUDA编程:深入了解CUDA编程与架构(一)

前言:

CUDA(Compute Unified Device Architecture,统一计算设备架构)是由NVIDIA公司开发的一种并行计算平台和编程模型。CUDA于2006年发布,旨在通过图形处理器(GPU)解决复杂的计算问题。在早期,GPU主要用于图像处理和游戏渲染,但随着技术的发展,其并行计算能力被广泛应用于科学计算、工程仿真、深度学习等领域。

CUDA的工作原理

CUDA的核心思想是将计算任务分配给GPU上的大量线程,这些线程可以并行地执行任务,从而实现高性能计算。CUDA将GPU划分为多个独立的计算单元,称为“流处理器”(Streaming Processor),这些流处理器可以独立地执行指令,互相加不干扰。

硬件层面

1、CUDA核心 (CUDA Core)

CUDA核心是执行线程计算的基本硬件单元。每个CUDA核心可以执行一个线程的计算任务。

图片

2、SM (Streaming Multiprocessor)

流多处理器 (SM) 是由多个CUDA核心组成的集成单元。每个SM负责管理和执行一个或多个线程块。SM内部有共享内存和缓存,用于加速数据访问和计算。

3、设备 (Device)

设备指的是整个GPU硬件。一个设备包含多个SM,能够处理大量并行计算任务。设备通过高带宽的内存和数据传输机制与主机(如CPU)进行数据交换。

图片

软件层面

1、线程 (Thread)

在CUDA编程中,线程是执行基本计算任务的最小单位。每个线程执行相同的程序代码,但可以处理不同的数据。

图片

2、线程块 (Thread Block)

线程块是由多个线程组成的集合。线程块中的线程可以共享数据,并且可以通过同步机制来协调彼此的工作。线程块的大小在程序执行时是固定的。

图片

3、网格 (Grid)

网格是由多个线程块组成的更大集合。网格中的所有线程块并行执行任务,网格的大小也在程序执行时固定。

图片

示例

实现两个向量相加 arr_c[] = arr_a[] +arr_b[]

#include <cuda.h>
#include <cuda_runtime_api.h>#include <cmath>
#include <iostream>#define CUDA_CHECK(call)                                           \{                                                              \const cudaError_t error = call;                            \if (error != cudaSuccess) {                                \fprintf(stderr, "Error: %s:%d, ", __FILE__, __LINE__); \fprintf(stderr, "code: %d, reason: %s\n", error,       \cudaGetErrorString(error));                    \exit(1);                                               \}                                                          \}__global__ void addKernel(float *pA, float *pB, float *pC, int size)
{int index = blockIdx.x * blockDim.x + threadIdx.x; // 计算当前数组中的索引if (index >= size)return;pC[index] = pA[index] + pB[index];
}int main()
{float a[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};float b[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};int arr_len = 16;float *dev_a, *dev_b, *dev_c;CUDA_CHECK(cudaMalloc(&dev_a, sizeof(float) * arr_len));CUDA_CHECK(cudaMalloc(&dev_b, sizeof(float) * arr_len));CUDA_CHECK(cudaMalloc(&dev_c, sizeof(float) * arr_len));CUDA_CHECK(cudaMemcpy(dev_a, a, sizeof(float) * arr_len, cudaMemcpyHostToDevice));CUDA_CHECK(cudaMemcpy(dev_b, b, sizeof(float) * arr_len, cudaMemcpyHostToDevice));int *count;CUDA_CHECK(cudaMalloc(&count, sizeof(int)));CUDA_CHECK(cudaMemset(count, 0, sizeof(int)));addKernel<<<arr_len + 512 - 1, 512>>>(dev_a, dev_b, dev_c, arr_len);float *output = (float *)malloc(arr_len * sizeof(float));CUDA_CHECK(cudaMemcpy(output, dev_c, sizeof(float) * arr_len, cudaMemcpyDeviceToHost));std::cout << " output add" << std::endl;for (int i = 0; i < arr_len; i++) {std::cout << " " << output[i];}std::cout << std::endl;return 0;
}

代码理解

addKernel<<<arr_len + 512 - 1, 512>>>

函数类型如下

addKernel<<<dim3 grid, dim3 block>>>

前面的表达等价于

addKernel<<<(dim3 grid(arr_len + 512 - 1), 1, 1), dim3 block(512, 1, 1)>>>

grid 与block 理解

假设只使用16个元素, arr_len =16

1、使用调整block的参数:

1.1只有x:

dim3 grid(1, 1, 1), block(arr_len, 1, 1); // 一个block里面有16个线程                   // 设置参数

图片

此时遍历的代码如下:

__global__ void addKernel(float *pA, float *pB, float *pC, int size){// block是一维的    int index = threadIdx.x; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

1.2 含有x, y

dim3 grid(1, 1, 1), block(8, 2, 1); //每个block x方向有8个线程,总共2组。 

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){  // block是二维的    int index = threadIdx.x + blockDim.x* threadIdx.y; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

2、更改grid 参数

2.1 只更改x方向的参数

dim3 grid(16, 1, 1), block(1, 1, 1);   //还有16个block, 每个block就一个线程                 // 设置参数

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){  // grid.x是一维的    int index = blockIdx.x; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

3、grid, block参数都改

3.1 grid block各改一个

dim3 grid(4, 1, 1), block(4, 1, 1) // 代码还有4个x方向block, 每个block x方向有4个线程

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){  // grid.x是一维的    int index = blockIdx.x*gridDim.x + threadIdx.x; // 计算当前数组中的索引    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

3.2 grid block更改两个

dim3 grid(2, 2, 1), block(2, 2, 1) // 代码还有2个X方向block,Y方向上有两组, 每个block x方向有2个线程, y方向上有两组

图片

__global__ void addKernel(float *pA, float *pB, float *pC, int size){      // 在第几个块中 * 块的大小 + 块中的x, y维度(几行几列)    int index = (blockIdx.y * gridDim.x + blockIdx.x) * (blockDim.x * blockDim.y) + threadIdx.y * blockDim.y + threadIdx.x;    if (index >= size)        return;    pC[index] = pA[index] + pB[index];}

总结

CUDA作为一种强大的并行计算平台和编程模型,极大地推动了高性能计算、深度学习等领域的快速发展。通过掌握CUDA,开发者可以充分利用GPU的并行计算能力,显著提升程序的运行效率和性能。无论是科学研究还是商业应用,CUDA都提供了广阔的可能性和机遇。

关注我的公众号auto_driver_ai(Ai fighting), 第一时间获取更新内容。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com