Programowanie procesorów graficznych GPGPU
Transkrypt
Programowanie procesorów graficznych GPGPU
Programowanie procesorów graficznych GPGPU
Krzysztof Banaś
Obliczenia równoległe
1
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
2
Model pamięci OpenCl
Krzysztof Banaś
Obliczenia równoległe
3
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
4
OpenCL – zależności zasobów
Krzysztof Banaś
Obliczenia równoległe
5
Przykład – mnożenie macierzwektor
➔
Prosta strategia: jeden wątek – jeden element wektora wyniku
dla dużych wektorów oznacza rozmiar grupy 1
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
6
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
7
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
8
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
9
Redukcja – GPU
Krzysztof Banaś
Obliczenia równoległe
10
Redukcja – GPU
➔
Wariant 1: standardowe drzewo redukcji 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
11
GPU memory banks
Krzysztof Banaś
Obliczenia równoległe
12
Redukcja – GPU
Krzysztof Banaś
Obliczenia równoległe
13
Redukcja – GPU
➔
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]; } } ➔
ciąg dalszy kodu iloczynu macierzwektor
if (get_local_id(0) == 0) W[y] = partialDotProduct[0]; barrier(CLK_LOCAL_MEM_FENCE); Krzysztof Banaś
Obliczenia równoległe
14