CUDA 開發環境設定與簡易程式範例
- 2020-12-10
- Liu, An-Chi 劉安齊
¶ 1. 簡介
GPU 以前是用來算圖形介面,後來有了通用 GPU 運算 (General Purpose GPU, GPGPU) 技術的誕生,因為 GPU 架構擁有非常多執行緒,在跑程式的時候可以將大量的數值運算交給 GPU,而邏輯的部分交給 CPU,這也是常見的 GPGPU 使用方式。其中 Nvidia 便是第一個提出 GPGPU 概念的公司,其公司提出 CUDA 技術,讓開發者可以用一般寫 C 的語法去驅動 GPU 來做運算。
¶ 2. CUDA 安裝
CUDA 有很多個版本,如果直接用 apt install cuda 不一定安裝到自己需要的版本。
除了一般的 CUDA 程式,我們可能還會想要給 Pytorch 或 Tensorflow 使用。比較好的做法是去官方網站找自己需要的版本,不過要記住要去 CUDA Toolkit Archive 的頁面選版本,不然 Nvidia 官網會直接導引到最新的版本。
大家可以照著我下面指示安裝。
¶ 2.1 CUDA 安裝
以下以 CUDA 11.0 版本為範例。首先去 Archive 頁面,選 11.0 點進去,然後照個你的需求把選項勾一勾。

例如上面就是選 x86 Ubuntu 20.04 版本,然後你可以選 runfile、deb local 或 deb network。
三種差異分別是一大包安裝檔、deb 包裝好的一大包安裝檔、完全靠網路下載的 deb 安裝檔。有沒有 deb 差異在於之後可不可以用套件管理員去管理。
通常我會選 deb local,你也可以用其他的,端看個人習慣。
選完之後他很貼心,就會給你一大串指令,基本上照著打就安裝完了。
Cuda 11.0 Ubuntu 20.04 x86 安裝流程:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.0.3/local_installers/cuda-repo-ubuntu2004-11-0-local_11.0.3-450.51.06-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2004-11-0-local_11.0.3-450.51.06-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu2004-11-0-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda
其中 dpkg -i 如果有問題的話,也可以直接用 apt ./xxx.deb 來代替。
¶ 2.2 CuDNN 安裝
我們可以順便裝一下 CuDNN,這是 CUDA 針對 Deep Learning 優化過的函數庫,假設有使用 Pytorch 就會用到。記得根據你的作業系統改一下連結,像是 ubuntu18.04 是指 Ubuntu 18。
$ sudo bash -c 'echo "deb http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1804/x86_64 /" > /etc/apt/sources.list.d/cuda_learn.list'
$ sudo apt install libcudnn7
¶ 2.3 OpenCL 安裝
如果裝了 CUDA 幾本上 OpenCL 也可以順便裝,因為 Nvidia GPU 使用 OpenCL 也是吃 CUDA,這樣之後要跑 OpenCL 就可以直接用。
$ sudo apt install -y nvidia-opencl-dev
$ sudo apt install opencl-headers
¶ 2.4 系統設定
然後首先要重新開機,這非常重要!!!!
順利裝完之後記得要把路徑設定好,在 ~/.bashrc 裡面加入
export PATH=$PATH:/usr/local/cuda/bin
export CUDADIR=/usr/local/cuda
¶ 2.5 檢查安裝
然後我們就可以檢查一下有沒有裝好。
CUDA 有裝好的話,以下指令應該都有反應。
$ nvidia-smi # Driver
$ nvcc --version # CUDA
$ /sbin/ldconfig -N -v $(sed 's/:/ /' <<< $LD_LIBRARY_PATH) 2>/dev/null | grep libcudnn # CuDNN
¶ 2.6 其他
如果上面安裝方法沒用的話,也可以參考這幾篇有用的文章:
- Easy installation of Cuda Toolkit on Ubuntu 18.04
- Tutorial: CUDA v10.2 + CUDNN v7.6.5 Installation @ Ubuntu 18.04
- Installing CUDA 10.1 on Ubuntu 20.04
¶ 3. CUDA 程式範例
想學好 CUDA 最好的辦法是看官方教學文件 CUDA C++ Programming Guide,畢竟是 Nvidia 自己家的產品。Gerassimos 寫的 Multicore and GPU Programming: An Integrated Approach 這本書也挺好的,很適合入門。
這邊提供一個簡單的範例:
matadd.cu:
#include <stdio.h>
#include <cuda.h>
#include <cuda_runtime.h>
#define N 512
#define BLOCK_SIZE 16
// GPU 的 Kernel
__global__ void MatAdd(float *A, float *B, float *C)
{
// 根據 CUDA 模型,算出當下 thread 對應的 x 與 y
int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y;
// 換算成線性的 index
int idx = j * N + i;
if (i < N && j < N)
{
C[idx] = A[idx] + B[idx];
}
}
int main()
{
float *h_A, *h_B, *h_C;
float *d_A, *d_B, *d_C;
int i;
// 宣告 Host 記憶體 (線性)
h_A = (float *)malloc(N * N * sizeof(float));
h_B = (float *)malloc(N * N * sizeof(float));
h_C = (float *)malloc(N * N * sizeof(float));
// 初始化 Host 的數值
for (i = 0; i < (N * N); i++)
{
h_A[i] = 1.0;
h_B[i] = 2.0;
h_C[i] = 0.0;
}
// 宣告 Device (GPU) 記憶體
cudaMalloc((void **)&d_A, N * N * sizeof(float));
cudaMalloc((void **)&d_B, N * N * sizeof(float));
cudaMalloc((void **)&d_C, N * N * sizeof(float));
// 將資料傳給 Device
cudaMemcpy(d_A, h_A, N * N * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, N * N * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(d_C, h_C, N * N * sizeof(float), cudaMemcpyHostToDevice);
dim3 blockSize(BLOCK_SIZE, BLOCK_SIZE);
dim3 numBlock(N / BLOCK_SIZE, N / BLOCK_SIZE);
// 執行 MatAdd kernel
MatAdd<<<numBlock, blockSize>>>(d_A, d_B, d_C);
// 等待 GPU 所有 thread 完成
cudaDeviceSynchronize();
// 將 Device 的資料傳回給 Host
cudaMemcpy(h_C, d_C, N * N * sizeof(float), cudaMemcpyDeviceToHost);
// 驗證正確性
for (i = 0; i < (N * N); i++)
{
if (h_C[i] != 3.0)
{
printf("Error:%f, idx:%d\n", h_C[i], i);
break;
}
}
printf("PASS\n");
// free memory
free(h_A);
free(h_B);
free(h_C);
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
return 0;
}
在 GPU 運算中,最小執行單位稱作 Kernel,等同 GPU 上的每個 Thread 要執行的函數。所以可以看到我宣告 __global__ void MatAdd() 函數,__global__ 告訴編譯器這是 GPU 的函數,MatAdd() 裡面顧名思義是將 A、B 兩個矩陣的同個 index 相加到 C。
資料有分 Host 端跟 Device 端,Host 就是我們的 CPU,而 Device 則是指 GPU。Host 宣告記憶體就是一般的 malloc,但 GPU 要宣告記憶體則要用 cudaMalloc。
因為我們是要把運算搬到 GPU 做,所以可以看到我們在 Host 的資料會需要先用 cudaMemcpy 搬到 GPU,才能去執行 MatAdd<<<numBlock, blockSize>>>,算完之後還要再用 cudaMemcpy 把 GPU 的資料搬回給 CPU,Host 才能拿資料做事。
那我們看到使用 Kernel 時語法是 MatAdd<<<numBlock, blockSize>>>,這跟 GPU 的 Thread 架構有關。

可以參考上圖,GPU 包含許多 Block,每個 Block 又有許多 Thread。所以我們在跑 Kernel 時要指定要用多少 Block,以及裡面使用多少 Thread。
大概知道在幹嘛之後,我們就可以編譯執行囉!
$ nvcc matadd.cu; ./a.out
PASS
跑的時候可以在另一個視窗呼叫 nvidia-smi,就可以看到 matadd 使用 GPU 狀況。
我們也可以用 nvprof 來看一下 CUDA 的效能:
$ nvprof ./a.out
==27161== NVPROF is profiling process 27161, command: ./a.out
PASS
==27161== Profiling application: ./a.out
==27161== Profiling result:
Type Time(%) Time Calls Avg Min Max Name
GPU activities: 70.06% 263.96us 3 87.986us 87.581us 88.285us [CUDA memcpy HtoD]
21.55% 81.181us 1 81.181us 81.181us 81.181us [CUDA memcpy DtoH]
8.40% 31.647us 1 31.647us 31.647us 31.647us MatAdd(float*, float*, float*)
API calls: 98.65% 133.57ms 3 44.524ms 3.6540us 133.50ms cudaMalloc
0.78% 1.0564ms 4 264.10us 169.89us 383.89us cudaMemcpy
0.19% 256.03us 3 85.342us 20.249us 148.41us cudaFree
0.14% 187.38us 1 187.38us 187.38us 187.38us cuDeviceTotalMem
0.13% 169.85us 97 1.7510us 193ns 69.776us cuDeviceGetAttribute
0.07% 98.576us 1 98.576us 98.576us 98.576us cudaDeviceSynchronize
0.02% 29.226us 1 29.226us 29.226us 29.226us cudaLaunchKernel
0.02% 26.508us 1 26.508us 26.508us 26.508us cuDeviceGetName
0.00% 4.0950us 1 4.0950us 4.0950us 4.0950us cuDeviceGetPCIBusId
0.00% 1.3720us 3 457ns 266ns 811ns cuDeviceGetCount
0.00% 1.0800us 2 540ns 192ns 888ns cuDeviceGet
0.00% 365ns 1 365ns 365ns 365ns cuDeviceGetUuid
上面顯示我們大部時間都花在搬資料,的確合理,因為我們運算的東西很簡單,導致 Overhead 發生在記憶體搬移上。
¶ 4. 結論
本文簡單介紹 CUDA 開發環境設置,並解說簡單的 CUDA 程式範例。GPGPU 幫助我們在大量運算時可以有效加速計算,許多數值計算函式庫抑或機器學習、深度學習框架也都會使用 GPGPU 來加速計算。