Wprowadzenie do programowania w środowisku CUDA. Środowisko CUDA

Podobne dokumenty
Wprowadzenie do programowania w środowisku CUDA. Środowisko CUDA

Programowanie kart graficznych. Architektura i API część 2

Programowanie kart graficznych

Programowanie Współbieżne

Programowanie kart graficznych

Programowanie kart graficznych. Architektura i API część 1

CUDA obliczenia ogólnego przeznaczenia na mocno zrównoleglonym sprzęcie. W prezentacji wykorzystano materiały firmy NVIDIA (

JCuda Czy Java i CUDA mogą się polubić? Konrad Szałkowski

Programowanie procesorów graficznych GPGPU. Krzysztof Banaś Obliczenia równoległe 1

Programowanie CUDA informacje praktycznie i. Wersja

Programowanie procesorów graficznych NVIDIA (rdzenie CUDA) Wykład nr 1

Obliczenia na GPU w technologii CUDA

Porównanie wydajności CUDA i OpenCL na przykładzie równoległego algorytmu wyznaczania wartości funkcji celu dla problemu gniazdowego

Programowanie CUDA informacje praktycznie i przykłady. Wersja

Tesla. Architektura Fermi

CUDA PROGRAMOWANIE PIERWSZE PROSTE PRZYKŁADY RÓWNOLEGŁE. Michał Bieńkowski Katarzyna Lewenda

Programowanie aplikacji równoległych i rozproszonych

Programowanie procesorów graficznych GPGPU

CUDA. cudniejsze przyk ady

Programowanie procesorów graficznych w CUDA.

Programowanie PKG - informacje praktycznie i przykłady. Wersja z Opracował: Rafał Walkowiak

Programowanie Równoległe Wykład, CUDA praktycznie 1. Maciej Matyka Instytut Fizyki Teoretycznej

Programowanie Równoległe wykład, CUDA, przykłady praktyczne 1. Maciej Matyka Instytut Fizyki Teoretycznej

Procesory kart graficznych i CUDA wer

Struktura programu. Projekty złożone składają się zwykłe z różnych plików. Zawartość każdego pliku programista wyznacza zgodnie z jego przeznaczeniem.

Przetwarzanie Równoległe i Rozproszone

Co to jest sterta? Sterta (ang. heap) to obszar pamięci udostępniany przez system operacyjny wszystkim działającym programom (procesom).

CUDA ćwiczenia praktyczne

Tablice i struktury. czyli złożone typy danych. Programowanie Proceduralne 1

Programowanie Równoległe wykład 12. OpenGL + algorytm n ciał. Maciej Matyka Instytut Fizyki Teoretycznej

GTX260 i CUDA wer

Procesory kart graficznych i CUDA

Programowanie procesorów graficznych GPGPU

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Programowanie procesorów graficznych GPGPU. Krzysztof Banaś Obliczenia równoległe 1

Procesory kart graficznych i CUDA wer PR

ZASADY PROGRAMOWANIA KOMPUTERÓW

CUDA. obliczenia na kartach graficznych. Łukasz Ligowski. 11 luty Łukasz Ligowski () CUDA 11 luty / 36

Programowanie obiektowe W3

typ y y p y z łoż o on o e n - tab a lice c e w iel e owym m ar a o r we, e stru r kt k ury

Lab 9 Podstawy Programowania

Procesory kart graficznych i CUDA wer

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

CUDA Median Filter filtr medianowy wykorzystujący bibliotekę CUDA sprawozdanie z projektu

Procesy i wątki. Krzysztof Banaś Obliczenia równoległe 1

Przygotowanie kilku wersji kodu zgodnie z wymogami wersji zadania,

DYNAMICZNE PRZYDZIELANIE PAMIECI

Podstawy programowania komputerów

Programowanie kart graficznych. Sprzęt i obliczenia

i3: internet - infrastruktury - innowacje

Stałe, tablice dynamiczne i wielowymiarowe

Programowanie równoległe Wprowadzenie do OpenCL. Rafał Skinderowicz

Zadania na zaliczenie przedmiotu Przetwarzanie równoległe Zebrał dla roku.ak. 2015/2016 Rafał Walkowiak,

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Podstawy programowania. Wykład 7 Tablice wielowymiarowe, SOA, AOS, itp. Krzysztof Banaś Podstawy programowania 1

Tworzenie programów równoległych cd. Krzysztof Banaś Obliczenia równoległe 1

Programowanie kart graficznych. Kompilator NVCC Podstawy programowania na poziomie API sterownika

Zaawansowane programowanie w języku C++ Zarządzanie pamięcią w C++

PROE wykład 2 operacje na wskaźnikach. dr inż. Jacek Naruniec

KURS C/C++ WYKŁAD 6. Wskaźniki

referencje Wykład 2. Programowanie (język C++) Referencje (1) int Num = 50; zdefiniowano zmienną Num (typu int) nadając jej wartość początkową 50.

Moc płynąca z kart graficznych

Podstawy Programowania C++

Akceleracja obliczeń algebry liniowej z wykorzystaniem masywnie równoległych, wielordzeniowych procesorów GPU Świerczewski Ł.

Dodatek A. CUDA. 1 Stosowany jest w tym kontekście skrót GPCPU (od ang. general-purpose computing on graphics processing units).

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Wykład 3 Składnia języka C# (cz. 2)

Tablice, funkcje - wprowadzenie

wykład II uzupełnienie notatek: dr Jerzy Białkowski Programowanie C/C++ Język C - funkcje, tablice i wskaźniki wykład II dr Jarosław Mederski Spis

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Programowanie w języku C++

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

CUDA część 1. platforma GPGPU w obliczeniach naukowych. Maciej Matyka

Wykład 8: klasy cz. 4

Wskaźniki. Informatyka

ZARZĄDZANIE PAMIĘCIĄ W TECHNOLOGII CUDA

Autor: dr inż. Zofia Kruczkiewicz, Programowanie aplikacji internetowych 1

PARADYGMATY PROGRAMOWANIA Wykład 4

Tablice. Monika Wrzosek (IM UG) Podstawy Programowania 96 / 119

Organizacja pamięci w procesorach graficznych

Programowanie równoległe Wprowadzenie do programowania GPU. Rafał Skinderowicz

Przetwarzanie Równoległe i Rozproszone

Jak Windows zarządza pamięcią?

Wskaźniki. nie są konieczne, ale dają językowi siłę i elastyczność są języki w których nie używa się wskaźników typ wskaźnikowy typ pochodny:

Hybrydowy system obliczeniowy z akceleratorami GPU

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 6. Karol Tarnowski A-1 p.

TABLICE W JĘZYKU C/C++ typ_elementu nazwa_tablicy [wymiar_1][wymiar_2]... [wymiar_n] ;

Jacek Matulewski - Fizyk zajmujący się na co dzień optyką kwantową i układami nieuporządkowanymi na Wydziale Fizyki, Astronomii i Informatyki

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

Tablice (jedno i wielowymiarowe), łańcuchy znaków

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

4 NVIDIA CUDA jako znakomita platforma do zrównoleglenia obliczeń

Język C++ zajęcia nr 2

Podstawy algorytmiki i programowania - wykład 4 C-struktury

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Powyższe wyrażenie alokuje 200 lub 400 w zależności od rozmiaru int w danym systemie. Wskaźnik wskazuje na adres pierwszego bajtu pamięci.

Język ludzki kod maszynowy

Struktury czyli rekordy w C/C++

Programowanie z wykorzystaniem technologii CUDA i OpenCL Wykład 1

Zmienne, stałe i operatory

Transkrypt:

Wprowadzenie do programowania w środowisku CUDA Środowisko CUDA 1

Budowa procesora CPU i GPU Architektura GPU wymaga większej ilości tranzystorów na przetwarzanie danych Control ALU ALU ALU ALU Cache DRAM DRAM CPU GPU Środowisko CUDA 2

Architektura SIMT SIMT single-instruction, multiple-thread Mamy wiele procesorów na których działa ten sam program. Dokładnie posiada strumieniowe multiprocesory (SM Streaming Multiprocessor), które zawierają 32 małe, szybkie procesory. Na jednym multiprocesorze wątki (grupę taką nazywamy warpem) muszą wykonywać tą samą instrukcję tego samego programu (instrukcję spod tego samego adresu). Wątki łączymy w bloki, które następnie łączymy w grid. Bloki/gridy stanowią jedno-, dwu- lub trzywymiarową kostkę. Środowisko CUDA 3

Warp itp. Wątki w ramach jednego bloku wykonywane są w podziale na warp-y. Jeden warp to 32 wątki. Półwarp (half-warp) to 16 wątków (pierwsze 16 spośród 32 lub drugie 16 spośród 32). Jeśli w bloku jest mniej niż 32 wątki, nie jest wykorzystywana pełna moc obliczeniowa procesora! W ramach warp-a wszystkie wątki muszą wykonywać tę samą instrukcję, jednak w tym samym czasie inny warp z tego samego bloku może być wykonywany na innym procesorze i te drugie 32 wątki mogą wykonywać wspólnie inną instrukcję. System zarządzający wykonywaniem warp-ów sam decyduje, który w danej chwili uruchomić. Jeśli chcemy zsynchronizować wszystkie wątki w ramach jednego bloku należy użyć metody syncthreads(). Nie ma możliwości synchronizacji wątków w ramach całego grid-u. Zamiast tego można wywoływać z kodu hosta kolejne metody kernela. Występuje problem ze zmiennymi lokalnymi oraz pamięcią współdzieloną. Środowisko CUDA 4

Podział na bloki i wątki Grid Block (0,0) Block (1,0) Block (2,0) Block (0,1) Block (1,1) Block (1,2) Block (1,1) Thread(0,0) Thread(1,0) Thread(2,0) Thread(3,0) Thread(0,1) Thread(1,1) Thread(2,1) Thread(3,1) Thread(0,2) Thread(1,2) Thread(2,2) Thread(3,2) Środowisko CUDA 5

Rodzaje pamięci Każdy z wątków ma dostęp do różnego rodzaju pamięci: Pamięć rejestrów pamięć najbliżej procesora, najszybsza, bardzo mała liczba rejestrów Pamięć lokalna szybka pamięć dostępna dla konkretnego wątku Pamięć współdzielona szybsza pamięć dostępna dla wszystkich wątków w bloku, niezbyt duża, chociaż większa niż lokalna Pamięć stała podobna do pamięci globalnej, jednak szybsza, gdyż nie można jej zapisywać (read-only) Pamięć tekstur podobna do pamięci stałej Pamięć globalna wolna pamięć dostępna dla wszystkich wątków w gridzie Środowisko CUDA 6

Hierarchia pamięci rejestry Wątek pamięć lokalna Blok pamięć współdzielna Grid pamięć globalna Pamięć stała i tekstur Środowisko CUDA 7

Architektura CUDA Środowisko CUDA 8

Architektura CUDA 1. Silnik przetwarzania równoległego wewnątrz NVIDIA GPU. 2. Wsparcie na poziomie kernela OS dla inicjalizacji sprzętu, konfiguracji itp. 3. Sterownik na poziomie użytkownika, który dostarcza API dla programisty na poziomie urządzenia. 4. PTX instruction set architecture (ISA) dla kerneli i funkcji przetwarzania równoległego Środowisko CUDA 9

Przydział bloków do multiprocesorów Środowisko CUDA 10

Wykonywanie kodu na GPU Komputer (lub inne urządzenie) na którym jest zainstalowana karta z CUDA nazywany jest Hostem. Karta graficzna nazwana jest device. Program działający na hoście wywołuje funkcję napisaną dla device. Funkcja taka nazywana jest kernelem (parallel kernel). Po powrocie z wywołania funkcji program na hoście kontynuuje wykonanie własnego kodu. Może również wywołać następny kernel. Środowisko CUDA 11

Wykonywanie programu // kod sekwencyjny Host Kernel0<<<>>>() Device Block Block Grid // kod sekwencyjny Host Kernel1<<<>>>() // kod sekwencyjny Device Grid Host Block Block Block Block Block Block Środowisko CUDA 12

Synchronizacja kodu dla host i device Dopóki nie nastąpi synchronizacja, program na hoście i program na device mogą działać niezależnie. Program na hoście czeka na zakończenie programu na device, gdy: Stara się wykonać kolejną funkcję na tym device Następuje kopiowanie danych pomiędzy pamięcią globalną a pamięcią na hoście. Środowisko CUDA 13

Grid, blok, wątek Wątki grupuje się dwupoziomowo. Funkcję na device uruchamia się dla gridu, który jako składowe posiada bloki. Każdy blok natomiast dzieli się na wątki. Dla wygody indeks wątku w ramach bloku opisany jest jako 3- elementowy wektor. Pozwala to w praktyce używać jedno-, dwu- i trzywymiarowego indeksowania wątku. Na obecnych GPU maksymalna liczba wątków na blok wynosi 512. Jest to uwarunkowane szybkością pamięci współdzielonej. Grid może być opisany jako jedno- lub dwuwymiarowa siatka bloków. Bloki muszą działać poprawnie, niezależnie czy będą wykonywane równolegle, sekwencyjnie (w dowolnej kolejności). Nie ma możliwości wpływania bezpośredniego na kolejność wykonywania bloków ani na ich synchronizację. Dokładne graniczne rozmiary gridu lub bloku mogą zależeć od karty GPU i zmieniać się w przyszłości. Środowisko CUDA 14

Ograniczenia na funkcję kernela Kernel funkcja działająca na device Nie może korzystać z rekurencji W ciele funkcji nie można deklarować zmiennych statycznych Nie może zwracać wartości Ilość parametrów musi być określona Łączny rozmiar parametrów nie może przekraczać 256 bajtów (w Compute Compability 1.x) lub 4KB (w Compute Compability 2.0) Środowisko CUDA 15

Program pisany jak w C/C++ Kompilator NVCC Istnieją dodatkowe specyfikatory oraz znaczniki dla funkcji kernela, rodzaju pamięci oraz wywoływania tych funkcji. Przykładowy Makefile: CODENAME=dodMacierz CC=nvcc FLAGS=-L $(CUDA_LIB_PATH) -L $(CUDA_SDK_PATH)/C/lib/ -I $(CUDA_SDK_PATH)/C/common/inc FLAGS+=$(CUDA_NVCC_MACHINE_FLAG) FLAGS+=-arch=sm_13 FLAGS+=-lcutil $(CODENAME): $(CODENAME).cu $(CC) $(FLAGS) $< -o $@ Środowisko CUDA 16

Numer wątku Ponieważ wątki są uruchamiane w ramach bloku, a blok w ramach gridu, każdemu wątkowi przydzielany jest trzyelementowy wektor określający miejsce wątku w ramach bloku (w zmiennej lokalnej threadidx o polach x,y,z), trzyelementowy wektor określający rozmiar bloku (w zmiennej lokalnej blockdim o polach x,y,z) oraz trzyelementowy wektor określający miejsce bloku w ramach gridu (w zmiennej lokalnej blockidx o polach x,y,z). Jeśli liczba rozmiarów bloku/gridu jest mniejsza niż 3, odpowiednie pola (y lub y,z)mają wartość 1 Środowisko CUDA 17

Kernel funkcje i ich wywołanie // Definicja kernela global void VecAdd(float* A, float* B, float* C) { int i = threadidx.x; C[i] = A[i] + B[i]; } int main() {... // wywołanie kernela VecAdd<<<1, N>>>(A, B, C); } global jest specyfikatorem funkcji urządzenia wywoływanej z hosta, natomiast potrójne znaki mniejszości i większości służą do zdefiniowania gridu oraz bloku w ramach tego gridu. W tym przypadku grid jest jednowymiarowy (pierwszy parametr jest liczbą) i w dodatku zawiera tylko jeden blok. Blok ten też jest jednowymiarową strukturą i zawiera N wątków. Powoduje to, że w funkcji kernela wystarczy używać tylko pola x ze zmiennej threadidx. Środowisko CUDA 18

// Kernel definition Blok dwuwymiarowy global void MatAdd(float A[N][N], float B[N][N], float C[N][N]) { int i = threadidx.x; int j = threadidx.y; C[i][j] = A[i][j] + B[i][j]; } int main() {... // Kernel invocation dim3 dimblock(n, N); MatAdd<<<1, dimblock>>>(a, B, C); } W tym przypadku blok ten też jest dwuwymiarową strukturą i zawiera N*N wątków. Powoduje to, że w funkcji kernela wystarczy użyć tylko pól x i y ze zmiennej threadidx. Jak widać w konstruktorze struktury dim3 można podać dwa wymiary (zamiast 3), ostatni wymiar wynosi wówczas 1. W tym przykładzie parametry funkcji kernela są tablicami również o wymiarach N*N, ale nie jest to konieczne, ani wymagane. Środowisko CUDA 19

Grid dwuwymiarowy // Kernel definition global void MatAdd(float A[N][N], float B[N][N],float C[N][N]) { int i = blockidx.x * blockdim.x + threadidx.x; int j = blockidx.y * blockdim.y + threadidx.y; if (i < N && j < N) C[i][j] = A[i][j] + B[i][j]; } int main() {... // Kernel invocation dim3 dimblock(16, 16); dim3 dimgrid((n + dimblock.x 1) / dimblock.x,(n + dimblock.y 1) / dimblock.y); MatAdd<<<dimGrid, dimblock>>>(a, B, C); } W tym przykładzie blok i grid są dwuwymiarowymi strukturami. Każdy blok jest siatką 16*16 wątków. Natomiast wymiary grida są tak dobrane, aby można było przetworzyć macierz N*N elementów. Jeśli N % 16 nie jest 0, to w blokach skrajnych nie wszystkie wątki powinny pracować i stąd warunek w funkcji kernela. Dla wyznaczenia który wątek odpowiada za obliczenia poszczególnych elementów macierzy należy odpowiednio użyć zmiennych lokalnych blockidx, blockdim, threadidx. Analogicznie będzie jeśli blok będzie strukturą 3-wymiarową, ale trzeba wtedy użyć dodatkowo pól z odpowiednich zmiennych. Środowisko CUDA 20

Deklaracje pamięci W celu korzystania (zarezerwowania/zwolnienia oraz kopiowania z/do) z pamięci globalnej z poziomu hosta należy korzystać z odpowiednich funkcji: cudamalloc(void** ptr, size) rezerwacja pamięci pod wskaźnikiem zwracanym przez ptr o rozmiarze size bajtów cudamemcpy(void *ptrto, void *ptrfrom, size, int tryb) kopiowanie między pamięcią hosta a pamięcią device. Kierunek kopiowania definiuje tryb: cudamemcpyhosttodevice cudamemcpydevicetohost cudafree(void *ptr) zwalnia pamięć zarezerwowaną poprzez cudamalloc. cudamallocpitch(), cudamalloc3d(), cudamemcpy2d(), cudamemcpy3d() analogiczne do powyższych metod, ale z wykorzystaniem wyrównania celem osiągnięcia większej efektywności dostępu do pamięci. Środowisko CUDA 21

Pamięć c.d. Istnieją jeszcze inne metody rezerwowania pamięci z wyrównaniem. Wskaźniki do pamięci w parametrach funkcji kernela muszą być wskaźnikami do pamięci globalnej device. Przykład: // Device code global void VecAdd(float* A, float* B, float* C) { int i = blockdim.x * blockidx.x + threadidx.x; if (i < N) C[i] = A[i] + B[i]; } Środowisko CUDA 22

Kod c.d. int main() // Host code { int N =...; size_t size = N * sizeof(float); // Allocate input vectors h_a and h_b in host memory float* h_a = malloc(size); float* h_b = malloc(size); // Allocate vectors in device memory float *d_a, *d_b, *d_c; cudamalloc((void**)&d_a, size); cudamalloc((void**)&d_b, size); cudamalloc((void**)&d_c, size); // Copy vectors from host memory to device memory cudamemcpy(d_a, h_a, size, cudamemcpyhosttodevice); cudamemcpy(d_b, h_b, size, cudamemcpyhosttodevice); // Invoke kernel int threadsperblock = 256; int blockspergrid = (N + threadsperblock 1)/threadsPerBlock; VecAdd<<<blocksPerGrid, threadsperblock>>>(d_a, d_b, d_c); // Copy result from device memory to host memory // h_c contains the result in host memory cudamemcpy(h_c, d_c, size, cudamemcpydevicetohost); cudafree(d_a); // Free device memory cudafree(d_b); cudafree(d_c); } Środowisko CUDA 23

Pamięć współdzielona Pamięć współdzieloną wskazuje się przez kwalifikator shared Zadeklarowanie zmiennej tego typu w procedurze urządzenia tworzy pamięć, do której dostęp mają wszystkie wątki w jednym bloku. Po deklaracji każdy wątek wypełnia pewną część tej pamięci (każdy inną). Najczęściej jest to kopiowanie komórek z pamięci globalnej. Następnie wątki wykonują operacje matematyczne na tej pamięci (zamiast pamięci globalnej), a po zakończeniu obliczeń przepisują wyniki z pamięci współdzielonej do pamięci globalnej (znów każdy wątek inną część pamięci współdzielonej) Pamięć współdzielona nie może być inicjowana w momencie deklaracji. Gdy używana jest pamięć współdzielona należy synchronizować działanie wątków. Gdyby tego zrobiono, pewna część pamięci współdzielonej może nie być jeszcze zainicjowana lub obliczona. Jest to spowodowane wykonywaniem się bloku w podziale na warpy. Środowisko CUDA 24

Kod (fragment 1) // Matrix multiplication kernel global void MatMulKernel(Matrix A, Matrix B, Matrix C) { // Block row and column int blockrow = blockidx.y; int blockcol = blockidx.x; // Each thread block computes one sub-matrix Csub of C Matrix Csub = GetSubMatrix(C, blockrow, blockcol); // Each thread computes one element of Csub // by accumulating results into Cvalue float Cvalue = 0; // Thread row and column within Csub int row = threadidx.y; int col = threadidx.x; // Loop over all the sub-matrices of A and B that are // required to compute Csub // Multiply each pair of sub-matrices together // and accumulate the results for (int m = 0; m < (A.width / BLOCK_SIZE); ++m) { // Get sub-matrix Asub of A Środowisko CUDA 25

Kod (fragment 2) Matrix Asub = GetSubMatrix(A, blockrow, m); // Get sub-matrix Bsub of B Matrix Bsub = GetSubMatrix(B, m, blockcol); // Shared memory used to store Asub and Bsub respectively shared float As[BLOCK_SIZE][BLOCK_SIZE]; shared float Bs[BLOCK_SIZE][BLOCK_SIZE]; // Load Asub and Bsub from device memory to shared memory // Each thread loads one element of each sub-matrix As[row][col] = GetElement(Asub, row, col); Bs[row][col] = GetElement(Bsub, row, col); // Synchronize to make sure the sub-matrices are loaded // before starting the computation syncthreads(); // Multiply Asub and Bsub together for (int e = 0; e < BLOCK_SIZE; ++e) Cvalue += As[row][e] * Bs[e][col]; } // Synchronize to make sure that the preceding // computation is done before loading two new // sub-matrices of A and B in the next iteration syncthreads(); } // Write Csub to device memory // Each thread writes one element SetElement(Csub, row, col, Cvalue); Środowisko CUDA 26

Klasyfikator device Procedury (wewnętrzne) w ramach urządzenia posiadają klasyfikator device. Pamięć globalna w ramach urządzenia (inna niż z parametrów) posiada również klasyfikator device. Środowisko CUDA 27

Kod (Fragment 3) // Matrices are stored in row-major order: M(row, col) = *(M.elements + row * M.stride + col) typedef struct { int width; int height; int stride; float* elements; } Matrix; // Get a matrix element device float GetElement(const Matrix A, int row, int col){ return A.elements[row * A.stride + col]; } // Set a matrix element device void SetElement(Matrix A, int row, int col, float value){ A.elements[row * A.stride + col] = value; } // Get the BLOCK_SIZExBLOCK_SIZE sub-matrix Asub of A that is located col sub-matrices to the right and row sub-matrices down from the upper-left corner of A device Matrix GetSubMatrix(Matrix A, int row, int col){ Matrix Asub; Asub.width = BLOCK_SIZE; Asub.height = BLOCK_SIZE; Asub.stride = A.stride; Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row + BLOCK_SIZE * col]; return Asub; } // Thread block size #define BLOCK_SIZE 16 Środowisko CUDA 28

Kod (Fragment 4) // Matrix multiplication - Host code; Matrix dimensions are assumed to be multiples of BLOCK_SIZE void MatMul(const Matrix A, const Matrix B, Matrix C){ Matrix d_a; // Load A and B to device memory d_a.width = d_a.stride = A.width; d_a.height = A.height; size_t size = A.width * A.height * sizeof(float); cudamalloc((void**)&d_a.elements, size); cudamemcpy(d_a.elements, A.elements, size,cudamemcpyhosttodevice); Matrix d_b; d_b.width = d_b.stride = B.width; d_b.height = B.height; size = B.width * B.height * sizeof(float); cudamalloc((void**)&d_b.elements, size); cudamemcpy(d_b.elements, B.elements, size, cudamemcpyhosttodevice); Matrix d_c; // Allocate C in device memory d_c.width = d_c.stride = C.width; d_c.height = C.height; size = C.width * C.height * sizeof(float); cudamalloc((void**)&d_c.elements, size); // Invoke kernel dim3 dimblock(block_size, BLOCK_SIZE); dim3 dimgrid(b.width / dimblock.x, A.height / dimblock.y); MatMulKernel<<<dimGrid, dimblock>>>(d_a, d_b, d_c); // Read C from device memory cudamemcpy(c.elements, d_c.elements, size, cudamemcpydevicetohost); // Free device memory cudafree(d_a.elements); cudafree(d_b.elements); cudafree(d_c.elements); } Środowisko CUDA 29