前言
随着深度学习的发展,AI算法对计算的需求量越来越大,传统的CPU串行编程已经不能满足企业对AI低延迟高性能要求, GPU并行编程越来越受到关注, 因此掌握一门GPU并行编程技术对于AI软件栈开发的人员非常必要.
关于GPU编程
目前Server端主流的GPU大部分采用NVIDIA GPU, 例如V100, A100等系列, 也有部分采用AMD 系列GPU, NVIDIA以及AMD都为GPU编程提供了相应的软件开发工具以及框架.
- NVIDIA: CUDA
- AMD: HIP
笔者对AMD GPU的软件栈略知一二, AMD的HIP编程基本上和NVIDIA CUDA非常相似, 目前已经有相应的工具可以将AMD HIP与NV CUDA的代码进行相互转换, 例如PyTorch提供的Hipfiy工具.
测试环境
- OS: Ubuntu 20.04
- CUDA: v11.4
- GCC: 10.3
- Docker: v20
- VSCode
Ubuntu CUDA开发环境快速搭建
Ubuntu上搭建CUDA开发环境有2种方式:
- NVIDIA官网下载CUDA安装包, 执行安装脚本
- 采用NVIDIA提供的CUDA docker环境, 开箱即用
在公司和企业中, 由于不同人员往往会交叉使用服务器资源,因此docker应用的比较广泛, docker可以提供一个标准化, 可复现的统一环境. 因此笔者决定采用NVIDIA提供的docker进行CUDA开发环境的创建.
Docker 环境检查
- 首先需要确保Ubuntu系统是否安装了docker:
docker --version
, 为了方便使用GPU, 选择docker的版本>19 - 安装: nvidia-docker2
输出结果:
Docker version 20.10.11, build dea9396
获取NVIDIA CUDA docker
DockerHub提供了 nvidia/cuda
的docker 镜像:
- dockerHub: https://registry.hub.docker.com/
image.png - nvidia/cuda镜像: 在dockerHub中搜索nvidia/cuda: https://registry.hub.docker.com/r/nvidia/cuda/tags
image.png
nvidia/cuda针对x86, ARM64等提供了各个版本的docker镜像, nvidia/cuda中的docker镜像主要包含3中不同的镜像:
hree flavors of images are provided:
base
: Includes the CUDA runtime (cudart)runtime
: Builds on thebase
and includes the [CUDA math libraries](https://developer.nvidia.com/gpu-> accelerated-libraries), and NCCL. Aruntime
image that also includes cuDNN is available.devel
: Builds on theruntime
and includes headers, development tools for building CUDA images. These images are particularly useful for multi-stage builds.
由于nvidia/docker提供了多种docker镜像, 因此我们根据自己的需求选择一个合适版本/处理器架构的docker镜像, 以 Ubuntu 20.04为例:
- 11.4.2-devel-ubuntu20.04
- 11.4.2-runtime-ubuntu20.04 包含cuda-base + CUDA 数学加速库(比如cublas, cufft) + cudnn
- 11.4.2-base-ubuntu20.04
- 11.4.2-cudnn8-runtime-ubuntu20.04
- 11.4.2-cudnn8-devel-ubuntu20.04 devel版本的docker镜像是基于runtime镜像创建的
笔者选择了一个比较全的docker镜像: 11.4.2-cudnn8-devel-ubuntu20.04
docker镜像下载: 在ubuntu终端中输入: docker pull nvidia/cuda:11.4.2-cudnn8-devel-ubuntu20.04
下载完成之后, 可以查看docker镜像: docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
nvidia/cuda 11.4.2-cudnn8-devel-ubuntu20.04 b1539d83387e 3 months ago 9.14GB
创建CUDA Docker容器
Docker容器: docker容器是docker镜像的实例化, docker镜像运行之后的产物; 类似于进程和程序的概念, 程序是静态的代码, 进程是程序载入内存之后运行态的程序.
下载好 nvidia/docker
镜像之后, 开始启动一个docker容器, 并且进入docker:
简单的命令: docker run -it --name=test-cuda --gpus=all nvidia/cuda:11.4.2-cudnn8-devel-ubuntu20.04
不出意外, docker容器创建成功并且自动进入了docker, 检查一下环境:
nvidia-smi # 查看GPU
nvcc --version # 查看CUDA编译器版本
工程测试
Ubuntu中CUDA 的安装位置说明
一般情况下CUDA默认安装的目录: /usr/local/cuda
, 存在如下目录:
- bin: 二进制目录,包含nvcc, nvprof. cuda-gdb等相关工具
- extras
- nsight-compute-2020.2.0
- nvvm
- src
- compute-sanitizer
- include: CUDA提供的C/C++ 头文件, 例如:
cuda_runtime.h
- nsightee_plugins
- README
- targets
- DOCS
- lib64: CUDA提供的so动态库
- nsight-systems-2020.3.4
- samples: CUDA演示的例子
- tools
- EULA.txt
- libnvvp
- nvml
- share
基于cmake 的简单CUDA测试程序
测试程序的功能: 两个数组简单相加, element-wise add
- main.cu
#include
#include
#include
#include
void elmtwise_sum_cpu(int* arr1, int* arr2, int* out, int N) {
for(int i=0;i>>(d_arr1, d_arr2, d_out, N);
// 4. Cpoy GPU result to CPU
cudaMemcpy(out, d_out, sizeof(int)*N, cudaMemcpyDeviceToHost);
// 5. Free GPU Memory
cudaFree(d_arr1);
cudaFree(d_arr2);
cudaFree(d_out);
}
int main() {
const int N = 512* 512;
int* arr1 = new int[N];
int* arr2 = new int[N];
int* out_cpu = new int[N];
int* out_gpu = new int[N];
srand(123456);
for(int i=0;i void {
std::cout << msg << std::endl;
int n = std::min(N, k);
for(int i=0;i
- CMakeLists.txt
project(TEST_CUDA LANGUAGES CXX CUDA)
cmake_minimum_required(VERSION 3.10)
# https://zhuanlan.zhihu.com/p/105721133
if(CUDA_ENABLE)
enable_language(CUDA)
endif()
add_executable(main "main.cu")
编译 & run:
mkdir -p build
cd build
cmake ../
make -j8
# run
./main
运行结果:5
程序分析:
典型的GPU程序执行流程: GPU端申请内存 ---> Copy data from CPU to GPU ---> Launch GPU kenrel ---> Copy result from GPU to CPU ---> Free GPU Memory
CUDA编程头文件:
, 包含常用的CUDA函数, 例如cudaMalloc(), cudaMemcpy() 用于在显存分配空间以及CPU-GPU端数据拷贝传输
__global__ void kernel_sum
: GPU上执行的核函数, kernel function,__global__
修饰符表示此函数是一个GPU kernel function, 次函数在CPU端调用,在GPU端执行GPU端线程配置
// 3. 设置GPU端线程配置, launch the GPU kernel
int blk_size = 128; --- block_size, 代表1个block中CUDA线程的数量,一般为2的幂数
int grid_size = (N + blk_size -1) / blk_size; --- gride_size: 代表全部计算需要的block个数, 注意这里需要向上取整
kernel_sum<<>>(d_arr1, d_arr2, d_out, N); --- <<< grid_size, blk_size>>> CUDA特有的kernel启动方式