Erste Schritte mit CUDA
Der typische Ablauf vieler Kernel-Aufrufe wie auch in der folgenden Aufgabe sieht so aus:
-
Speicher auf der GPU belegen mit Hilfe von cudaMalloc. Beispiel für einen Vektor cuda_a mit \(N\) Elementen:
double* cuda_a; CHECK_CUDA(cudaMalloc, (void**)&cuda_a, N * sizeof(double));
-
Daten von CPU-Speicher auf GPU-Speicher transferieren. Beispiel für das Kopieren des Vektors a im CPU-Speicher zum Vektor cuda_a im GPU-Speicher mit Hilfe von cudaMemcpy:
CHECK_CUDA(cudaMemcpy, cuda_a, a, N * sizeof(double), cudaMemcpyHostToDevice);
-
Aufruf der Kernel-Funktion. Hierbei sollte die Gesamtgröße sinnvoll auf Blöcke aufgeteilt werden. In erster Näherung sind 128 oder 256 Threads pro Block sinnvoll. Beispiel:
unsigned int threads_per_block = 128; /* a multiple of the warp size */ unsigned int num_blocks = (N + threads_per_block - 1) / threads_per_block; axpy<<<num_blocks, threads_per_block>>>(N, 2.0, cuda_a, 1, cuda_b, 1);
-
Rücktransfer der Ergebnisse. Hier ist bei cudaMemcpy die Richtungsangabe cudaMemcpyDeviceToHost zu verwenden:
CHECK_CUDA(cudaMemcpy, a, cuda_a, N * sizeof(double), cudaMemcpyDeviceToHost);
-
Freigabe des GPU-Speichers:
CHECK_CUDA(cudaFree, cuda_a);
Aufgabe
Entwickeln Sie einen CUDA-Kernel scal, der einen Vektor x mit n Elementen skaliert: \(\vec{x} \leftarrow \alpha \vec{x}\). Schreiben Sie dazu ein kleines Testprogramm, das einen Vektor im CPU-Speicher anlegt, initialisiert, zur GPU transferiert, dann die Kernel-Funktion aufruft, das Ergebnis wieder zurückholt und ausgibt.
Das Makro CHECK_CUDA findet sich in der Header-Datei <hpc/cuda/check.h>, die unter /home/numerik/pub/hpc/session24 in der Vorlesungsbibliothek zur Verfügung steht. So kann Ihr Programm auf der Hochwanner übersetzt werden:
nvcc -o scal -I/home/numerik/pub/hpc/session24 --gpu-architecture compute_20 scal.cu