Podstawy programowania równoległego z użyciem CUDA
Słowa kluczowe: GPU programming, programming, coding, CUDA toolkit, CUDA, C++, GPU, programowanie równoległe, instalacja CUDA, pierwszy program CUDA, alokacja pamięci GPU, kernel CUDA, nvcc, nvidia-smi
W pierwszej części tej serii pokażemy, jak przygotować środowisko programistyczne do pracy z CUDA w C++, a następnie napiszemy i uruchomimy prosty program, który podnosi liczby do kwadratu na karcie graficznej NVIDIA.
Upewnij się, że masz kompatybilną kartę graficzną NVIDIA (np. z serii GeForce, Quadro, Tesla).
Linux: Na Linuxie możesz skorzystać z menedżera pakietów (np. apt na Ubuntu) lub pobrać instalator ze strony NVIDIA.
Pamiętaj, że na Linuxie może być konieczne ręczne zainstalowanie sterowników NVIDIA przed instalacją CUDA Toolkit.
Z naszego doświadczenia, na linuxie najlepiej jest użyć Ubuntu 22.04/24.04 LTS, które są szeroko wspierane przez NVIDIA i mają dobre wsparcie dla sterowników i narzędzi CUDA.
Możesz na swoim Windowsie postawić Ubuntu 24.04 LTS, przez WSL (Windows Subsystem for Linux), co pozwoli Ci korzystać z narzędzi Linuxowych i jednocześnie mieć dostęp do GPU NVIDIA. Zazwyczaj GPU jest dostępna w WSL automatycznie w nowszych Windowsach. W przypadku braku automatycznej integracji, konfiguracja WSL z obsługą GPU może być nieco bardziej skomplikowana niż tradycyjna instalacja na natywnym Linuxie, ale jest to świetna opcja dla programistów, którzy chcą korzystać z obu środowisk, mimo wszystko. Tutaj instrukcja do postawienia Ubuntu 24.04 LTS przez WSL.
📌 Po instalacji sprawdź wersję CUDA:
nvcc --version
📌 Sprawdź, czy karta graficzna jest widoczna:
nvidia-smi
Możesz używać dowolnego edytora kodu, np.:
nvccPoniżej znajduje się kod, który:
Jest to prosty skrypt w CUDA C++, który pokazuje, jak alokować pamięć na GPU, kopiować dane, uruchamiać kernel i synchronizować wyniki. Potraktujemy go jako “sanity check” — czyli podstawowy test, który pozwoli nam upewnić się, że środowisko CUDA jest poprawnie skonfigurowane i że możemy uruchamiać programy na GPU. W kolejnych częściach będziemy omawiać już określone funkcjonalności w szczególe. Rzuć okiem na kod i jego komentarze, a następnie wklej go do pliku kernel.cu i uruchom go na swoim komputerze z GPU NVIDIA, za pomocą komendy nvcc.
#include <iostream>
#include <cuda_runtime.h> // Główna biblioteka CUDA do zarządzania pamięcią i urządzeniem
#include <device_launch_parameters.h> // Parametry uruchamiania kernela (np. blockIdx, threadIdx)
// Kernel uruchamiany na GPU — każdy wątek podnosi jeden element tablicy do kwadratu
__global__ void squareKernel(int* x, int n) {
// Obliczenie globalnego indeksu wątku w siatce
int i = blockIdx.x * blockDim.x + threadIdx.x;
// Sprawdzenie, czy indeks mieści się w zakresie tablicy
if (i < n) {
x[i] = x[i] * x[i]; // Podniesienie wartości do kwadratu
}
}
int main() {
const int size = 100; // Rozmiar tablicy
int* host_x = new int[size]; // Dynamiczna alokacja pamięci na CPU (na stercie)
// Inicjalizacja tablicy wartościami od 0 do 99
for (int i = 0; i < size; ++i)
host_x[i] = i;
// Alokacja pamięci na GPU (device)
int* device_x;
cudaMalloc(&device_x, size * sizeof(int));
// Kopiowanie danych z CPU (host) do GPU (device)
cudaMemcpy(device_x, host_x, size * sizeof(int), cudaMemcpyHostToDevice);
// Uruchomienie kernela: 20 bloków po 5 wątków = 100 wątków
squareKernel <<< 20, 5 >>> (device_x, size);
// Synchronizacja — czekamy aż GPU zakończy pracę
cudaDeviceSynchronize();
// Sprawdzenie, czy kernel wykonał się poprawnie
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
std::cerr << "Blad kernela: " << cudaGetErrorString(err) << "\n";
delete[] host_x; // Zwolnienie pamięci na CPU
return 1;
}
// Kopiowanie wyników z GPU z powrotem do CPU
cudaMemcpy(host_x, device_x, size * sizeof(int), cudaMemcpyDeviceToHost);
// Wypisanie wyników na konsolę
for (int i = 0; i < size; ++i)
std::cout << "x[" << i << "] = " << host_x[i] << "\n";
// Zwolnienie pamięci na GPU i CPU
cudaFree(device_x);
delete[] host_x;
return 0;
}
Na linuxie możesz użyć terminala (kompilując program, dodając uprawnienia do wykonania skompilowanego pliku i uruchamiając go):
nvcc kernel.cu -o kernel
chmod u+x ./kernel
./kernel
a na Windowsie PowerShell lub CMD:
nvcc kernel.cu -o kernel.exe
.\kernel.exe
Zapisz sobie te komendy! Będziesz z nich korzystać co sekundę, gdy będziesz pisać i testować programy CUDA.
__global__ oznacza funkcję uruchamianą na GPU>> to sposób uruchamiania kernela — tutaj 100 wątkówcudaMalloc, cudaMemcpy, cudaFree — to podstawowe operacje na pamięci GPUcudaDeviceSynchronize() — czeka, aż GPU zakończy pracęcudaGetLastError() — pozwala wykryć błędy wykonania kernelaW tym tutorialu w 3 krokach pokazaliśmy, jak zacząć używać CUDA Toolkit i przetestować z prostym programem w CUDA C++. W kolejnych częściach zagłębimy się w szczegóły programowania równoległego z CUDA.