CUDA 基础指南(一): 环境配置与基础概念

环境配置

CUDA软件生态自顶向下结构大致如下:

  • Applications:
  • CuLibraries: CuBlas,CuDLA,TensorRT等
  • CUDA tool kit:
  • GPU Driver:
  • GPU devices: GTX3080, K80等
    请使用带有N卡的设备,不管是服务器,PC还是嵌入式开发板
    注意:nvidia主要站点均在国外,所以需要自备科学上网.
    无法达到科学上网或者下载速度过慢,可以先点击用浏览器下载,然后把浏览器下载中的下载链接复制到迅雷之类的下载器中下载.

    Windows环境配置

    安装驱动

  • 根据N卡类型,可以从国外站点下载对应驱动.该站点包括服务器N卡和游戏N卡.
  • 对于大多数人用的游戏卡(GeFroce),也可以从国内站点搜索驱动下载.
  • 其中notebook 指笔记本上的显卡,此处注意与PC显卡区别开,按照自己需求下载

    安装VS2022

    CUDA-toolkit还是需要编译器编译以生成动态库dll,所以,在安装CUDA-tool-kit 之前需要安装对应的VS2022 或 VS2019.注意,cuda-11.0+ 需要VS2019+, 所以强烈推荐先安装VS2019或VS2022.
    此处下载Visual studio installer,社区版的就能很好满足我们的需求;
    Visual studio installer安装过程中可以主要需要"使用C++的桌面开发"包,其余包可以按需添加;

    安装CUDA tool kit

    此处下载CUDA-tool-kit安装对应的版本即可;
    建议使用CUDA-tool-kit 11.0以上的版本;
    (页面也许需要注册Nvidia论坛账户才可以使用,记得注册).
    安装完成后,需要将安装路径比如:

    C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.2\bin

    添加到环境变量中,然后重启系统,在命令行中测试nvcc(cuda程序编译器)是否能被全局找到:

    C:\Users\a'a>nvcc --version
    nvcc: NVIDIA (R) Cuda compiler driver
    Copyright (c) 2005-2020 NVIDIA Corporation
    Built on Mon_Nov_30_19:15:10_Pacific_Standard_Time_2020
    Cuda compilation tools, release 11.2, V11.2.67
    Build cuda_11.2.r11.2/compiler.29373293_0

    以上就能证明当前环境下配置好了CUDA-tool-kit,且版本为release 11.2.

    下载CUDA-Sample

    Cuda-samples是CUDA官方使用的样例,是我们逐步进入CUDA世界最佳学习材料之一.
    cuda-11.0+ cuda-sample已经从cuda-tool-kit中移出成为独立项目, 以下可以从github上拉取Cuda-Sample

    https://github.com/NVIDIA/cuda-samples/releases

    要注意Cuda-samples是与Cuda-tool-kit版本对应,所以请先安装下本机的Cuda-tool-kit再安装对应版本的Cuda-samples.

    Linux环境配置

    参考鸣谢中<>

    Jetson环境配置

    Jetson是Nvidia发布的带有GPU的嵌入式开发板.官方提供JetPack包,包含所有CUDA driver和Cuda-tool-kit.使用前只需要更新JetPack即可.
    环境配置教程可参考:<>

CUDA编译流程

以下环境均基于cuda-11.4
先回顾下C++的编译流程:

  • 预处理:优化源代码内容
  • 编译:将源代码编译成汇编代码
  • 汇编:将汇编代码生成目标文件
  • 链接:将目标文件链接库,生成可执行文件

    g++ -o hello_world hello_world.cpp

    Cuda是基于C++的一种插件型语法,他的整个流程也是与C++的编译流程相同:

    从基础上也可以看出对cu代码的处理也是经过类似的部分:

  • 预处理
  • 编译
  • 汇编
  • 链接
    对应的指令流程为:

    nvcc -o hello_world hello_world.cu

    对于多个cu文件,可以考虑使用cmake或者Makefile来处理,以下是一个Makefile的样例:

    CUDA_PATH=/usr/local/cuda
    HOST_COMPILER ?= g++
    NVCC=${CUDA_PATH}/bin/nvcc -ccbin ${HOST_COMPILER}
    TARGET=hello_world
    
    INCLUDES= -I${CUDA_PATH}/samples/common/inc
    NVCC_FLAGS=-m64 -lineinfo
    
    IS_CUDA_11:=${shell expr `$(NVCC) --version | grep compilation | grep -Eo -m 1 '[0-9]+.[0-9]' | head -1` \>= 11.0}
    
    # Gencode argumentes
    SMS = 35 37 50 52 60 61 70 75
    ifeq "$(IS_CUDA_11)" "1"
    SMS = 52 60 61 70 75 80
    endif
    $(foreach sm, ${SMS}, $(eval GENCODE_FLAGS += -gencode arch=compute_$(sm),code=sm_$(sm)))
    
    all : ${TARGET}
    
    hello_world: hello_world.cu
      ${NVCC} ${INCLUDES} ${ALL_CCFLAGS} ${GENCODE_FLAGS} -o $@ $<
    
    clean:
      rm -f ${TARGET} 

    CmakeList会相对简单一些

    cmake_minimum_required(VERSION 3.14)
    project(hello_world CUDA)
    
    set(CMAKE_CUDA_STANDARD 17)
    
    add_executable(hello_world hello_world.cu)
    
    set_target_properties(hello_world PROPERTIES
          CUDA_SEPARABLE_COMPILATION ON)

    前提是你的CUDA能够正确被Cmake Find到,如果不行,那就老实使用MakeFile的形式;
    更多Cmake和makefile的写法,会在将来的博客中讨论;

    Hello_world

    现在让我们从hello_world开始我们的旅途

    #include
    #include 
    
    __global__ void print_from_gpu(void) {
      printf("Hello World! from thread [%d,%d] \
          From device\n", threadIdx.x,blockIdx.x); 
    }
    
    int main(void) { 
      printf("Hello World from host!\n"); 
      print_from_gpu<<<4,2>>>();
      cudaDeviceSynchronize();
      return 0; 
    }

    通过以下指令编译:

    nvcc -o hello_world hello_world.cu

    输出结构为:

    Hello World from Host! 
    Hello World! From thread [0, 3]  from devices 
    Hello World! From thread [1, 3]  from devices 
    Hello World! From thread [0, 1]  from devices 
    Hello World! From thread [1, 1]  from devices 
    Hello World! From thread [0, 2]  from devices 
    Hello World! From thread [1, 2]  from devices 
    Hello World! From thread [0, 0]  from devices 
    Hello World! From thread [1, 0]  from devices 

    这里,我们可以发现以下cuda程序特点:

  • 使用__global__ 标识函数调用范围
  • 存在可以不用申明的threadIdx.x 和 blockIdx.x
  • 需要通过print_from_gpu<<<4,2>>>()的结构调用
  • 需要在调用结束后执行cudaDeviceSynchronize()
    我们挨个来研究:
    1.__gloabl__标识device函数的作用范围,可以在Host和device上执行.同样,还有只能在host执行标识符__host__,以及只能在device执行函数__device__;
    2.threadIdx 被成为线程序号,blockIdx被成为块序号,它们都是3维的,具有x,y,z的属性;
    3.<<>>() 调用device函数,其中a决定了blockIdx最大值,b决定了ThreadIdx最大值;

    print_from_gpu<<<4,2>>>(); => print_from_gpu<<>>();

    4.可以尝试将cudaDeviceSynchronize()注释掉,可以发现此时仅会打印

    Hello World from Host! 

    由于cuda程序分别会在host和device上执行,并且两个部分潜在时间不相等,所以这是个异步的程序.调用cudaDeviceSynchronize()会使得Host阻塞,等待所有device上的Kernel function执行完成后,再回到Host执行.所以cudaDeviceSynchronize()还是很有必要的.

鸣谢

部分操作参考下列文章和书籍,在此表示感谢.

  1. <>
  2. Cuda-tool-kit-document/nvcc
  3. Jaegeun Han, Bharatkumar Sharma - Learn CUDA Programming_ A beginner's guide to GPU programming and parallel computing with CUDA 10.x and C_C++-Packt Publishing (2019)

你可能感兴趣的:(cudagpu)