CUDA lab2 opracowanie odpowiedz

background image

Programowanie równoległe i rozproszone – opracowanie lab nr 2

GPGPU (ang. General-Purpose computing on Graphics Processor Units – obliczenia ogólnego
przeznaczenia na układach GPU) - technika, dzięki której GPU, zwykle zajmujący się tylko

obliczeniami związanymi z grafiką komputerową, umożliwia wykonywanie obliczeń ogólnego
przeznaczenia tak jak CPU. Dzięki temu wiele obliczeń, głównie obliczenia równoległe, można

przeprowadzić znacznie szybciej.

Procesor jest przede wszystkim

jednostką wykonującą i podejmującą
decyzje, zgodnie z zaleceniami

oprogramowania.

Procesory wideo są znacznie lepsze w

wykonywaniu rutynowych zadań. GPU
mają duże ilości ALU w porównaniu do

procesorów. W wyniku tego, mogą one
wykonywać duże ilości zadań

matematycznych w odróżnieniu od CPU
(źródło:

http://infocoin.pl/tech/teoria/cpu-vs-
gpu/

).

CPU składa się z kilku rdzeni zoptymalizowanych pod sekwencyjne szeregowe działanie,
podczas gdy GPU ma w znacznym stopniu równoległą architekturę składają się z tysięcy

małych, bardziej efektywnych rdzeni stworzonych do obsługi wielokrotnych zadań
równocześnie (za

http://www.nvidia.com/object/what-is-gpu-computing.html

).

Aktualnie istnieją dwa kompleksowe środowiska do programowania procesorów GPU (ATI
Stream, nVidia CUDA). Równolegle powstaje OpenCL — otwarty standard do obliczeń

równoległych.

CUDA (ang. Compute Unified Device Architecture) - opracowana przez firmę Nvidia
uniwersalna architektura procesorów wielordzeniowych (głównie kart graficznych)

umożliwiająca wykorzystanie ich mocy obliczeniowej do rozwiązywania ogólnych problemów
numerycznych w sposób wydajniejszy niż w tradycyjnych, sekwencyjnych procesorach

ogólnego zastosowania.

background image

CUDA posiada oparte na języku programowania C środowisko programistyczne wysokiego

poziomu, w którego skład wchodzą m.in. specjalny kompilator (nvcc), debugger (cuda-gdb),
profiler oraz interfejs programowania aplikacji. Istnieją biblioteki do Python, Fortran, Java, C#

oraz Matlab. Wspiera Windows, Linux oraz Mac Os X (od wersji 2.0).

Zalety i wady:

Zalety:

Język programowania oparty na językach C/C++, w tym pełna obsługa

szablonów C++.

Model pamięci procesora ściśle odpowiadający architekturze sprzętowej, co

umożliwia świadome, efektywne wykorzystywanie dostępnych zasobów GPU, w
tym pamięci współdzielonej.

Kod uruchamiany na GPU może odczytywać i zapisywać dane z dowolnego
adresu w pamięci GPU.

Pełna kompatybilność programów – napisany dziś program wykonywalny ma w
przyszłości działać bez żadnych zmian na coraz wydajniejszych procesorach

graficznych posiadających coraz większą liczbę rdzeni, rejestrów, pamięci
operacyjnej i innych zasobów.

Dostępność na wszystkich kartach firmy NVIDIA począwszy od serii GeForce 8 w
tym Quadro oraz Tesla.

Obsługa procesorów ARM (od wersji 6.5).

Wady:

CUDA korzysta z podzbioru języka C++. Nie można na przykład definiować
statycznych zmiennych wewnątrz funkcji, a funkcje mogą mieć tylko stałą liczbę

parametrów.

Dla liczb zmiennoprzecinkowych podwójnej precyzji (dostępnych w nowszych

procesorach) istnieją pewne odstępstwa od standardu w zakresie zaokrąglania
liczb.

Przepustowość i opóźnienia magistrali PCI-Express łączącej CPU i GPU mogą być
wąskim gardłem w przypadku przesyłania dużej ilości danych.

CUDA jest dostępna jedynie dla kart graficznych produkowanych przez firmę
NVIDIA

background image

Jądra (ang. kernels) i wątki w CUDA

Współbieżne bloki aplikacji są wykonywane na urządzeniu jako jądra (kernels). Tylko jedno
jądro może się wykonywać w danej chwili. Wiele wątków (tysiące) wykonuje to samo jądro.

Różnice pomiędzy wątkami CUDA a CPU: - wątki CUDA są „bardzo lekkie” (bardzo krótki czas
tworzenia, natychmiastowe przełączanie), - CUDA używa tysiące wątków do osiągnięcia

wydajności, wielordzeniowe CPU może obsługiwać tylko kilka wątków.

Słowniczek: - urządzenie = GPU, - host = CPU, - kernel – funkcja uruchamiana na

urządzeniu.

Jądro CUDA jest wykonywane przez tablicę wątków. Każdy wątek uruchamia ten sam kod.

Każdy wątek posiada ID używany do obliczania adresów pamięci oraz podejmowania decyzji.

Siatka (grid) bloków z wątkami

Jądro uruchamia siatkę bloków z wątkami.
Wątki w obrębie bloku korzystają ze

wspólnego obszaru pamięci oraz mogą być
synchronizowane. Wątki w różnych blokach

nie mogą współpracować.

Adres bloku w siatce

może być

dwuwymiarowy (jest strukturą z polami x
oraz y). Adres wątku w bloku może być dwuwymiarowy (jest strukturą z polami x, y). Na

powyższym rysunku adres jest jednowymiarowy — pola y, z są równe 1. Zatem jeżeli wątek
operuje na wektorze 0, ..., N*M to numer elementu wektora może być obliczony przy pomocy

wyrażenia – numer_wątku_w_bloku+rozmiar_bloku*numer_bloku.

Skalowalność

Sprzęt może zaszeregować dowolny blok na dowolnym procesorze. Brak założeń o kolejności
wykonywania bloków.

background image

Model pamięci w architekturze CUDA

Global memory (odczyt i zapis) – wolna i

niecache'owalna, host może odczytywać i
zapisywać do tej pamięci

Texture memory (tylko odczyt) – pamięć cache

zooptymalizowana do wzorców z dostępem
dwuwymiarowym

Constant memory – przechowywane są tam stałe

oraz argumenty jądra, wolna ale cache'owalna

Shared memory – mała, szybka, współdzielona
przez blok

Local memory – używana na wszystko, co się nie

zmieściło w rejestrach wątku, część pamięci
globalnej więc wolna i niecache'owalna

Registers – szybka, każdy wątek posiada swoje

CUDA SDK

Pliki źródłowe kompilowane za pomocą nvcc mogą zawierać kod hosta jak również kod
urządzenia

nvcc rozdziela kod dla urządzenia oraz kod dla hosta

kod dla urządzenia kompiluje do postaci assemblerowej (PTX kod) i/lub binarnej

postaci (obiekt cubin),

modyfikuje kod hosta zamieniając znaczniki <<<...>>> występujące przy
wywoływaniu jądra na potrzebne funkcje środowiska CUDA ładujące i

uruchamiające każde skompilowane jądro z kodu PTX i/lub obiektu cubin.

Zmodyfikowany plik hosta jest traktowany jako plik w języku C i jest zostawiany do
kompilacji za pomocą innego narzędzia lub do skompilowania podczas ostatniego etapu

kompilacji nvcc.

Aplikacja następnie może:

zlinkować do skompilowanego pliku hosta (najpopularniejszy przypadek)

lub zignorować zmodyfikowany plik hosta i użyć API sterownika CUDA w celu
załadowania i wykonania kodu PTX lub obiektu cubin.

background image

Wbudowane typy wektorów

int1, float2, double3, char4, … - typy wektorów zbudowane na podstawie typów

całkowitych i zmiennoprzecinkowych. Możliwość stworzenia maksymalnie
czterowymiarowego wektora, dostęp do składowych poprzez zmienne x, y, z i w.

Generowanie: „make_<type name>”.

int2 make_int2(int x, int y);

dim3 – wektor liczb całkowitych bazujący na typie uint3, który używany jest to
określania wymiarów. Jeśli, któryś z elementów pozostawimy niezdefiniowany to

domyślnie przyjmie on wartość 1.

dim3 grid( 512 ); // 512 x 1 x 1
dim3 block( 1024, 1024 ); // 1024 x 1024 x 1

Wbudowane zmienne

Wbudowane zmienne określają wymiary siatki oraz blok wątków i indeks bloku oraz wątku w

bloku. Zmienne te dostępne są tylko w obrębie funkcji wykonywanych w urządzeniu (GPU).

gridDim – zmienna typu dim3, zawiera wymiary siatki.

blockIdx – zmienna typu uint3, zawiera indeks bloku w siatce.

blockDim – zmienna typu dim3, zawiera wymiary bloku.

threadIdx – zmienna typu uint3, zawiera indeks wątku w bloku.

int ThreadIDX= threadIdx.x+blockDim.x*blockIdx.x;

Wykonywanie kodu na GPU

Jądra są funkcjami języka C z pewnymi ograniczeniami:

Nie mogą odwoływać się do pamięci CPU.

Muszą zwracać void jako wynik.

Nie mogą przyjmować zmiennej liczby argumentów.

Nie mogą być rekurencyjne.

Nie mogą deklarować zmiennych statycznych.

Parametry są kopiowane automatycznie z CPU na GPU. Kod jąder umieszcza się w plikach

.cu.

Deklarowane z kwalifikatorem __global__ oznaczającym funkcję wywoływaną

z CPU i wykonującą się na GPU. Inne kwalifikatory to:

__device__ oznacza funkcję wywoływaną z GPU i wykonującą się na GPU.

__host__ oznacza funkcję wywoływaną z CPU w wykonującą się na CPU (domyślnie)

__device__ i __host__ mogą być łączone.

__global__ i __host__ nie mogą być łączone.

Przykład deklaracji:

__global__ void doSomethingOnDevice(...)
{

….

}

background image

Wywołanie jądra

Wywoływanie jądra odbywa się w podobny sposób do wywołania zwykłej funkcji w C, z tym że
dodatkowo musimy zadeklarować wymiar siatki, wymiar każdego bloku wątków i opcjonalnie

liczbę bajtów w przestrzeni wspólnej, która jest dynamicznie alokowana przez blok dodatkowo
do już przydzielonej statycznie pamięci (domyślnie 0) używając nawiasów <<< … , …[, …]

>>>.

int blocksize=256;
dim3 dimBlock (blocksize);

dim3 dimGrid( ceil( N / (float)blocksize) );
inc_gpu<<<dimGrid, dimBlock>>>(d_a, N);

Kwalifikatory typów zmiennych w CUDA

Deklaracja zmiennej

Pamięć

Zasięg

Czas życia taki jak

__device__ __local__ int LocalVar;

lokalna

wątek

wątek

__device__ __shared__ int SharedVar;

współdzielona blok

blok

__device__ int GlobalVar;

globalna

siatka bloków aplikacja

__device__ __constant__ int ConstantVar;

stała

siatka bloków aplikacja

Opcja __device__ jest opcjonalna, kiedy używana jest z __local__, __shared__ lub
__constant__.

Zmienne bez żadnego kwalifikatora zapisywane są w rejestrach wątku (skalary oraz
wbudowane typy wektorów). Te dane, które nie zmieszczą się w rejestrach lub tablice

zapisywane są do pamięci lokalnej.

Kiedy deklarujemy zmienną w pamięci współdzielonej jako zewnętrzną tablice w następujący

sposób:

extern __shared__ float shared[];

rozmiar tablicy określany jest podczas uruchamiania jądra (parametr trzeci wywołania jądra).

Wszystkie zmienne zadeklarowane w ten sposób mają początek w tym samym adresie w
pamięci.

__global__ void kernel(…)

{

extern __shared__ float sData[];

}
int main(void)

{

smBytes = blockSize*sizeof(float);
kernel<<<nBlocks, blockSize, smBytes>>>(…);

}

Wskaźniki mogą wskazywać tylko na pamięć za alokowaną lub zadeklarowaną w pamięci
globalnej GPU.

background image

Gdzie deklarować zmienne?

Alokacja pamięci GPU

CPU zarządza pamięcią GPU:

cudaMalloc(void ** pointer, size_t nbytes)

cudaMemset(void * pointer, int value, size_t count)

cudaFree(void* pointer)

int n = 1024;
int nbytes = 1024*sizeof(int);

// Wskaźnik na pamięć GPU;
int * d_a = 0; //

cudaMalloc( (void**)&d_a, nbytes );
// Próba odwołania się do pamięci wskazywanej przez d_a

// w kodzie CPU prowadzi do błędu ochrony pamięci
cudaMemset( d_a, 0, nbytes);

cudaFree(d_a);

Kopiowanie danych

cudaMemcpy

(

void

*dst,

void

*src,

size_t

nbytes,

enum cudaMemcpyKind

direction);

„Direction” określa lokalizację (urządzenie lub host) zmiennych src lub dst.

Blokuje wątek CPU – wznawia wątek jak wszystkie dane zostaną przekopiowane.

Nie rozpoczyna kopiowania, dopóki wcześniejsze wywołania CUDA nie zostały skończone.

enum cudaMemcpyKind

cudaMemcpyHostToDevice

cudaMemcpyDeviceToHost

cudaMemcpyDeviceToDevice

Funkcja cudaMemcpyAsync() nie blokuje kodu CPU

Synchronizacja CPU z GPU

Wywołania jąder są asynchroniczne

Sterowanie powraca od razu do CPU bez oczekiwania na zakończenie wątków
jądra.

Jądro jest kolejkowane i wykona się po zakończeniu wszystkich poprzednich

wywołań CUDA.

Funkcja cudaMemcpy jest synchroniczna

Kopiowanie rozpocznie się po wykonaniu poprzednich wywołań CUDA.

background image

Sterowanie powraca do CPU po wykonaniu kopii.

Kopie pomiędzy pamięciami GPU są asynchroniczne.

Funkcja cudaThreadSynchronize() wstrzymuje kod CPU do momentu wykonania
wszystkich kolejkowanych wywołań CUDA.

Synchronizacja wątków w bloku

CUDA umożliwia współpracę wątków w ramach bloku. Do tego celu służy funkcja void
_syncthreads()
.

Synchronizacja barierowa: każdy wątek w bloku oczekuje, aż wszystkie inne wątki

osiągną punkt wywołania funkcji _syncthreads().

Bariera dotyczy wyłącznie wątków w bloku, nie w siatce. Tylko wątki w obrębie danego
bloku mogą korzystać z pamięci współdzielonej.

Stosowana w celu uniknięcia wyścigów przy dostępie do pamięci wspólnej.

Jeżeli występuje w kodzie warunkowym np. instrukcja if, to musimy zadbać aby
wszystkie wątki ją wywołały.

Atomowe operacje w kodzie GPU

Operacje atomowe na danych typu int/unsigned w pamięci globalnej.

add, sub, min, max

and, or, xor

inc, dec

atomicAdd()

Kody błędów CUDA

Wszystkie funkcje API, z wyjątkiem wywołań jąder zwracają kody błędów.

Wyrażenie typu cudaError_t

Funkcja cudaError_t cudaGetLastError(void) zwraca kod ostatniego błędu

char* cudaGetErrorString(cudaError_t code) zwraca ciąg znaków zakończony zerem
zawierający opis błędu w języku angielskim

printf(“%s\n”, cudaGetErrorString( cudaGetLastError() ) );

Przykładowe biblioteki CUDA

CUBLAS — podzbiór bibliotek BLAS, operacje ma macierzach i wektorach

CUFFT — szybka transformata Fourier'a (jednowymiarowa i dwuwymiarowa)

background image

Do zadania:

Filtracja obrazów — filtracja obrazów jest zaliczana do metod cyfrowego przetwarzania

sygnałów. Filtracja jest operacją matematyczną na pikselach obrazu źródłowego, w której
wyniku uzyskiwany jest nowy, przekształcony obraz. Filtrację określa się jako przekształcenie

kontekstowe, gdyż dla wyznaczenia nowej wartości piksela obrazu docelowego potrzebna jest
informacja z wielu pikseli obrazu źródłowego.

Filtracja stosowana jest przeważnie jako metoda wydobycia z oryginalnego obrazu szeregu

informacji w celu ich dalszej obróbki. Informacjami takimi mogą być: położenie krawędzi,
pozycje rogów obiektów itp. Innym zastosowaniem filtracji jest usuwanie szumów (filtr

medianowy i inne) lub rozmycie obrazu (filtry uśredniające, Gaussa). Filtrację można
przeprowadzać zarówno w dziedzinie przestrzennej, jak i częstotliwościowej. Filtracje w

dziedzinie przestrzennej uzyskuje się wykorzystując operacje splotu. W dziedzinie
częstotliwości odpowiednikiem splotu jest operacja mnożenia transformat obrazu i filtru.

Filtry dolnoprzepustowe. Działanie tego typu filtrów opiera się na usuwaniu elementów

obrazu o wysokiej częstotliwości i przepuszczaniu elementów o niskiej częstotliwości. Ponieważ
większość szumów występujących w obrazach zawiera się w wysokich częstotliwościach, filtry

te przeważnie wykorzystuje się właśnie do eliminacji zakłóceń.

Filtry górnoprzepustowe. Filtry te działają w sposób odwrotny do filtrów
dolnoprzepustowych, tłumią one nisko częstotliwościowe elementy obrazu, wzmacniają

natomiast elementy o wysokich częstotliwościach (szczegóły). Wynikiem działania tego typu
filtrów jest podkreślenie, uwypuklenie elementów obrazu o dużej częstotliwości poprzez

zwiększenie ich jasności, koloru itp. Dla obrazu jako całości efektem jest zazwyczaj
zwiększenie kontrastu poprzez podkreślenie ostrych krawędzi obiektów.

OpenCV – biblioteka funkcji wykorzystywanych podczas obróbki obrazu, oparta na otwartym

kodzie i zapoczątkowana przez Intela. Biblioteka ta jest wieloplatformowa, można z niej
korzystać w Mac OS X, Windows, jak i Linux. Autorzy jej skupiają się na przetwarzaniu obrazu

w czasie rzeczywistym.

background image

Pytania prowadzącego:

jakie mamy rodzaje pamięci w CUDA?

co to jest i jak działa Kernel?

dlaczego w obecnych czasach zauważamy wzrost zainteresowania obliczeniami w
CUDA?

rozwiń skrót CUDA

jakie zasięgi mają pamięci?

co potrzebujesz żeby programować w CUDA?

jakie mamy kompilatory?


Wyszukiwarka

Podobne podstrony:
CUDA lab2 opracowanie odpowiedz
finanse lokalne opracowane odpowiedzi, Różne Dokumenty, MARKETING EKONOMIA ZARZĄDZANIE
MPI lab4 opracowanie odpowiedz
PODSTAWY PRAWA, Prawo - test (opracowane odpowiedzi), 1
OPRACOWANE ODPOWIEDZI id 337615 Nieznany
Lab2-PA-odpowiedzi skokowe
Zbiór2 pytań wraz z opracowanymi odpowiedziami z przedmiot
opracowane odpowiedzi do zestawu 2
lab2 Opracowanie02 id 750512 Nieznany
opracowane odpowiedzi na pytani Nieznany
finanse lokalne opracowane odpowiedzi--ściaga, Różne Dokumenty, MARKETING EKONOMIA ZARZĄDZANIE
prawo test opracowanie odpowiedzi 15, Agh kier. gig. rok 3 sem 6, podziemka podsadzka, sem VI, Prawo
Opracowane odpowiedzi część
OpenMP lab1 opracowanie odpowiedz
opracowane odpowiedzi, MRiT-Mechanika i Budowa Maszyn, Fizyka
kolo opracowanie odpowiedzi
Egzamin Podstawy Projektowania 12 opracowane odpowiedzi

więcej podobnych podstron