【CUDA笔记】01-入门简介

引言

本系列主要是对与 Cuda 入门官方课程的整理与小结。

在开始这门课程之前,已经有大概认知,通过 Cuda, 可以利用 GPU 来加速特定计算任务 。

本节课的主要目的 是了解 Cuda 程序的一些基础概念,以及如何编写第一个入门的 Cuda 程序
课程主页: https://www.olcf.ornl.gov/cuda-training-series/

课程练习代码下载地址: https://github.com/olcf/cuda-training-series

课程视频b站转载地址:
https://www.bilibili.com/video/BV1yr4AzKETn?spm_id_from=333.788.videopod.sections&vd_source=228fe41b4a8c01494b03cb2a38fd1be4

Cuda API 文档: https://docs.nvidia.com/cuda/cuda-runtime-api/

1. CUDA 中的一些基础概念

1.1 Host and Device

Host: The CPU and its memory (host memory)
Device: The GPU and its memory (device memory)

(Cuda 提供的 API 会有与这两个概念相关联的一些接口)

1.2 GPU Kernels: DEVICE

(也就是常说的核函数)
(1)函数实现 在 GPU 上执行
(2)在 CPU 这边的代码发起调用
(3)函数以 关键字 "global" 进行修饰

复制代码
__global__ void vecAdd(const float *A, const float *B, float *C, int vecDim)
{

    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < vecDim)
    {
        C[idx] = A[idx] + B[idx];
    }
}

4.CPU 这边会以如下格式发起调用

复制代码
kernelFunc<<<1,1>>>(param1, param2,...);

1.3 Grid, Block, Threads

基于 GPU 的架构设计, CUDA 这边设计出了 Thread, Block, Grid 概念(预告:后面还会有一个 Wrap 的概念)

1个 Grid 中可以有 M 个 Block, 1个 Block 中可以有 N个 Thread, 对应核函数的调用就是

复制代码
kernelFunc<<<M,N>>>(param1, param2,...);

通过 Cuda 的内置变量

复制代码
blockIdx.x
threadIdx.x

可以在核函数中获取到某一个 block 中 某个 thread 的 id;

通过

复制代码
blockDim.x

可以知道 block 在 x 方向上的尺寸

继而在 blockDim.y block.Dim.z 皆为 1 的情况下,通过公式

复制代码
int index = threadIdx.x + blockDim.x * blockIdx.x;

可以在核函数中,计算出所有执行当前核函数的线程的id。

block 与 thread 的组成关系大致如下。

1.4 内存管理

cudaMalloc

功能:在设备(GPU)上分配内存。

复制代码
(__host__​__device__)​cudaError_t cudaMalloc ( void** devPtr, size_t size )

参数

devPtr:指向设备指针的指针,分配后指向设备内存。

size:要分配的字节数。

返回值

cudaSuccess 表示成功,其他为错误码。

示例

复制代码
float* d_A = nullptr;
cudaMalloc((void**)&d_A, 100 * sizeof(float));

cudaFree

功能:释放设备(GPU)上分配的内存。

复制代码
(__host__​__device__)cudaError_t cudaFree(void *devPtr);

参数

devPtr:要释放的设备内存指针。(注意, 这里和上面 Malloc 是不一样的)

返回值

cudaSuccess 表示成功,其他为错误码。

示例

复制代码
cudaFree(d_A);

cudaMemcpy

功能:在主机(CPU)和设备(GPU)之间或设备内部拷贝内存。

复制代码
(__host__) cudaError_t cudaMemcpy ( void* dst, const void* src, size_t count, cudaMemcpyKind kind )

参数

dst:目标内存地址(主机或设备)。

src:源内存地址(主机或设备)。

count:拷贝的字节数。

kind::拷贝类型,常用有:

  • cudaMemcpyHostToDevice(主机到设备)
  • cudaMemcpyDeviceToHost(设备到主机)
  • cudaMemcpyDeviceToDevice(设备到设备)
  • cudaMemcpyHostToHost(主机到主机)

2. 以 CUDA 实现 向量加法 为例进行介绍

基于上面的基础概念的介绍, 我们可以 初步拼凑出 Cuda实现向量加法程序

复制代码
#include <stdio.h>
#include <stdlib.h>

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

// error checking macro
#define cudaCheckErrors(msg)                                   \
    do                                                         \
    {                                                          \
        cudaError_t __err = cudaGetLastError();                \
        if (__err != cudaSuccess)                              \
        {                                                      \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                    msg, cudaGetErrorString(__err),            \
                    __FILE__, __LINE__);                       \
            fprintf(stderr, "*** FAILED - ABORTING\n");        \
            exit(1);                                           \
        }                                                      \
    } while (0)

const int DSIZE = 4096;
const int block_size = 256; // CUDA maximum is 1024
// -------- 向量加法的 kernel: C = A + B -------- //
__global__ void vecAdd(const float *A, const float *B, float *C, int ds)
{
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < ds)
    {
        C[idx] = A[idx] + B[idx];
    }
}

int main()
{
    // 1.初始化 参与计算 与 获得结果 的向量
    float *h_A, *h_B, *h_C, *d_A, *d_B, *d_C;
    h_A = new float[DSIZE]; // allocate space for vectors in host memory
    h_B = new float[DSIZE];
    h_C = new float[DSIZE];
    for (int i = 0; i < DSIZE; i++)
    { // initialize vectors in host memory
        h_A[i] = rand() / (float)RAND_MAX;
        h_B[i] = rand() / (float)RAND_MAX;
        h_C[i] = 0;
    }
     // 2.为参与计算 与 获得结果 的向量 在 GPU Memory 上分配空间
    cudaMalloc((void**)&d_A, DSIZE * sizeof(float)); // allocate device space for vector A
    cudaMalloc((void**)&d_B, DSIZE * sizeof(float)); // allocate device space for vector B
    cudaMalloc((void**)&d_C, DSIZE * sizeof(float)); // allocate device space for vector C
    cudaCheckErrors("cudaMalloc failure");   // error checking
    
    // 3.将数据拷贝到 GPU 上
    cudaMemcpy(d_A, h_A, DSIZE * sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, DSIZE * sizeof(float), cudaMemcpyHostToDevice);
    cudaCheckErrors("cudaMemcpy H2D failure");
    
    // 4.调用核函数进行计算
    vecAdd<<<(DSIZE + block_size - 1) / block_size, block_size>>>(d_A, d_B, d_C, DSIZE);
    cudaCheckErrors("kernel launch failure");
    
    // 5.将计算结果从 GPU Memory 拷贝会 CPU 侧
    cudaMemcpy(h_C, d_C, DSIZE * sizeof(float), cudaMemcpyDeviceToHost);
    cudaCheckErrors("kernel execution failure or cudaMemcpy H2D failure");
    
    // 6.打印计算结果
    printf("A[0] = %f\n", h_A[0]);
    printf("B[0] = %f\n", h_B[0]);
    printf("C[0] = %f\n", h_C[0]);

    // 7. 释放 GPU Memory
    cudaFree(d_A);
    cudaFree(d_B);
    cudaFree(d_C);

    // 8. 释放 CPU 的 Memory
    delete[] h_A;
    delete[] h_B;
    delete[] h_C;
    return 0;
}

3.本节作业实现

本节的作业让我们尝试修改几行核函数的代码实现 矩阵乘法。 实现该完整程序之前还需了解一些前置概念

3.1 前置概念

前面介绍过的 核函数常用的调用形式如下,

复制代码
kernelFunc<<<M,N>>>(param1, param2,...);

<<<,>>> 中输入的是两个整型的变量

实际上, 这两个变量也可以传入cuda 定义的 类型 dim3, 如

复制代码
dim3 block(4, 4); // dim3 variable holds 3 dimensions
dim3 grid(4, 4);
kernelFunc<<<grid,block>>>(param1, param2,...);

以达到将block 与 thread 以平面方式组织的效果。 此时在核函数中可通过如下方式来获取线程的id 的行号与列号。

复制代码
int idx = threadIdx.x + blockIdx.x * blockDim.x;
int idy = threadIdx.y + blockIdx.y * blockDim.y;

3.2 练习实现 矩阵乘法

至此基于前置只是以及矩阵乘法, 可以得到下面计算矩阵乘法的例子

复制代码
/**
 * 4096 x 4096 矩阵乘法的例子
 */
#include <stdio.h>
#include <stdlib.h>

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

// these are just for timing measurments
#include <time.h>

// error checking macro
#define cudaCheckErrors(msg)                             \
  do                                                     \
  {                                                      \
    cudaError_t __err = cudaGetLastError();              \
    if (__err != cudaSuccess)                            \
    {                                                    \
      fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
              msg, cudaGetErrorString(__err),            \
              __FILE__, __LINE__);                       \
      fprintf(stderr, "*** FAILED - ABORTING\n");        \
      exit(1);                                           \
    }                                                    \
  } while (0)

const int DSIZE = 4096;
const int block_size = 16; // CUDA maximum is 1024 *total* threads in block
const float A_val = 1.0f;
const float B_val = 2.0f;

// matrix multiply (naive) kernel: C = A * B
__global__ void mmul(const float *A, const float *B, float *C, int ds)
{

  int idx = threadIdx.x + blockDim.x * blockIdx.x; // create thread x index
  int idy = threadIdx.y + blockDim.y * blockIdx.y; // create thread y index

  if ((idx < ds) && (idy < ds))
  {
    float temp = 0;
    for (int i = 0; i < ds; i++)
    {
        temp += A[idy * ds + i] * B[i * ds + idx]; // dot product of row and column
    }
    C[idy * ds + idx] = temp;
  }
}

int main()
{

  float *h_A, *h_B, *h_C, *d_A, *d_B, *d_C;

  // these are just for timing
  clock_t t0, t1, t2;
  double t1sum = 0.0;
  double t2sum = 0.0;

  // start timing
  t0 = clock();

  h_A = new float[DSIZE * DSIZE];
  h_B = new float[DSIZE * DSIZE];
  h_C = new float[DSIZE * DSIZE];
  for (int i = 0; i < DSIZE * DSIZE; i++)
  {
    h_A[i] = A_val;
    h_B[i] = B_val;
    h_C[i] = 0;
  }

  // Initialization timing
  t1 = clock();
  t1sum = ((double)(t1 - t0)) / CLOCKS_PER_SEC;
  printf("Init took %f seconds.  Begin compute\n", t1sum);

  // Allocate device memory and copy input data over to GPU
  cudaMalloc(&d_A, DSIZE * DSIZE * sizeof(float));
  cudaMalloc(&d_B, DSIZE * DSIZE * sizeof(float));
  cudaMalloc(&d_C, DSIZE * DSIZE * sizeof(float));
  cudaCheckErrors("cudaMalloc failure");
  
  cudaMemcpy(d_A, h_A, DSIZE * DSIZE * sizeof(float), cudaMemcpyHostToDevice);
  cudaMemcpy(d_B, h_B, DSIZE * DSIZE * sizeof(float), cudaMemcpyHostToDevice);
  cudaCheckErrors("cudaMemcpy H2D failure");
  // Cuda processing sequence step 1 is complete

  // Launch kernel
  dim3 block(block_size, block_size); // dim3 variable holds 3 dimensions
  dim3 grid((DSIZE + block.x - 1) / block.x, (DSIZE + block.y - 1) / block.y);
  mmul<<<grid, block>>>(d_A, d_B, d_C, DSIZE);
  cudaCheckErrors("kernel launch failure");

  // Cuda processing sequence step 2 is complete

  // Copy results back to host
  cudaMemcpy(h_C, d_C, DSIZE * DSIZE * sizeof(float), cudaMemcpyDeviceToHost);

  // GPU timing
  t2 = clock();
  t2sum = ((double)(t2 - t1)) / CLOCKS_PER_SEC;
  printf("Done. Compute took %f seconds\n", t2sum);

  // Cuda processing sequence step 3 is complete

  // Verify results
  cudaCheckErrors("kernel execution failure or cudaMemcpy H2D failure");
  for (int i = 0; i < DSIZE * DSIZE; i++)
    if (h_C[i] != A_val * B_val * DSIZE)
    {
      printf("mismatch at index %d, was: %f, should be: %f\n", i, h_C[i], A_val * B_val * DSIZE);
      return -1;
    }
  printf("Success!\n");

  // free device memory
  cudaFree(d_A);
  cudaFree(d_B);
  cudaFree(d_C);

  // free host memory
  delete[] h_A;
  delete[] h_B;
  delete[] h_C;
  return 0;
}
相关推荐
开开心心就好2 小时前
微软官方出品:免费数据恢复工具推荐
网络·笔记·microsoft·pdf·word·音视频·symfony
little_xianzhong4 小时前
三个常听到的消息/中间件MQTT RabbitMQ Kafka
java·笔记·中间件·消息队列
ysa0510306 小时前
虚拟位置映射(标签鸽
数据结构·c++·笔记·算法
songyuc6 小时前
《A Bilateral CFAR Algorithm for Ship Detection in SAR Images》译读笔记
人工智能·笔记·计算机视觉
01100001乄夵7 小时前
第二课:时序逻辑入门-零基础FPGA闯关教程
经验分享·笔记·学习方法
摇滚侠8 小时前
Spring Boot3零基础教程,Reactive-Stream 发布订阅写法,笔记104 笔记105
java·spring boot·笔记
循环过三天14 小时前
3.4、Python-集合
开发语言·笔记·python·学习·算法
昌sit!16 小时前
Linux系统性基础学习笔记
linux·笔记·学习
没有钱的钱仔16 小时前
机器学习笔记
人工智能·笔记·机器学习