【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;
}
相关推荐
Yawesh_best21 小时前
告别系统壁垒!WSL+cpolar 让跨平台开发效率翻倍
运维·服务器·数据库·笔记·web安全
Ccjf酷儿1 天前
操作系统 蒋炎岩 3.硬件视角的操作系统
笔记
习习.y1 天前
python笔记梳理以及一些题目整理
开发语言·笔记·python
在逃热干面1 天前
(笔记)自定义 systemd 服务
笔记
DKPT1 天前
ZGC和G1收集器相比哪个更好?
java·jvm·笔记·学习·spring
QT 小鲜肉1 天前
【孙子兵法之上篇】001. 孙子兵法·计篇
笔记·读书·孙子兵法
星轨初途1 天前
数据结构排序算法详解(5)——非比较函数:计数排序(鸽巢原理)及排序算法复杂度和稳定性分析
c语言·开发语言·数据结构·经验分享·笔记·算法·排序算法
QT 小鲜肉1 天前
【孙子兵法之上篇】001. 孙子兵法·计篇深度解析与现代应用
笔记·读书·孙子兵法
love530love1 天前
【笔记】ComfUI RIFEInterpolation 节点缺失问题(cupy CUDA 安装)解决方案
人工智能·windows·笔记·python·插件·comfyui
愚戏师1 天前
MySQL 数据导出
数据库·笔记·mysql