這一篇只做兩件事:
- 把 CUDA 開發環境裝好、驗證顯示卡可用;
- 寫出你人生第一支 「Hello from GPU」 程式,看到 GPU 真的在幫你工作。
我會用超直白的比喻帶你走,碰到不同作業系統也有對應的做法。
NVIDIA 顯示卡(近 10 年的卡幾乎都行)。
作業系統:Windows 或 Linux(含 WSL2)。
管理權限:能裝驅動與 CUDA Toolkit。
看見顯示卡
Win + X
→「裝置管理員」→「顯示卡」應該看到 NVIDIA。lspci | grep -i nvidia
(能看到列出 NVIDIA 代表有裝)。驅動正常
nvidia-smi
。編譯器存在
nvcc --version
,應該會印出 NVCC 與 CUDA 版本。心法:
nvidia-smi
管驅動、nvcc
管開發工具。兩者都 OK,你就準備好了。
nvcc --version
應可看到版本。.run
安裝器)。/usr/local/cuda/bin
與 /usr/local/cuda/lib64
已加到 PATH/LD_LIBRARY_PATH。nvcc --version
與 nvidia-smi
。nvidia-smi
、nvcc --version
。如果你對「版本相容」有疑慮:驅動要不低於 CUDA Toolkit 要求。通常裝最新驅動再裝對應的 CUDA最省心。
建立一個資料夾 cuda-hello/
,裡面只有一個檔案:
cuda-hello/
└─ hello.cu
副檔名
.cu
代表「這是 CUDA C/C++ 原始碼」。
__global__
:把一個函式標記成「GPU Kernel」。
<<<grid, block>>>
:啟動 Kernel 的「人力配置」。
block
是「每隊幾人」grid
是「要幾隊」printf
in GPU:可以在 Kernel 裡印字,但要在結束前 cudaDeviceSynchronize()
讓輸出刷到主機端。
錯誤檢查:cudaGetLastError()
、cudaDeviceSynchronize()
是入門最實用的兩個。
hello.cu
這份程式會先由 CPU 印一次「Hello from CPU!」,再請 GPU 開 1 個 block × 4 個 thread,由第 0 線程代表印出「Hello from GPU!」。這樣不會一次印太多行,初學好觀察。
// hello.cu
#include <cstdio>
#include <cuda_runtime.h>
__global__ void helloFromGPU() {
// 只讓每個 block 的第 0 條 thread 印,避免刷一堆行
if (threadIdx.x == 0) {
printf("Hello from GPU! (block %d, thread %d)\n", blockIdx.x, threadIdx.x);
}
}
int main() {
printf("Hello from CPU!\n");
// 啟動 1 個 block,每個 block 有 4 個 thread
dim3 grid(1);
dim3 block(4);
helloFromGPU<<<grid, block>>>();
// 檢查 Launch 是否成功
cudaError_t launchErr = cudaGetLastError();
if (launchErr != cudaSuccess) {
fprintf(stderr, "Kernel launch error: %s\n", cudaGetErrorString(launchErr));
return 1;
}
// 等 GPU 做完,並把 printf 的內容刷出來
cudaError_t syncErr = cudaDeviceSynchronize();
if (syncErr != cudaSuccess) {
fprintf(stderr, "Device sync error: %s\n", cudaGetErrorString(syncErr));
return 1;
}
return 0;
}
打開「x64 Native Tools」或「Developer Command Prompt for VS」,切到專案資料夾:
nvcc hello.cu -o hello.exe
hello.exe
nvcc hello.cu -o hello
./hello
Hello from CPU!
Hello from GPU! (block 0, thread 0)
如果你把
if (threadIdx.x == 0)
拿掉,會變成同一個 block 的 4 條 thread 都印,你會看到 4 行「Hello from GPU!」。這就是平行的最直觀證據。
CPU 端:跑 main()
,呼叫 helloFromGPU<<<1,4>>>()
。
→ 意思是:「GPU 啟動 1 個 block,每個 block 派出 4 個 thread 跑 helloFromGPU()
。」
GPU 端:每條 thread 都會執行 helloFromGPU()
的程式碼。
if (threadIdx.x == 0)
限制只有隊內第 0 位代表發言。cudaDeviceSynchronize()
:讓 CPU 等 GPU 做完,順便把 GPU printf
的輸出收齊。
'nvcc' 不是內部或外部指令
/ command not found
cuda_runtime.h: No such file or directory
cl.exe
或 g++
;請用 nvcc
來編譯 .cu
檔。執行時出現 no kernel image is available for execution on the device
deviceQuery
看你的 Compute Capability(例如 7.5、8.6)。-arch=sm_75
或 -arch=sm_86
編譯:nvcc -arch=sm_86 hello.cu -o hello
nvidia-smi
正常但程式跑不起來
GPU printf
沒出現
cudaDeviceSynchronize()
;沒有等到 GPU 完成,輸出可能沒刷出來。printf
,一次會沖太多行。想看到多線程真的同時跑?把 Kernel 啟動參數改一下:
dim3 grid(2); // 2 個 block
dim3 block(8); // 每個 block 8 條 thread
再把條件改成只讓「每個 block 的第 0 條」印:
if (threadIdx.x == 0) {
printf("Hello from block %d, thread %d\n", blockIdx.x, threadIdx.x);
}
你會看到兩行(block 0 與 block 1 各印一行)。
把 if
拿掉,就會看到 2 × 8 = 16 行 印出來(每條 thread 都說話)。
安裝驅動 + CUDA Toolkit → 用
nvcc
編譯.cu
→ 啟動第一個 Kernel → 看見 GPU 印出 Hello。
你已經踏進 CUDA 的世界:知道什麼是 Kernel / Grid / Block / Thread,也嘗到「一次啟動很多執行緒」的滋味。