4. Funkcionális primitívek GPUn

Hasonló dokumentumok
Videókártya - CUDA kompatibilitás: CUDA weboldal: Példaterületek:

Párhuzamos és Grid rendszerek

OpenCL - The open standard for parallel programming of heterogeneous systems

Magas szintű optimalizálás

CUDA haladó ismeretek

Pénzügyi algoritmusok

OpenCL alapú eszközök verifikációja és validációja a gyakorlatban

Bevezetés a programozásba. 8. Előadás: Függvények 2.

Programozás C++ -ban 2007/7

Grafikus kártyák, mint olcsó szuperszámítógépek - I.

Készítette: Trosztel Mátyás Konzulens: Hajós Gergely

Bevezetés a programozásba 2

Programozás II. 2. Dr. Iványi Péter

3. Osztályok II. Programozás II

Eichhardt Iván GPGPU óra anyagai

GPU Lab. 14. fejezet. OpenCL textúra használat. Grafikus Processzorok Tudományos Célú Programozása. Berényi Dániel Nagy-Egri Máté Ferenc

Mechatronika és mikroszámítógépek 2017/2018 I. félév. Bevezetés a C nyelvbe

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Kivételkezelés a C++ nyelvben Bevezetés

GPU Lab. 4. fejezet. Fordítók felépítése. Grafikus Processzorok Tudományos Célú Programozása. Berényi Dániel Nagy-Egri Máté Ferenc

CUDA alapok CUDA projektek. CUDA bemutató. Adatbányászat és Webes Keresés Kutatócsoport SZTAKI

Dinamikus csatolású függvénykönyvtár készítése és használata Plugin-szerű betöltés Egyszeű C++ osztályok készítése

Diplomamunka. Miskolci Egyetem. GPGPU technológia kriptográfiai alkalmazása. Készítette: Csikó Richárd VIJFZK mérnök informatikus

Eichhardt Iván GPGPU óra anyagai

GPGPU-k és programozásuk Dezső, Sima Sándor, Szénási

A verem (stack) A verem egy olyan struktúra, aminek a tetejéről kivehetünk egy (vagy sorban több) elemet. A verem felhasználása

Clang Static Analyzer belülről

Jurek Zoltán, Tóth Gyula

Google Summer of Code OpenCL image support for the r600g driver

Programozás C nyelven FELÜLNÉZETBŐL elhullatott MORZSÁK. Sapientia EMTE

Függvény pointer. Feladat: Egy tömbben soroljunk fel függvényeket, és hívjuk meg valahányszor.

1. Bevezetés szeptember 9. BME Fizika Intézet. Szám. szim. labor ea. Tőke Csaba. Tudnivalók. feladat. Tematika. Moodle Házi feladatok

A függvény kód szekvenciáját kapcsos zárójelek közt definiáljuk, a { } -ek közti részt a Bash héj kód blokknak (code block) nevezi.

Miről lesz ma szó? A PROGAMOZÁS ALAPJAI 1. Dinamikus változók. Dinamikus változók. Dinamikus változók. Dinamikus változók. 7.

1.1. A forrásprogramok felépítése Nevek és kulcsszavak Alapvető típusok. C programozás 3

Programozás C és C++ -ban

Bevezetés a programozásba. 9. Előadás: Rekordok

1. Template (sablon) 1.1. Függvénysablon Függvénysablon példányosítás Osztálysablon

GPGPU-k és programozásuk

Programozás II gyakorlat. 4. Öröklődés

Mobil Informatikai Rendszerek

Felhasználó által definiált adattípus

JUnit. JUnit használata. IDE támogatás. Parancssori használat. Teszt készítése. Teszt készítése

Programozás alapjai gyakorlat. 2. gyakorlat C alapok

Grafikus kártyák, mint olcsó szuperszámítógépek - II.

AliROOT szimulációk GPU alapokon

OpenGL Compute Shader-ek. Valasek Gábor

Programozás alapjai. 5. előadás

Programozás II. 4. Dr. Iványi Péter

Gregorics Tibor Modularizált programok C++ nyelvi elemei 1

KÉPFELDOLGOZÓ ALGORITMUSOK FEJLESZTÉSE GRAFIKUS HARDVER KÖRNYEZETBEN

GPGPU és számítások heterogén rendszereken

C++ Standard Template Library (STL)

GPGPU alapok. GPGPU alapok Grafikus kártyák evolúciója GPU programozás sajátosságai

Programozás I. 5. Előadás: Függvények

Programozás 1. Dr. Iványi Péter

Bevezetés a programozásba Előadás: A const

GPU Lab. 5. fejezet. A C++ fordítási modellje. Grafikus Processzorok Tudományos Célú Programozása. Berényi Dániel Nagy-Egri Máté Ferenc

Programozási nyelvek (ADA)

Bevezetés a programozásba Előadás: Objektumszintű és osztályszintű elemek, hibakezelés

C programozás. 6 óra Függvények, függvényszerű makrók, globális és

1. Bevezetés A C++ nem objektumorientált újdonságai 3

Kivételek, kivételkezelés a C++ nyelvben

Járműfedélzeti rendszerek II. 3. előadás Dr. Bécsi Tamás

OPENCV TELEPÍTÉSE SZÁMÍTÓGÉPES LÁTÁS ÉS KÉPFELDOLGOZÁS. Tanács Attila Képfeldolgozás és Számítógépes Grafika Tanszék Szegedi Tudományegyetem

1. Mi a fejállományok szerepe C és C++ nyelvben és hogyan használjuk őket? 2. Milyen alapvető változókat használhatunk a C és C++ nyelvben?

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós április 4. Széchenyi István Egyetem, Gy r

GPGPU. GPU-k felépítése. Valasek Gábor

Függvények. Programozás I. Hatwágner F. Miklós november 16. Széchenyi István Egyetem, Gy r

Kivételek kezelése (exception handling) Hibakezelés old style. Kivételkezelés

5-6. ea Created by mrjrm & Pogácsa, frissítette: Félix

C++ programok fordítása

Bevezetés a programozásba I.

Alprogramok, paraméterátadás

Java I. A Java programozási nyelv

117. AA Megoldó Alfréd AA 117.

Adat- és feladat párhuzamos modell Az ISO C99 szabvány részhalmaza

A CUDA előnyei: - Elszórt memória olvasás (az adatok a memória bármely területéről olvashatóak) PC-Vilag.hu CUDA, a jövő technológiája?!

Programozás 6. Dr. Iványi Péter

Operációs rendszerek III.

Újrakonfigurálható technológiák nagy teljesítményű alkalmazásai

Az UPPAAL egyes modellezési lehetőségeinek összefoglalása. Majzik István BME Méréstechnika és Információs Rendszerek Tanszék

Programozási nyelvek Java

Bevezetés a programozásba II. 8. Előadás: Osztályok, objektumok, osztályszintű metódusok

Kivételkezelés, beágyazott osztályok. Nyolcadik gyakorlat

LabView Academy. 4. óra párhuzamos programozás

Programozási nyelvek Java

Programozási nyelvek JAVA EA+GY 1. gyakolat

Programozás I. gyakorlat

A programozás alapjai

Újrakonfigurálható technológiák nagy teljesítményű alkalmazásai

A PROGAMOZÁS ALAPJAI 1. Függvény mint függvény paramétere. Függvény mint függvény paramétere. Függvény mint függvény paramétere

Programozás C++ -ban

C vagy C++? Programozási Nyelvek és Fordítóprogramok Tanszék. Pataki Norbert. Programozási Nyelvek I.

Java II. I A Java programozási nyelv alapelemei

Az osztályok csomagokba vannak rendezve, minden csomag tetszőleges. Könyvtárhierarhiát fed: Pl.: java/util/scanner.java

Python tanfolyam Python bevezető I. rész

Mérnöki programozás 7. Szerkesztette: dr. Vass Péter Tamás

A C programozási nyelv I. Bevezetés

GPGPU programozás lehetőségei. Nagy Máté Ferenc Budapest ALICE ELTE TTK Fizika MSc 2011 e-science Café

Átírás:

4. Funkcionális primitívek GPUn

GPU API emlékeztető Jelenleg a következő eszközök állnak rendelkezésre GPUs kódok futtatására: DirectX vagy OpenGL vagy Vulkan Compute Shader Ez grafikai célokra van kitalálva, nem tudományos számításokra Nincs teljes körű kontroll a memória elrendezés felett (nem lehet átcastolni memóriát), nincs garantálva a pontosság minden IEEE lebegőpontos műveletre Microsoft C++ AMP leálltak a fejlesztéssel, csak egy demonstráció volt OpenMP, OpenACC és társai: pragma alapú párhuzamosítás a forciklusok előtt, nem túl elegáns OpenCL: A C kernelnyelv nem skálázik, összetett dolgokra és generikusságra nem alkalmas, a 2.1-be tervezett C++ kernelnyelv még sehol nincs implementálva Berényi D. - Nagy-Egri M. F. 2

GPU API emlékeztető Jelenleg a következő eszközök állnak rendelkezésre GPUs kódok futtatására: Clang / GCC is tud C/C++ kódokat a megfelelő köztesekre átfordítani AMD HCC stack: csak AMD eszközökön működő, a C++ AMP szabvány továbbfejlesztésére épülő nyelvi kiterjesztés Mi az alábbiakkal fogunk részletesebben foglalkozni: NVIDIA CUDA: C++ nyelvi kiterjesztés, aktívan fejlesztik, azonban csak NVIDIA GPUkon fut (bár vannak kísérletek ennek független eszközökkel való átfordítására KHRONOS SYCL standard és annak a ComputeCpp implementációja Még eléggé béta állapotú, de szintén aktívan fejlesztett könyvtár (NEM nyelvi kiterjesztés!!!) Előnye, hogy elvileg minden OpenCL-t támogató szabványos implementáció fölött képes futni (a gyakorlatban ez jelenleg Intel és AMD eszközöket jelent) Berényi D. - Nagy-Egri M. F. 3

CUDA vs SYCL Berényi D. - Nagy-Egri M. F. 4

CUDA vs SYCL Hasonlóságok: Mindkét rendszer lehetővé teszi a C++ nyelvi eszközök használatát a GPU-kon. Nyilván azokat a részeket, amiket az eszköz nem támogat, nem lehet használni, ebben a közös probléma az exception handling, ezért lényegében a C++ STL-je nem használható GPUkon. Mindkét rendszer alól lehet a grafikai elemeket használni, így textúrákat, grafikus interopot, vektor regisztereket, stb. Mindkét rendszer single-source, azaz közös forrásfájlban található az eszköz és a gazda oldali kód (ellentétben pl. az OpenCL-el). Berényi D. - Nagy-Egri M. F. 5

CUDA vs SYCL Különbségek: A CUDA támogat dinamikus allokációt az eszközoldali kódban, a SYCL nem. A CUDA fordítója az nvcc, amely PTX köztest állít elő A SYCL forráskód először C++ kódra és SPIR / SPIRV köztesre fordul, majd a C++ kódot egy gazda oldali fordító fordítja binárissá, amely a megfelelő OpenCL hívásokkal betölti a köztest. Berényi D. - Nagy-Egri M. F. 6

CUDA vs SYCL Különbségek: A CUDA egy nyelvi kiterjesztés, a függvényeket annotálni kell, hogy az eszközön, a gazdaoldalon, vagy mindkettőn lesznek majd meghívva. SYCL-ben ilyen megkülönböztetés nincs. //Ez csak host oldalon hívható: global int sq(int x) return x*x; //Ez csak az eszközön: device int sq(int x) return x*x; //Ez mindkettőn: global device int sq(int x) return x*x; Berényi D. - Nagy-Egri M. F. 7

CUDA vs SYCL Különbségek: SYCL-ben nincs megkülönböztetve a kód, nem kell a függvényeket és lambdákat annotálni. A fordító ismeri fel, hogy melyik függvény az, ami majd az eszköz hívás belépési pontja lesz és az abban hivatkozott függvényeket megpróbálja az eszközre is lefordítani. Amíg nem használunk olyan funkcionalitást az eszközoldali kódon belül, amit nem lehet a GPUra lefordítani, addig a fordító szó nélkül feldolgozza a tetszőleges (akár nem is GPUra szánt) kódokat. Berényi D. - Nagy-Egri M. F. 8

CUDA vs SYCL Jelenlegi állapot: CUDA 10 (2018 szeptember óta) C++ 14 támogatás az eszközoldali kódban is, helyenként limitációkkal SYCL 1.2.1 konform GPUs implementáció: ComputeCpp 1.0.2 (2018 október) Clang 6.0 alapú, így a legtöbb c++17 szolgáltatás elérhető benne Berényi D. - Nagy-Egri M. F. 9

CUDA vs SYCL Példakódok, SDK: A CUDA példakódokat a CUDA telepítő teszi fel. CUDA dokumentáció SYCL specifikáció Compute Cpp SDK: sok hasznos példakód: https://github.com/codeplaysoftware/computecpp-sdk Berényi D. - Nagy-Egri M. F. 10

CUDA vs SYCL Hibakezelés: CUDA: Error kódokat használ host device const char* cudageterrorname ( cudaerror_t error ) Returns the string representation of an error code enum name. host device const char* cudageterrorstring ( cudaerror_t error ) Returns the description string for an error code. host device cudaerror_t cudagetlasterror ( void ) Returns the last error from a runtime call. host device cudaerror_t cudapeekatlasterror ( void ) Returns the last error from a runtime call. Berényi D. - Nagy-Egri M. F. 11

CUDA vs SYCL Hibakezelés: SYCL: Exception-ök int main() try auto plats = cl::sycl::platform::get_platforms(); if (plats.empty()) throw std::runtime_error "No OpenCL platform found." ; catch (cl::sycl::exception e) catch (std::exception e) return 0; Berényi D. - Nagy-Egri M. F. 12

Funkcionális primitívek GPU-n A három legfontosabb funkcionális primitív, a map, zip, reduce hatékonyan párhuzamosítható, így ezeket implementáljuk először a GPU-kra. Berényi D. - Nagy-Egri M. F. 13

CUDA map A teljes kód itt érhető el: https://github.com/wigner-gpu-lab/teaching/blob/master/gpgpu2/map.cu Berényi D. - Nagy-Egri M. F. 14

CUDA map Az eszközoldali kód törzse: template<typename F, typename T> global void map(f f, T* src, T* dst) int id = blockidx.x * blockdim.x + threadidx.x; dst[id] = f(src[id]); Berényi D. - Nagy-Egri M. F. 15

CUDA map Az eszközoldali kód törzse: Ez a belépési pont a gazdaoldalról lesz hívva. template<typename F, typename T> global void map(f f, T* src, T* dst) int id = blockidx.x * blockdim.x + threadidx.x; dst[id] = f(src[id]); Berényi D. - Nagy-Egri M. F. 16

CUDA map Az eszközoldali kód törzse: template<typename F, typename T> global void map(f f, T* src, T* dst) int id = blockidx.x * blockdim.x + threadidx.x; dst[id] = f(src[id]); Az eszközoldalon beépített változók a számítási tartomány paraméterei (mint opencl-ben) Berényi D. - Nagy-Egri M. F. 17

CUDA map Az eszközoldali kód törzse: template<typename F, typename T> global void map(f f, T* src, T* dst) int id = blockidx.x * blockdim.x + threadidx.x; dst[id] = f(src[id]); A meghívott f függvénynek device annotáltnak kell lennie! Berényi D. - Nagy-Egri M. F. 18

CUDA map A gazdaoldali kód törzse: int main() using T = double; using P = T*; int n = 16; size_t sz = n*sizeof(t); std::vector<t> h_src(n), h_dst(n); P d_src, d_dst; cudamalloc( &d_src, sz ); cudamalloc( &d_dst, sz ); cudamemcpy( d_src, h_src.data(), sz, cudamemcpyhosttodevice); cudamemcpy( d_dst, h_dst.data(), sz, cudamemcpyhosttodevice); Berényi D. - Nagy-Egri M. F. 19

CUDA map A gazdaoldali kód törzse: int main() using T = double; using P = T*; int n = 16; size_t sz = n*sizeof(t); std::vector<t> h_src(n), h_dst(n); P d_src, d_dst; cudamalloc( &d_src, sz ); cudamalloc( &d_dst, sz ); Memóriafoglalás az eszközön cudamemcpy( d_src, h_src.data(), sz, cudamemcpyhosttodevice); cudamemcpy( d_dst, h_dst.data(), sz, cudamemcpyhosttodevice); Berényi D. - Nagy-Egri M. F. 20

CUDA map A gazdaoldali kód törzse: int main() using T = double; using P = T*; int n = 16; size_t sz = n*sizeof(t); std::vector<t> h_src(n), h_dst(n); P d_src, d_dst; cudamalloc( &d_src, sz ); cudamalloc( &d_dst, sz ); Memória másolás az eszközre cudamemcpy( d_src, h_src.data(), sz, cudamemcpyhosttodevice); cudamemcpy( d_dst, h_dst.data(), sz, cudamemcpyhosttodevice); Berényi D. - Nagy-Egri M. F. 21

CUDA map A gazdaoldali kód törzse: int blocksize = 4; int gridsize = (int)ceil((float)n/blocksize); auto sq = [] device (auto const& x) return x*x; ; map<<<gridsize, blocksize>>>(sq, d_src, d_dst); Berényi D. - Nagy-Egri M. F. 22

CUDA map A gazdaoldali kód törzse: int blocksize = 4; int gridsize = (int)ceil((float)n/blocksize); A teljes elemszámot felosztjuk 4-es blokkokra auto sq = [] device (auto const& x) return x*x; ; map<<<gridsize, blocksize>>>(sq, d_src, d_dst); Berényi D. - Nagy-Egri M. F. 23

CUDA map A gazdaoldali kód törzse: Itt adjuk át a méreteket az eszköznek: a blokkok számát és az egy blokkon belüli szálak számát int blocksize = 4; int gridsize = (int)ceil((float)n/blocksize); auto sq = [] device (auto const& x) return x*x; ; map<<<gridsize, blocksize>>>(sq, d_src, d_dst); Berényi D. - Nagy-Egri M. F. 24

CUDA map A gazdaoldali kód törzse: Az eszköz oldalon alkalmazandó lambda int blocksize = 4; int gridsize = (int)ceil((float)n/blocksize); auto sq = [] device (auto const& x) return x*x; ; map<<<gridsize, blocksize>>>(sq, d_src, d_dst); Berényi D. - Nagy-Egri M. F. 25

CUDA map A gazdaoldali kód törzse: cudamemcpy( h_dst.data(), d_dst, sz, cudamemcpydevicetohost ); cudafree(d_src); cudafree(d_dst); Berényi D. - Nagy-Egri M. F. 26

CUDA map A gazdaoldali kód törzse: cudamemcpy( h_dst.data(), d_dst, sz, cudamemcpydevicetohost ); cudafree(d_src); cudafree(d_dst); Az eszközoldali hívás után a memóriát vissza kell másolni a gazda oldalra Berényi D. - Nagy-Egri M. F. 27

CUDA map A gazdaoldali kód törzse: cudamemcpy( h_dst.data(), d_dst, sz, cudamemcpydevicetohost ); cudafree(d_src); cudafree(d_dst); Ha végeztünk az eszköz oldali memóriát fel kell szabadítani Berényi D. - Nagy-Egri M. F. 28

CUDA map Fordítás: Ha CMAKE-el szeretnénk fordítani, nézzük meg az itteni példát. Paranssorból kézzel: nvcc -std=c++14 --expt-extended-lambda map.cu Berényi D. - Nagy-Egri M. F. 29

CUDA map Fordítás: Ha CMAKE-el szeretnénk fordítani, nézzük meg az itteni példát. Paranssorból kézzel: nvcc -std=c++14 --expt-extended-lambda map.cu A lambdák annotálásához szükséges kiterjesztés engedélyezése Berényi D. - Nagy-Egri M. F. 30

CUDA zip A teljes kód itt érhető el: https://github.com/wigner-gpu-lab/teaching/blob/master/gpgpu2/zip.cu Berényi D. - Nagy-Egri M. F. 31

CUDA zip A zip teljesen hasonló a map-hoz: template<typename F, typename T> global void zip(f f, T* src1, T* src2, T* dst) int id = blockidx.x * blockdim.x + threadidx.x; dst[id] = f(src1[id], src2[id]); Berényi D. - Nagy-Egri M. F. 32

És most ugyan ez SYCL-ben! Berényi D. - Nagy-Egri M. F. 33

SYCL map A teljes kód itt érhető el: https://github.com/wigner-gpu-lab/teaching/blob/master/gpgpu2/map_sycl.cpp Berényi D. - Nagy-Egri M. F. 34

SYCL map A sycl használatához be kell includolni a sycl.hpp-t: // SYCL include #include <CL/sycl.hpp> Berényi D. - Nagy-Egri M. F. 35

SYCL map A map függvény belsejét külön függvényben írtuk meg: template<typename F, typename RA, typename WA> void map(f f, RA src, WA dst, cl::sycl::id<1> id) dst[id] = f(src[id]); Berényi D. - Nagy-Egri M. F. 36

SYCL map A map függvény belsejét külön függvényben írtuk meg: template<typename F, typename RA, typename WA> void map(f f, RA src, WA dst, cl::sycl::id<1> id) dst[id] = f(src[id]); Nincs semmilyen extra annotáció, szemben a CUDA-val Berényi D. - Nagy-Egri M. F. 37

SYCL map A map függvény belsejét külön függvényben írtuk meg: template<typename F, typename RA, typename WA> void map(f f, RA src, WA dst, cl::sycl::id<1> id) dst[id] = f(src[id]); Az itteni megvalósításban a bemenő argumentumok accessorok, és a szálazonosító is argumentumként jön a függvénybe (nincsenek beépített változók, mint CUDA-ban) Berényi D. - Nagy-Egri M. F. 38

SYCL map A SYCL kód gazda oldali része: cl::sycl::queue queue cl::sycl::gpu_selector() ; cl::sycl::buffer<t, 1> b_src(h_src.data(), n); cl::sycl::buffer<t, 1> b_dst(h_dst.data(), n); cl::sycl::range<1> r(n); Berényi D. - Nagy-Egri M. F. 39

SYCL map A SYCL kód gazda oldali része: cl::sycl::queue queue cl::sycl::gpu_selector() ; cl::sycl::buffer<t, 1> b_src(h_src.data(), n); cl::sycl::buffer<t, 1> b_dst(h_dst.data(), n); cl::sycl::range<1> r(n); Parancslista létrehozása, ami most az első GPU eszközt próbálja meg kiválasztani Berényi D. - Nagy-Egri M. F. 40

SYCL map A SYCL kód gazda oldali része: cl::sycl::queue queue cl::sycl::gpu_selector() ; cl::sycl::buffer<t, 1> b_src(h_src.data(), n); cl::sycl::buffer<t, 1> b_dst(h_dst.data(), n); cl::sycl::range<1> r(n); Bufferek létrehozása az adatokhoz Berényi D. - Nagy-Egri M. F. 41

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Berényi D. - Nagy-Egri M. F. 42

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Semmilyen annotáció nem kell a lambdához sem Berényi D. - Nagy-Egri M. F. 43

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Hozzáférést kérünk a bufferekhez Berényi D. - Nagy-Egri M. F. 44

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Egy parallel_for hívást hajtunk végre a megadott kernel névvel Berényi D. - Nagy-Egri M. F. 45

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Átveszünk mindent érték szerint, ideértve az accessorokat is Berényi D. - Nagy-Egri M. F. 46

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Argumentumként kapjuk a szálazonosítót Berényi D. - Nagy-Egri M. F. 47

SYCL map A SYCL kód gazda oldali része: auto sq = [](auto const& x) return x*x; ; queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cgh.parallel_for<class Map>(r, [=](cl::sycl::id<1> i) map(sq, a_src, a_dst, i); ); ); Meghívjuk a korábban megírt függvényünket. Berényi D. - Nagy-Egri M. F. 48

SYCL fordítás A ComputeCpp-vel, CMAKE-el történő fordításhoz, nézzük meg ezt a példakódot: https://github.com/wigner-gpu-lab/teaching/tree/master/gpgpu1/sycl Amihez szükség van a FindComputeCpp CMAKE modulra, aminek a legfrissebb verziója innen tölthető le: https://github.com/codeplaysoftware/computecpp-sdk/tree/master/cmake/modules Berényi D. - Nagy-Egri M. F. 49

SYCL fordítás Ha nem akarunk CMAKE-el fordítani, akkor parancssorból kb. így fest a dolog: Először a compute++ fordító kiszedi a cpp fáljból a SYCL specifikus részeket és legenerál egy úgynevezett integration headert (esetünkben main.cpp.sycl): /opt/codeplay/computecpp/latest/bin/compute++ -std=c++14 -O2 -mllvm -inline-threshold=1000 -sycl -emit-llvm -intelspirmetadata -isystem /opt/codeplay/computecpp/latest/include/ -I/opt/Codeplay/ComputeCpp/latest/include/ -I/usr/include -o main.cpp.sycl -c main.cpp Ezután a szokásos módon lefordítjuk a forrásfáljt, CSAK hozzá kell adni a generált headert includeként expliciten!: g++ -isystem /opt/codeplay/computecpp/latest/include -O3 -DNDEBUG -include main.cpp.sycl -std=gnu++14 -o main.o -c main.cpp Végül linkelünk (a Wl, -rpath varázslásról bővebben itt): g++ -O3 -DNDEBUG main.o -o SYCL_app rdynamic /opt/codeplay/computecpp/latest/lib/libcomputecpp.so -lopencl -Wl,-rpath,/opt/Codeplay/ComputeCpp/latest/lib Berényi D. - Nagy-Egri M. F. 50

SYCL zip A teljes kód itt: https://github.com/wigner-gpu-lab/teaching/blob/master/gpgpu2/zip_sycl.cpp Berényi D. - Nagy-Egri M. F. 51

Redukciók GPU-n Berényi D. - Nagy-Egri M. F. 52

Redukciók GPU-n A korábban bevezetett primitívek között a foldl/r szerepelt mint redukciós primitív: foldl :: (a -> b -> a) -> a -> f b -> a Ezzel az a probléma, hogy nem párhuzamosítható, mert explicit végrehajtási sorrendet ír elő. Berényi D. - Nagy-Egri M. F. 53

Redukciók GPU-n A korábban bevezetett primitívek között a foldl/r szerepelt mint redukciós primitív: foldl :: (a -> b -> a) -> a -> f b -> a Ha megváltoztatjuk a szignatúrát, és feltételezzük, hogy a bináris művelet asszociatív és kommutatív, akkor lehetőségünk nyílik átrendezni és párhuzamosítani a műveletet: reduce :: (a -> a -> a) -> f a -> a Berényi D. - Nagy-Egri M. F. 54

Redukciók GPU-n A redukciót a következő logikával végezzük el: Az első globális memóriából történő olvasásnál két elemet olvasunk be, eltolva a blokk mérettel (koaleszkált memória hozzáférés) Berényi D. - Nagy-Egri M. F. 55

Redukciók GPU-n A redukciót a következő logikával végezzük el: Az első globális memóriából történő olvasásnál két elemet olvasunk be, eltolva a blokk mérettel (koaleszkált memória hozzáférés) ezeket azonnal redukáljuk és letároljuk a lokális (shared) memóriában. Berényi D. - Nagy-Egri M. F. 56

Redukciók GPU-n A redukciót a következő logikával végezzük el: Az első globális memóriából történő olvasásnál két elemet olvasunk be, eltolva a blokk mérettel (koaleszkált memória hozzáférés) ezeket azonnal redukáljuk és letároljuk a lokális (shared) memóriában. A további redukciót is ugyan itt végezzük, minden lépésben felezve az eltolást (a megosztott memória bank-conflictjainak elkerülésére) Berényi D. - Nagy-Egri M. F. 57

Redukciók GPU-n A redukciót a következő logikával végezzük el: Az első globális memóriából történő olvasásnál két elemet olvasunk be, eltolva a blokk mérettel (koaleszkált memória hozzáférés) ezeket azonnal redukáljuk és letároljuk a lokális (shared) memóriában. A további redukciót is ugyan itt végezzük, minden lépésben felezve az eltolást (a megosztott memória bank-conflictjainak elkerülésére) Végeredményben minden munkacsoport (blokk) egyetlen értéket fog előállítani. Ennek a redukálását vagy gazda oldalon végezzük, vagy egy külön kernel indításban. Berényi D. - Nagy-Egri M. F. 58

CUDA reduce A teljes kód itt: https://github.com/wigner-gpu-lab/teaching/blob/master/gpgpu2/reduce.cu Berényi D. - Nagy-Egri M. F. 59

CUDA reduce template<typename F, typename T> global void reduce(f f, T* src, T* dst) extern shared T tmp[]; auto t = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[t] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(t < s) tmp[t] = f(tmp[t], tmp[t + s]); syncthreads(); if(t == 0) dst[blockidx.x] = tmp[0]; Berényi D. - Nagy-Egri M. F. 60

CUDA reduce template<typename F, typename T> Lokális memória bevezetése, global void reduce(f f, T* src, T* dst) a mérete a hívó oldalról jön. extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Berényi D. - Nagy-Egri M. F. 61

CUDA reduce template<typename F, typename T> global void reduce(f f, T* src, T* dst) Lokális (blokkon belüli) szálindex extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Berényi D. - Nagy-Egri M. F. 62

CUDA reduce template<typename F, typename T> Globális szálazonosító, de global void reduce(f f, T* src, T* dst) kétszeresére felhízlalt blokkmérettel az eltolás miatt extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Berényi D. - Nagy-Egri M. F. 63

CUDA reduce template<typename F, typename T> global void reduce(f f, T* src, T* dst) extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Berényi D. - Nagy-Egri M. F. 64 Első redukció a globális olvasáskor, a két elem között egy blokkméret eltolás van

CUDA reduce template<typename F, typename T> global void reduce(f f, T* src, T* dst) extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Lokális memóriát írtunk, és most olvasni fogjuk, ezért szinkronizálni kell a szálakat! Berényi D. - Nagy-Egri M. F. 65

CUDA reduce template<typename F, typename T> global void reduce(f f, T* src, T* dst) extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Egyre csökkenő eltolással további redukció a lokális memóriában Minden lépés végén szinkronizáció! Berényi D. - Nagy-Egri M. F. 66

CUDA reduce template<typename F, typename T> global void reduce(f f, T* src, T* dst) extern shared T tmp[]; auto l = threadidx.x; auto i = blockidx.x * (2*blockDim.x) + threadidx.x; tmp[l] = f( src[i], src[i+blockdim.x] ); syncthreads(); for(auto s=blockdim.x/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); syncthreads(); if(l == 0) dst[blockidx.x] = tmp[0]; Berényi D. - Nagy-Egri M. F. 67 Végül egyetlen szál visszaírja a lokális memória elejéről a kimenő tömbbe a blokk eredményét

CUDA reduce Gazdaoldali felparaméterezés: int n = 256; int blocksize = 16; size_t gridsize = (size_t)ceil((float)n/2/blocksize); reduce<<<gridsize, blocksize, blocksize*sizeof(t)>>>(sum, d_src, d_dst); Berényi D. - Nagy-Egri M. F. 68

CUDA reduce Gazdaoldali felparaméterezés: Minden szál 2 értéket olvas a globális memóriából, ezért a teljes méretet itt felezni kell! int n = 256; int blocksize = 16; size_t gridsize = (size_t)ceil((float)n/2/blocksize); reduce<<<gridsize, blocksize, blocksize*sizeof(t)>>>(sum, d_src, d_dst); Berényi D. - Nagy-Egri M. F. 69

CUDA reduce Gazdaoldali felparaméterezés: int n = 256; int blocksize = 16; size_t gridsize = (size_t)ceil((float)n/2/blocksize); reduce<<<gridsize, blocksize, blocksize*sizeof(t)>>>(sum, d_src, d_dst); Itt kell megadni a lokális memória méretét byte-okban Berényi D. - Nagy-Egri M. F. 70

SYCL reduce Hogy fest ugyanez SYCL-ben? A teljes kód itt: https://github.com/wigner-gpu-lab/teaching/blob/master/gpgpu2/reduce_sycl.cpp Berényi D. - Nagy-Egri M. F. 71

SYCL reduce template<typename F, typename RA, typename RWA, typename WA> void reduce(f f, RA src, RWA tmp, WA dst, cl::sycl::nd_item<1> id) auto g = id.get_group().get(0); auto bs = id.get_local_range().get(0); auto l = id.get_local().get(0); auto i = g * bs * 2 + l; tmp[l] = f( src[i], src[i+bs] ); id.barrier(cl::sycl::access::fence_space::local_space); for(auto s=bs/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); id.barrier(cl::sycl::access::fence_space::local_space); if(l == 0) dst[g] = tmp[0]; Berényi D. - Nagy-Egri M. F. 72

SYCL reduce template<typename F, typename RA, typename RWA, typename WA> void reduce(f f, RA src, RWA tmp, WA dst, cl::sycl::nd_item<1> id) auto g = id.get_group().get(0); auto bs = id.get_local_range().get(0); auto l = id.get_local().get(0); auto i = g * bs * 2 + l; tmp[l] = f( src[i], src[i+bs] ); id.barrier(cl::sycl::access::fence_space::local_space); A lokális memóriát is accessorral érjük el, mint a buffereket! for(auto s=bs/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); id.barrier(cl::sycl::access::fence_space::local_space); if(l == 0) dst[g] = tmp[0]; Berényi D. - Nagy-Egri M. F. 73

SYCL reduce template<typename F, typename RA, typename RWA, typename WA> void reduce(f f, RA src, RWA tmp, WA dst, cl::sycl::nd_item<1> id) auto g = id.get_group().get(0); auto bs = id.get_local_range().get(0); auto l = id.get_local().get(0); auto i = g * bs * 2 + l; tmp[l] = f( src[i], src[i+bs] ); Mivel most lokális index is van, nd_item-et használunk id.barrier(cl::sycl::access::fence_space::local_space); for(auto s=bs/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); id.barrier(cl::sycl::access::fence_space::local_space); if(l == 0) dst[g] = tmp[0]; Berényi D. - Nagy-Egri M. F. 74

SYCL reduce template<typename F, typename RA, typename RWA, typename WA> void reduce(f f, RA src, RWA tmp, WA dst, cl::sycl::nd_item<1> id) auto g = id.get_group().get(0); auto bs = id.get_local_range().get(0); auto l = id.get_local().get(0); auto i = g * bs * 2 + l; tmp[l] = f( src[i], src[i+bs] ); id.barrier(cl::sycl::access::fence_space::local_space); Ez az indexelés pontosan a CUDA-s megvalósítást másolja minden méretet és indexet az nd_item-ből tudunk lekérni for(auto s=bs/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); id.barrier(cl::sycl::access::fence_space::local_space); if(l == 0) dst[g] = tmp[0]; Berényi D. - Nagy-Egri M. F. 75

SYCL reduce template<typename F, typename RA, typename RWA, typename WA> void reduce(f f, RA src, RWA tmp, WA dst, cl::sycl::nd_item<1> id) auto g = id.get_group().get(0); auto bs = id.get_local_range().get(0); auto l = id.get_local().get(0); auto i = g * bs * 2 + l; tmp[l] = f( src[i], src[i+bs] ); Szintén az nd_item-en keresztül történik a szinkronizáció is. id.barrier(cl::sycl::access::fence_space::local_space); for(auto s=bs/2; s > 0; s >>= 1) if(l < s) tmp[l] = f(tmp[l], tmp[l + s]); id.barrier(cl::sycl::access::fence_space::local_space); if(l == 0) dst[g] = tmp[0]; Berényi D. - Nagy-Egri M. F. 76

SYCL reduce Gazdaoldali kód: size_t n = 256, local_count = 16; cl::sycl::nd_range<1> r(n/2, local_count); queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cl::sycl::accessor<t, 1, cl::sycl::access::mode::read_write, cl::sycl::access::target::local> a_tmp(cl::sycl::range<1>(local_count), cgh); ); cgh.parallel_for<class Reduce>(r, [=](cl::sycl::nd_item<1> i) reduce(sum, a_src, a_tmp, a_dst, i); ); Berényi D. - Nagy-Egri M. F. 77

SYCL reduce Gazdaoldali kód: size_t n = 256, local_count = 16; cl::sycl::nd_range<1> r(n/2, local_count); queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cl::sycl::accessor<t, 1, cl::sycl::access::mode::read_write, cl::sycl::access::target::local> a_tmp(cl::sycl::range<1>(local_count), cgh); Itt hozzuk létre az nd_range-t, ami a globális méretet és a blokkméretet írja le. ); cgh.parallel_for<class Reduce>(r, [=](cl::sycl::nd_item<1> i) reduce(sum, a_src, a_tmp, a_dst, i); ); Berényi D. - Nagy-Egri M. F. 78

SYCL reduce Gazdaoldali kód: size_t n = 256, local_count = 16; cl::sycl::nd_range<1> r(n/2, local_count); queue.submit([&](cl::sycl::handler& cgh) auto a_src = b_src.get_access<cl::sycl::access::mode::read>(cgh); auto a_dst = b_dst.get_access<cl::sycl::access::mode::write>(cgh); cl::sycl::accessor<t, 1, cl::sycl::access::mode::read_write, cl::sycl::access::target::local> a_tmp(cl::sycl::range<1>(local_count), cgh); Ez az accessor a lokális memóriához ad hozzáférést. A típusa a használatát, a konstruktora a méretet adja meg. ); cgh.parallel_for<class Reduce>(r, [=](cl::sycl::nd_item<1> i) reduce(sum, a_src, a_tmp, a_dst, i); ); Berényi D. - Nagy-Egri M. F. 79

GPU Reduce A redukció még sokkal tovább optimalizálható a belső for ciklus kifejtésével. Érdemes elolvasni ezt a redukció optimalizálásának gondolatmenetéről. Berényi D. - Nagy-Egri M. F. 80

GPU Reduce A most ismertetett konstrukcióban kihasználtuk, hogy a bináris művelet kommutatív és asszociatív! Ha nem asszociatív, akkor csak foldolni tudunk. Ha asszociatív, de nem kommutatív, akkor párhuzamosíthatunk, de a memóriahozzáférés nem lesz ideális Berényi D. - Nagy-Egri M. F. 81