Programowanie procesorów graficznych w CUDA. Kompilujemy program Alokacja zasobów gpgpu oraz załadowanie modułu CUDA odbywa się za pomocą komend: qsub -q gpgpu -I -l walltime=2:00:00,nodes=1:ppn=1:gpus=1 module add gpu/cuda Kompilacji programów dokonujemy za pomocą komendy nvcc, obsługującej przemieszane kody C/C++ i CUDA (te ostatnie często, choć nie zawsze zapisywane w plikach z rozszerzeniem.cu). Proszę obejrzeć parametry wywołania kompilatora, np. za pomocą nvcc help, lub w podobny sposób. Sugerowane parametry kompilacji: -gencode arch=compute_20,code=sm_20. Odczytanie parametrów karty #include<builtin_types.h> #include "cuda_runtime.h" #include "device_launch_parameters.h" #include <iostream> #include <fstream> #include <stdio.h> #include <math.h> #include <vector> #include <time.h> using namespace std; void printdevprop(cudadeviceprop devprop){ const int kb = 1024; const int mb = kb*kb; cout << "Cuda vesion :" << CUDART_VERSION << endl; cout << "Name :" << devprop.name << endl; cout << "Total global memory :" << devprop.totalglobalmem / mb << "MB" << endl; cout << "Total shared memory :" << devprop.sharedmemperblock / kb << "KB" << endl; cout << "Total register per block :" << devprop.regsperblock << endl; cout << "Total warp size :" << devprop.warpsize << endl; cout << "Max memory pitch :" << devprop.mempitch << endl; cout << "Max threads per block :" << devprop.maxthreadsperblock << endl; for( int i = 0; i < 3; ++i) cout << "Max dimension of block :" << devprop.maxthreadsdim[i] << endl;
for(int i = 0; i < 3; ++i) cout << "Max dimension of grid :" << devprop.maxthreadsdim[i] << endl; cout << "Clock rate :" << devprop.clockrate << endl; cout << "Total const memory :" << devprop.totalconstmem / kb << endl; cout << "Number of multiprocessors :" << devprop.multiprocessorcount << endl; int main() { int devcnt; cudastatus = cudagetdevicecount(&devcnt); //sprawdznie czy istnieją urządzenia cuda w systemie if(cudastatus!= cudasuccess) fprintf(stderr, "cuda GetDivevie Count failed!!"); else cout << "There are" << devcnt << " CUDA devices." << endl; for(int i = 0; i < devcnt; ++i){ cudadeviceprop devprop; cudagetdeviceproperties(&devprop, i); printdevprop(devprop); cout << "Print to eny key to exit..." << endl; char c; cin >> c; return 0; Program odczytuje podstawowe parametry zainstalowanego GPU. Proszę uruchomić program, a następnie przeczytać i przeanalizować wyniki - będą przydatne przy kolejnych programach. Za podstawowe parametry możemy uznać: 1. Liczba multiprocesorów 2. Taktowanie multiprocesorów oraz pamięci 3. Szerokość szyny danych 4. Wielkość pamieci RAM 5. Rodzaj pamięci 6. Maksymalne ilości wątków oraz bloków. Pierwszy kernel + pomiar czasu Proszę zapoznać się z poniższymi funkcjami. Alokacja oraz kopiowanie danych :
cudamalloc((void **) &a_d, size); // alokuj pamięć na GPU cudamemcpy(a_d, a_h, size, cudamemcpyhosttodevice);//kopiuj dane na GPU Pomiaru czasu na GPU dokonujemy za pomocą funkcji opartych na zdarzeniach : cudaevent_t start, stop; cudaprintfinit(); cudaeventcreate(&start); cudaeventcreate(&stop); cudaeventrecord(start, 0); wykonaj obliczenia na GPU: kernel <<< n_blocks, block_size >>> (a_d, N); cudaeventsynchronize(stop); float time; cudaeventelapsedtime(&time, start, stop); cout << "Czas wykonania programu GPU: " << time << endl; cudaeventdestroy(starthtd); cudaeventdestroy(stophtd); prześlij wyniki cudamemcpy(a_h, a_d, sizeof(float)*n, cudamemcpydevicetohost); cudafree(a_d); Proszę uruchomić program : #include<builtin_types.h> #include "cuda_runtime.h" #include "device_launch_parameters.h" #include <iostream> #include "cuprintf.cu" #define N 16 int Array[N]; int *devarray; int blocks; void allocatememoryandcopydatatodevice(void){ for(int i = 0; i < N; i++){ Array[i] = i; cudastatus = cudamalloc((void**)&devarray, sizeof(array)); std::cerr << "cudamemcpy host to device failed!\n"; cudastatus = cudamemcpy(devarray, Array, sizeof(array), cudamemcpyhosttodevice);
std::cerr << "cudamemcpy failed!\n"; void copydatatohost(void){ cudastatus = cudamemcpy(array, devarray, sizeof(array),cudamemcpydevicetohost); std::cerr << "cudamemcpy device to host failed!\n"; cudafree(devarray); global void showdataindevice(int *A){ if(threadidx.x == 0){ for(int i = 0; i < N; i++) cuprintf("data in device : %d \n", A[i]); int main() { cudadeviceprop prop; int count; cudastatus = cudagetdevicecount(&count); if(cudastatus!= cudasuccess) { std::cerr << "cudagetdevicecount failed, bailing out\n"; cudastatus = cudagetdeviceproperties(&prop,0); if (cudastatus!= cudasuccess){ std::cerr << "cudagetdeviceproperties failed, bailing out\n"; cudaprintfinit(); allocatememoryandcopydatatodevice(); showdataindevice<<<1,1>>>(devarray); cudaprintfdisplay(stdout, true); cudaprintfend(); cudathreadsynchronize(); copydatatohost(); return 0; Następnie proszę zmienić liczbę bloków oraz liczbę wątków w bloku(pierwszy i drugi parametr wywołania kernela<<>> ). W programie została zastosowana funkcja cuprintf(). Dzięki niej możemy wyświetlić wyniki prac danego wątku. Aby było możliwe jej użycie należy do folderu w którym znajdują się nasze programy skopiować cuprintf.cu oraz cuprintf.cuh.
Dodawanie wektorów Proszę uruchomić program dodawania wektorów na GPU i CPU napisany według poniższego schematu. #include<builtin_types.h> #include "cuda_runtime.h" #include "device_launch_parameters.h" #include "cuprintf.cu" #include <iostream> #define N 16 #define NumberOfThreads 4 int A[N]; int B[N]; int C[N] = {0; int *deva; int *devb; int *devc; int blocks; void alocatememoryandcopydatatodevice(void){ for(int i = 0; i < N; i++){ A[i] = i; B[i] = i*i; cudastatus = cudamalloc((void**)&deva, sizeof(a)); std::cerr << "cudamalloc failed!\n"; cudastatus = cudamalloc((void**)&devb, sizeof(b)); std::cerr << "cudamalloc failed!\n"; cudastatus = cudamalloc((void**)&devc, sizeof(c)); std::cerr << "cudamalloc failed!\n"; cudastatus = cudamemcpy(deva, A, sizeof(a), cudamemcpyhosttodevice); std::cerr << "cudamemcpy host to device failed!\n"; cudastatus = cudamemcpy(devb, B, sizeof(b), cudamemcpyhosttodevice); std::cerr << "cudamemcpy host to device failed!\n"; void copydatatohost(void){ cudastatus = cudamemcpy(c, devc, sizeof(c),cudamemcpydevicetohost);
std::cerr << "cudamemcpy device to host failed!\n"; cudafree(deva); cudafree(devb); cudafree(devc); global void AddKernelInSharedMemory(int *A, int *B, int *C){ shared int SharedMemoryA[N]; shared int SharedMemoryB[N]; shared int SharedMemoryC[N]; unsigned int tid = threadidx.x + blockidx.x*blockdim.x; //copy data to shared memory SharedMemoryA[threadIdx.x] = A[tid]; SharedMemoryB[threadIdx.x] = B[tid]; SharedMemoryC[threadIdx.x] = SharedMemoryA[threadIdx.x] + SharedMemoryB[threadIdx.x]; syncthreads(); //copy result to global memory C[tid] = SharedMemoryC[threadIdx.x]; int main() { int no_devices; //check device status cudastatus = cudagetdevicecount(&no_devices); if(cudastatus!= cudasuccess) { std::cerr << "cudagetdevicecount failed, bailing out\n"; cudadeviceprop props; cudastatus = cudagetdeviceproperties(&props,0); if(cudastatus!= cudasuccess) { std::cerr << "cudagetdeviceproperties failed, bailing out\n"; cudaprintfinit(); blocks = N/NumberOfThreads; alocatememoryandcopydatatodevice(); AddKernelInSharedMemory<<<blocks,NumberOfThreads>>>(devA,devB,devC); cudaprintfdisplay(stdout, true); cudaprintfend(); cudathreadsynchronize(); copydatatohost(); std::cout << "Result:\n"; for(int i = 0; i < N; i ++) { std::cout << A[i] << "+" << B[i] << "=" << C[i] << std::endl; return 0;
Proszę uzupełnić kod o pomiar czasu, a następnie uruchomić go próbując różne rozmiary tablicy, różne liczby wątków i bloków. Proszę też zwrócić uwagę na ograniczenia na maksymalną liczbę wątków w bloku. W szczególności proszę: 1. Umożliwić ustalanie rozmiarów tablicy z klawiatury w trakcie wykonania, tak żeby łatwo było zebrać wyniki dla różnych danych; 2. Dodać kod, który wykonuje dodawanie tylko na CPU z użyciem OpenMP który podaje czas wykonania - tak żeby można było porównać GPU z CPU. 3. Umożliwić zmianę struktury bloków (ile wątków na blok?) i gridów (ile bloków w gridzie?). Proszę ocenić jak wpływa to efektywność programu.