CUDA ( Compute Unified Device Architecture )

25
CUDA (Compute Unified Device Architecture) W. Bożejko GPGPU - General-purpose computing on graphics processing units

description

GPGPU - General-purpose computing on graphics processing units. CUDA ( Compute Unified Device Architecture ). W. Bożejko. Plan. Wstęp Model programowania Model pamięci CUDA API Przykład – iloczyn skalarny. Wstęp. Tesla C870. całkowity rozmiar pamięci globalnej 1,61 GB - PowerPoint PPT Presentation

Transcript of CUDA ( Compute Unified Device Architecture )

Page 1: CUDA ( Compute Unified Device Architecture )

CUDA(Compute Unified Device Architecture)

W. Bożejko

GPGPU - General-purpose computing on graphics processing units

Page 2: CUDA ( Compute Unified Device Architecture )

Plan

• Wstęp

• Model programowania

• Model pamięci

• CUDA API

• Przykład – iloczyn skalarny

Page 3: CUDA ( Compute Unified Device Architecture )

Wstęp

Page 4: CUDA ( Compute Unified Device Architecture )

Tesla C870

Produkt Tesla C870

Obudowa ATX, 4.38” x 12.28”

Ilość GPU Tesla 1

Dedykowana pamięć 1.5 GB GDDR3

Szczytowa wydajność Ponad 500 gigaflopów

Precyzja obliczeń zmiennoprzecinkowych

Pojedyncza precyzja w standardzie IEEE 754

Interfejs pamięci 384-bit

Przepustowość pamięci 76.8 GBps

Maksymalny pobór mocy 170W

Interfejs systemowy PCI Express x16

Dodatkowe źródła zasilania Tak (2)Liczba slotów 2

Chłodzenie wentylator

• całkowity rozmiar pamięci globalnej 1,61 GB

• liczba multiprocesorów 16• liczba rdzeni (procesorów) 128• całkowity rozmiar pamięci stałej 65536 KB

• całkowity rozmiar pamięci współdzielonej przypadającej na jeden blok 16384 KB

• liczba rejestrów dostępna dla każdego bloku 8192

• częstotliwość zegara 1,35 GHz

Page 5: CUDA ( Compute Unified Device Architecture )

CUDA – model programowania

• GPU jest widziane jako urządzenie obliczeniowe mogące wykonać część aplikacji która– musi być wykonana wielokrotnie– może być wyizolowana jako funkcja– działa niezależnie na różnych danych (model

SIMD)

• Taka funkcja może być skompilowana o wykonana na GPU

Page 6: CUDA ( Compute Unified Device Architecture )

CUDA – model programowania

• Blok wątków (Thread Block)– Wątki mogą kooperować

• Mają szybką pamięć współdzieloną• Są zsynchronizowane• można je łatwo rozróżniać (mają Thread ID)

– Blok może być 1,2 lub 3-wymiarową tablicą

Page 7: CUDA ( Compute Unified Device Architecture )

CUDA – model programowania

• Grid bloków wątków– Ograniczona ilość wątków w bloku– Pozwala wywołać większą liczbę wątków za

pomocą jednego wywołania– Bloki są identyfikowane za pomocą block ID

– Wymaga zmniejszenia kooperacji wątków

– Bloki mogą być 1 lub 2-wymiarowymi tablicami

Page 8: CUDA ( Compute Unified Device Architecture )

CUDA – model programowania

Page 9: CUDA ( Compute Unified Device Architecture )

CUDA – model pamięci

Page 10: CUDA ( Compute Unified Device Architecture )

CUDA – model pamięci• Shared Memory

– Wbudowana w chip• Znacznie szybsza niż pamięć lokalna i globalna• Tak szybka jak rejestry (jeśli nie ma konfliktów) • Dzielna na równej wielkości banki

– Kolejne 32-bitowe słowa są przypisane do kolejnych banków,

– Każdy bank ma przepustowość (bandwidth) 32 bity na 1 cykl zegara

Page 11: CUDA ( Compute Unified Device Architecture )

CUDA – model pamięci• Shared Memory

Page 12: CUDA ( Compute Unified Device Architecture )

CUDA API• Rozszerzenie języka C

– Kwalifikatory typu funkcji specyfikujące wykonanie na procesorze (host) lub na urządzeniu GPU

– Kwalifikatory typu zmiennej specyfikujące rodzaj pamięci w GPU

– Nowe składnia <<< mówiąca jak wykonać program na urządzeniu

– Cztery wbudowane zmienne pamiętające rozmiary grid’a i bloku oraz numery bloku i wątku

Page 13: CUDA ( Compute Unified Device Architecture )

CUDA API• Kwalifikatory typu funkcji

__device__ • Wykonywane na GPU• Wywoływane tylko z GPU

__global__ • Wykonywane na GPU• Wywoływane tylko z procesora głównego (host’a)

__host__ • Wykonywane na host’cie, • Wywoływane tylko z procesora głównego (host’a)

Page 14: CUDA ( Compute Unified Device Architecture )

CUDA API• Kwalifikatory typu zmiennych

__device__ • Umieszone w pamięci globalnej• Widoczne przez cały czas działania programu• Dostępne dla wszystkich wątków w grid’zie oraz z hosta (poprzez

runtime library)

__constant__ (ewentulanie razem z __device__) • Umieszczone w pamięci stałej (constant memory space), • Widoczne przez cały czas działania programu• Dostępne dla wszystkich wątków w grid’zie oraz z hosta (poprzez

runtime library)•

__shared__ (ewentulanie razem z __device__) • Umieszczone w pamięci współdzielonej (shared memory) bloku danego

wątku• Widoczne tak długo jak istnieje blok• Dostępne tylko dla wszystkich wątków w bloku

Page 15: CUDA ( Compute Unified Device Architecture )

CUDA API• Konfiguracja wykonania

– Musi być sprecyzowana dla kazdego wywołania funkcji typu __global__

– Definiuje rozmiary grid’a i bloków– Umieszczana pomiędzy nazwą funkcji a listą

argumentów:

funkcja:__global__ void Func(float* parameter);

musi być wywołana tak: Func<<< Dg, Db, Ns >>>(parameter);

Page 16: CUDA ( Compute Unified Device Architecture )

CUDA API• Konfiguracja wykonania

gdzie Dg, Db, Ns są:

– Dg jest typu dim3 wymiar i rozmiar gridaDg.x * Dg.y = ilość uruchamianych bloków;

– Db jest typu dim3 wymiar i rozmiar blokówDb.x * Db.y * Db.z = ilość wątków na blok;

– Ns jest typu size_t ilość bajtów w pamięci współdzielonej (shared memory) która jest dynamiczne alokowana dodatkowo do pamięci alokowanej statycznie

• Ns jest opcjonalne; domyślnie 0.

Page 17: CUDA ( Compute Unified Device Architecture )

CUDA API• Wbdowane zmienne

– gridDim typu dim3 wymiary grida.

– blockIdx typu uint3 number bloku w grid’zie

– blockDim typu dim3 wymiary bloku

– threadIdx is of type uint3 numer wątku w

bloku

Page 18: CUDA ( Compute Unified Device Architecture )

Przykład – iloczyn skalarny• Policzyć iloczyn skalarny

– 32 par wektorów– Kożdy po 4096 elementów

• Efektywna organizacja obliczeń:– grid składający się z 32 bloków– z 256 wątkami na blok

• Otrzymamy 4096/265 = 16 segmentów na wektor

Page 19: CUDA ( Compute Unified Device Architecture )

• Dane będą trzymane w GPU jako dwie tablice; wynik umieszczony zostanie w tablicy

• Każdy iloczyn par wektórw An, Bn będzie obliczany w segmentach, dodawanych do wyniku

Vector A0 Vector A1 Vector AN-1…Vector B0 Vector B1 Vector BN-1…

Results 0 to N-1

Vector A0

Vector B0

Results 0 Results 1

Partial results 0 to S-1

segment 0 segment 1 segment S-1…

Przykład – iloczyn skalarny

Page 20: CUDA ( Compute Unified Device Architecture )

Program dla host’aint main(int argc, char *argv[]){

CUT_CHECK_DEVICE(); …

h_A = (float *)malloc(DATA_SZ); …

cudaMalloc((void **)&d_A, DATA_SZ); …

cudaMemcpy(d_A, h_A, DATA_SZ, cudaMemcpyHostToDevice);

… ProdGPU<<<BLOCK_N, THREAD_N>>>(d_C, d_A, d_B); …

cudaMemcpy(h_C_GPU, d_C, RESULT_SZ, cudaMemcpyDeviceToHost);

CUDA_SAFE_CALL( cudaFree(d_A) ); free(h_A); …

CUT_EXIT(argc, argv);

}

Przykład – iloczyn skalarny

Page 21: CUDA ( Compute Unified Device Architecture )

Funkcja dla GPU (Kernel Function)

• Parametry:– d_C: wskaźnik do wyniku

(tj. tablicy)– d_A, d_B wskaźniki do

danych (tablic)

• Tablice lokalne:– t[]: wynkki8 pojedynczego

wątku– r[]: używane do dodawania

wyników segmentów• I: numer (Id) wątku w bloku

__global__ void ProdGPU(float *d_C, float *d_A, float *d_B){ __shared__ float t[THREAD_N]; __shared__ float r[SLICE_N]; const int I = threadIdx.x;

for(int vec_n=blockIdx.x; vec_n<VECTOR_N; vec_n+=gridDim.x){

int base = ELEMENT_N * vec_n;

for(int slice = 0; slice < SLICE_N; slice++, base += THREAD_N){

t[I] = d_A[base + I] * d_B[base + I]; __syncthreads();

for(int stride = THREAD_N / 2; stride > 0; stride /= 2){

if(I < stride) t[I] += t[stride + I]; __syncthreads();

}

if(I == 0) r[slice] = t[0];

}

for(int stride = SLICE_N / 2; stride > 0; stride /= 2){ if(I < stride) r[I] += r[stride + I]; __syncthreads(); }

if(I == 0) d_C[vec_n] = r[0];

}}

Przykład – iloczyn skalarny

Page 22: CUDA ( Compute Unified Device Architecture )

Funkcja dla GPU

• Uruchamiane dla każdej pary wektorów wejściowych

• Zostanie uruchomione tylko raz, ponieważ:

Grid dimension == number of vectors

vector number = block Id

__global__ void ProdGPU(float *d_C, float *d_A, float *d_B){ __shared__ float t[THREAD_N]; __shared__ float r[SLICE_N]; const int I = threadIdx.x;

for(int vec_n=blockIdx.x; vec_n<VECTOR_N; vec_n+=gridDim.x){

int base = ELEMENT_N * vec_n;

for(int slice = 0; slice < SLICE_N; slice++, base += THREAD_N){

t[I] = d_A[base + I] * d_B[base + I]; __syncthreads();

for(int stride = THREAD_N / 2; stride > 0; stride /= 2){

if(I < stride) t[I] += t[stride + I]; __syncthreads();

}

if(I == 0) r[slice] = t[0];

}

for(int stride = SLICE_N / 2; stride > 0; stride /= 2){ if(I < stride) r[I] += r[stride + I]; __syncthreads(); }

if(I == 0) d_C[vec_n] = r[0];

}}

Przykład – iloczyn skalarny

Page 23: CUDA ( Compute Unified Device Architecture )

Funkcja dla GPU

• Uruchamiane dla każdego segmentu wektorów wejściowych

• Każdy wątek wylicza jeden iloczyn i zapamiętuje go

__global__ void ProdGPU(float *d_C, float *d_A, float *d_B){ __shared__ float t[THREAD_N]; __shared__ float r[SLICE_N]; const int I = threadIdx.x;

for(int vec_n=blockIdx.x; vec_n<VECTOR_N; vec_n+=gridDim.x){

int base = ELEMENT_N * vec_n;

for(int slice = 0; slice < SLICE_N; slice++, base += THREAD_N){

t[I] = d_A[base + I] * d_B[base + I]; __syncthreads();

for(int stride = THREAD_N / 2; stride > 0; stride /= 2){

if(I < stride) t[I] += t[stride + I]; __syncthreads();

}

if(I == 0) r[slice] = t[0];

}

for(int stride = SLICE_N / 2; stride > 0; stride /= 2){ if(I < stride) r[I] += r[stride + I]; __syncthreads(); }

if(I == 0) d_C[vec_n] = r[0];

}}

Przykład – iloczyn skalarny

Page 24: CUDA ( Compute Unified Device Architecture )

Funkcja dla GPU

• Wyliczenie wyniku częściowego dla segmentu

• Zapamiętanie wyniku częsciowego

__global__ void ProdGPU(float *d_C, float *d_A, float *d_B){ __shared__ float t[THREAD_N]; __shared__ float r[SLICE_N]; const int I = threadIdx.x;

for(int vec_n=blockIdx.x; vec_n<VECTOR_N; vec_n+=gridDim.x){

int base = ELEMENT_N * vec_n;

for(int slice = 0; slice < SLICE_N; slice++, base += THREAD_N){

t[I] = d_A[base + I] * d_B[base + I]; __syncthreads();

for(int stride = THREAD_N / 2; stride > 0; stride /= 2){

if(I < stride) t[I] += t[stride + I]; __syncthreads();

}

if(I == 0) r[slice] = t[0];

}

for(int stride = SLICE_N / 2; stride > 0; stride /= 2){ if(I < stride) r[I] += r[stride + I]; __syncthreads(); }

if(I == 0) d_C[vec_n] = r[0];

}}

t[0] += t[128]

t[1] += t[129] t[0] += t[64]

t[2] += t[130] t[1] += t[65] … t[0] += t[1]

… …

… t[64]+= t[127]

t[127]+= t[255]

Przykład – iloczyn skalarny

Page 25: CUDA ( Compute Unified Device Architecture )

Funkcja dla GPU

• Dodanie wyników dla wszystkich segmentów

• Zapisanie wyniku w pamięci

__global__ void ProdGPU(float *d_C, float *d_A, float *d_B){ __shared__ float t[THREAD_N]; __shared__ float r[SLICE_N]; const int I = threadIdx.x;

for(int vec_n=blockIdx.x; vec_n<VECTOR_N; vec_n+=gridDim.x){

int base = ELEMENT_N * vec_n;

for(int slice = 0; slice < SLICE_N; slice++, base += THREAD_N){

t[I] = d_A[base + I] * d_B[base + I]; __syncthreads();

for(int stride = THREAD_N / 2; stride > 0; stride /= 2){

if(I < stride) t[I] += t[stride + I]; __syncthreads();

}

if(I == 0) r[slice] = t[0];

}

for(int stride = SLICE_N / 2; stride > 0; stride /= 2){ if(I < stride) r[I] += r[stride + I]; __syncthreads(); }

if(I == 0) d_C[vec_n] = r[0];

}}

Przykład – iloczyn skalarny