Redukcja pm pm
Transkrypt
Redukcja pm pm
Programowanie procesorów graficznych GPGPU
Krzysztof Banaś
Obliczenia równoległe
1
Redukcja GPU
➔
Redukcja
naiwna redukcja
• wątki zapisują wynik do tablicy
• pojedynczy wątek sumuje wyrazy tablicy
• konieczność synchronizacji działania
redukcja właściwa
• rozmaite warianty wykorzystania drzewa redukcji
Krzysztof Banaś
Obliczenia równoległe
2
Redukcja GPU
➔
Redukcja
standardowy przykład: iloczyn skalarny
float dotProduct = 0; for (uint i = 0; i < N; ++i) dotProduct += V1[i] * V2[i];
wersja wielowątkowa – krok 1:
• każdy wątek uzyskuje sumę iloczynów dla sobie przydzielonych składowych V1 i V2 i zapisuje ją w zmiennej suma
• dekompozycja wektorów V1 i V2 może zostać dokonana blokowo lub cyklicznie
• jaki jest wariant optymalny dla GPU?
Krzysztof Banaś
Obliczenia równoległe
3
Redukcja – GPU
➔
Redukcja wielowątkowa – krok 2
wykorzystanie standardowego drzewa redukcji
konieczność zastosowania pamięci wspólnej do komunikacji miedzy wątkami
wariant 1
__local partialDotProduct[WORKGROUP_SIZE]
partialDotProduct[get_local_id(0)] = sum; for (uint stride = 1; stride < get_local_size(0); stride *= 2) { barrier(CLK_LOCAL_MEM_FENCE); uint index = 2 * stride * get_local_id(0); if (index < get_local_size(0)) { partialDotProduct[index] += partialDotProduct[index + stride]; } } Krzysztof Banaś
Obliczenia równoległe
4
Redukcja – GPU
Krzysztof Banaś
Obliczenia równoległe
5
GPU memory banks
Krzysztof Banaś
Obliczenia równoległe
6
Redukcja – GPU
Krzysztof Banaś
Obliczenia równoległe
7
Redukcja – GPU
➔
Redukcja wielowątkowa – krok 2
Wariant 2: optymalny dostęp do pamięci lokalnej (wspólnej) – kolejne wątki uzyskują dostęp do kolejnych komórek pamięci
partialDotProduct[get_local_id(0)] = sum; for (uint stride = get_local_size(0)/2; stride > 0; stride /= 2) { barrier(CLK_LOCAL_MEM_FENCE); if (get_local_id(0) < stride) { partialDotProduct[get_local_id(0)] += partialDotProduct[get_local_id(0)+stride]; } } Krzysztof Banaś
Obliczenia równoległe
8
Przykład – mnożenie macierzwektor
➔
Prosta strategia: jeden wątek – jeden element wektora wyniku
czy dostęp do tablicy M jest optymalny?
__kernel void mat_vec_1_kernel(
const __global float* M, uint width, uint height, const __global float* V, __global float* W) { uint j = get_global_id(0); const __global float* row = M + j * width; float dotProduct = 0; for (uint i = 0; i < width; ++i) dotProduct += row[i] * V[i]; W[j] = dotProduct; }
Krzysztof Banaś
Obliczenia równoległe
9
Przykład – mnożenie macierzwektor
➔
Modyfikacja: jeden wątek – wiele elementów wektora wyniku
zwiększenie rozmiaru grupy, liczba grup dobierana dowolnie
dostęp do M nie zmieniony
__kernel void mat_vec_2_kernel(
const __global float* M, uint width, uint height, const __global float* V, __global float* W) { for (uint j = get_global_id(0); j < height; j += get_global_size(0)){ const __global float* row = M + j * width; float dotProduct = 0; for (uint i = 0; i < width; ++i) dotProduct += row[i] * V[i]; W[j] = dotProduct; }
Krzysztof Banaś
Obliczenia równoległe
10
Przykład – mnożenie macierzwektor
➔
Modyfikacja dostępu do M
jeden wiersz dla grupy wątków, wiele wierszy na grupę
kolejne wątki w grupie czytają kolejne wyrazy M
konieczność redukcji
__kernel void mat_vec_3_kernel( ... ) { for (uint j = get_group_id(0); j < height; j += get_num_groups(0)) { const __global float* row = M + j * width; float sum = 0.0; for (uint x = get_local_id(0); x < width; x += get_local_size(0)) sum += row[x] * V[x]; // REDUKCJA...
....
Krzysztof Banaś
Obliczenia równoległe
11
}
Przykład – mnożenie macierzwektor
➔
Zoptymalizowany algorytm mnożenia macierzwektor
__kernel void mat_vec_3_kernel( ... ) { for (uint j = get_group_id(0); j < height; j += get_num_groups(0)) { const __global float* row = M + j * width; float sum = 0.0; for (uint x = get_local_id(0); x < width; x += get_local_size(0)) sum += row[x] * V[x];
// REDUKCJA – wynik w partialDotProduct[0] if (get_local_id(0) == 0) W[y] = partialDotProduct[0]; barrier(CLK_LOCAL_MEM_FENCE); }
Krzysztof Banaś
Obliczenia równoległe
12
Projektowanie kerneli
➔
Zasady optymalizacji:
opóźnienie w dostępie do pamięci jest ukrywane przez współbieżne wykonywanie wielu wątków na pojedynczym PE
• wiele grup wątków na jednym CU
• bardzo duże grupy wątków (z rozmiarem będącym wielokrotnością warp/wavefront)
obowiązują standardowe zasady:
• usuwanie zależności – danych, zasobów
• redukcja złożoności wyrażeń (strength reduction)
➢ operacje / (dzielenie całkowite) i %(modulo) są zazwyczaj kosztowne
• minimalizacja liczby operacji
• optymalizacja dostępu do pamięci
Krzysztof Banaś
Obliczenia równoległe
13
Projektowanie kerneli
➔
Zasady optymalizacji:
należy maksymalizować liczbę wątków (w rozsądnych granicach, granice zależą też od możliwości sprzętu)
• globalna liczba wątków jest ograniczona (także indywidualnie dla każdego wymiaru przestrzeni wątków)
• liczba wątków w grupie jest ograniczona • liczba grup jest ograniczona (także indywidualnie dla każdego wymiaru przestrzeni wątków)
➢ liczba grup aktywnych jest ograniczona:
» przez ograniczoną liczbę rejestrów CU
» przez ograniczony rozmiar pamięci wspólnej CU
» przez możliwości sprzętu
optymalny dobór podziału pracy na wątki może być bardzo złożony i różny dla każdego GPU
• można próbować rozwiązać problem analitycznie
• zdarza się, że konieczne są eksperymenty Krzysztof Banaś
Obliczenia równoległe
14
Przykład – mnożenie macierzy
➔
Przypomnienie:
mnożenie macierzy jest algorytmem, dla którego przy nieskończonej liczbie rejestrów występuje bardzo korzystny stosunek liczby operacji do liczby dostępów do pamięci 3
2
s
= (2n
)/(3n
) ~ 2n/3 • pm
• (n – rozmiar macierzy)
przy małej liczbie rejestrów i małym rozmiarze pamięci podręcznej naiwna implementacja prowadzi do znacznego spadku stosunku spm:
3
3
• spm = (2n )/(n +....) ~ 2 Krzysztof Banaś
➔
➔
implementacja naiwna
schemat przechowywania wierszami: c(row, col) = c[row*n + col]
for(i=0;i<n;i++){
for(j=0;j<n;j++){
c[i*n+j]=0.0;
for(k=0;k<n;k++){
c[i*n+j] += a[i*n+k]*b[k*n+j];
}
}
}
Obliczenia równoległe
15
Przykład – mnożenie macierzy
➔
➔
➔
__kernel void mat_mul_1_kernel(
Naiwna implementacja __global float* A, GPU – jeden wątek na __global float* B, jeden element __global float* C,
macierzy wynikowej C int N
Operacje wyłącznie na ) { pamięci globalnej
int i;
Nieoptymalny dostęp int row = get_global_id(1); do tablicy A (choć int col = get_global_id(0); lokalność przestrzenna float temp = 0.0;
może pomóc jeśli for (i = 0; i < N; i++) {
temp += A[row * N + i] * B[i * N + col]; pamięć globalna }
posiada pamięć C[row * N + col] = temp; podręczną)
}
Krzysztof Banaś
Obliczenia równoległe
16
Przykład – mnożenie macierzy
➔
➔
➔
Klasyczna technika optymalizacji – blokowanie Wyróżnienie bloków w tablicach A, B i C przechowywanych w szybkiej pamięci
Wykonanie jak największej liczby operacji na blokach w szybkiej pamięci Krzysztof Banaś
Obliczenia równoległe
17
Przykład – mnożenie macierzy
➔
Implementacja blokowania:
wariant 1 – duże bloki:
• rozmiar bloku dobrany tak, żeby bloki mieściły się w szybkiej pamięci (cache blocking)
➢ CPU – pojedynczy wątek korzysta z bloków macierzy wejściowych przechowywanych w pamięci podręcznej i oblicza wyrazy bloku macierzy wyjściowej
➢ GPU – grupa wątków pobiera z pamięci globalnej bloki macierzy wejściowych do pamięci wspólnej, a pojedynczy wątek może wykonywać operacje dla jednego wyrazu macierzy wyjściowej, korzystając z wielu wyrazów bloków w pamięci wspólnej
wariant 2 – małe bloki:
• rozmiar bloku dobrany tak, żeby wartości mogły być przechowywane w rejestrach
➢ jeden wątek oblicza wartości kilku elementów macierzy wyjściowej
Krzysztof Banaś
Obliczenia równoległe
18
Przykład – mnożenie macierzy
int row = get_global_id(1); int col = get_global_id(0); int local_row = get_local_id(1); int local_col = get_local_id(0); float temp = 0.0; int nr_blocks = N/BLOCK_SIZE;
for(iblock = 0; iblock < nr_blocks; iblock++){
A_local[local_row * BLOCK_SIZE + local_col] = A[row * N + iblock*BLOCK_SIZE + local_col]; B_local[local_row * BLOCK_SIZE + local_col] = B[(local_row+iblock*BLOCK_SIZE) * N + col];
barrier(CLK_LOCAL_MEM_FENCE);
for(i=0; i< BLOCK_SIZE; i++){
temp += A_local[local_row*BLOCK_SIZE+i] *
B_local[i*BLOCK_SIZE+local_col];
}
barrier(CLK_LOCAL_MEM_FENCE);
}
C[row * N + col] = temp; }
Krzysztof Banaś
Obliczenia równoległe
19
Problemy GPGPU
➔
GPU wymagają specyficznych algorytmów
➔
Model programowania GPU jest wciąż złożony
➔
➔
masowa wielowątkowość
zgodny dostęp do obszarów pamięci wspólnej
mało komunikacji, synchronizacji
powstały i zyskują popularność modele oparte na dyrektywach (OpenACC, OpenMP 4.0) Aplikacje dostosowane do GPU zyskują wiele
Jest wiele aplikacji, które zyskują mało lub nic
Krzysztof Banaś
Obliczenia równoległe
20