You are on page 1of 64

Arhitektura grafičkih procesora

izv. prof. dr. sc. Tomislav Hrkać


izv. prof. dr. sc. Zoran Kalafatić

Sveučilište u Zagrebu, Fakultet elektrotehnike i računarstva

tomislav.hrkac@fer.hr

7. lipnja 2018.

1/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

Jednostavni skalarni procesor


Komponente: dohvat i dekodiranje instrukcija (narančasto); ALU / izvršna jedinica (žuto);
kontekst / registri (plavo)
Prevedeni (strojni) program se izvodi slijedno, instrukciju po instrukciju

2/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

1. poboljšanje: superskalarno izvodenje (ILP)


Višestruke izvršne jedinice i jedinice za dohvat i dekodiranje instrukcija
Sklopovlje na procesoru dinamički otkriva neovisne instrukcije u programskom kodu koje se
mogu istovremeno izvoditi (u primjeru dolje desno takvih nema)

3/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

2. poboljšanje: vektorsko izvodenje (SIMD)


Višestruke izvršne jedinice (ALU)
Izvodi se ista instrukcija nad većim brojem podataka (jedna jedinica za dohvat i dekodiranje)
Drukčiji programski kod (vektorske instrukcije)

4/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

3. poboljšanje: sklopovska višedretvenost


Uvišestručenje skupa registara
Omogućava procesoru brzo prebacivanje medu nekoliko dretvi izvodenja
Motivacija: prikrivanje memorijskih zastoja

5/64
Hrkać Arhitktura grafičkih procesora
Prikrivanje memorijskih zastoja

Mjere brzine memorije


Memorijska latencija - vrijeme potrebno za odgovor memorije na procesorov memorijski
zahtjev (npr. load ili store) – red veličine 100 ciklusa takta
Memorijska propusnost (engl. bandwidth) - količina podataka koju memorija može dostaviti
procesoru u jedinici vremena – npr. 20 GB/s

Memorijski zastoj
Vrijeme pristupa memoriji – oko 100 ciklusa takta
Procesor za to vrijeme mora čekati i ne može nastaviti s izvodenjem daljnjih instrukcija koje
ovise o traženom podatku
Ublažavanje problema: korištenje priručnih memorija (cache)

6/64
Hrkać Arhitktura grafičkih procesora
Prikrivanje memorijskih zastoja

7/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

4. poboljšanje: više jezgara na istom čipu


Umjesto daljnjeg kompliciranja logike za ubrzanje jednog slijednog programa, iskoristiti višak
tranzistora za ostvarivanje dodatne jezgre (ili jezgara) na čipu
Jednostavnije jezgre: pojedinačna jezgra je sporija u izvodenju pojedinačnog slijednog
programa od procesora koji bi koristio napredne tehnike
Potencijal za ubrzanje dolazi od većeg broja jezgara

8/64
Hrkać Arhitktura grafičkih procesora
Primjeri višejezgrenih procesora

9/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

Moguće različite kombinacije navedenih tehnika


Primjer 1: Superskalarni dvojezgreni procesor

10/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

Moguće različite kombinacije navedenih tehnika


Primjer 2: 16-jezgreni SIMD procesor, uz širinu vektora od 8 elemenata -
128 operacija u paraleli

11/64
Hrkać Arhitktura grafičkih procesora
Suvremeni višejezgreni procesor

Moguće različite kombinacije navedenih tehnika


Primjer 3: Četverojezgreni višedretveni superskalarni SIMD

12/64
Hrkać Arhitktura grafičkih procesora
Tipičan procesor prijenosnog računala (pojednostavljeni prikaz)

13/64
Hrkać Arhitktura grafičkih procesora
GPU

NVIDIA GeForce GTX1080


20 jezgara, 4x SMT, 32-elementni SIMD, 2-instrukcijski ILP
(superskalarnost), 64-struka isprepletena sklopovska višedretvenost

14/64
Hrkać Arhitktura grafičkih procesora
Memorijski sustav CPU / GPU

GPU – manje priručne memorije; memorijske zastoje maskira većim


oslanjanjem na isprepletenu višedretvenost
15/64
Hrkać Arhitktura grafičkih procesora
Osnovna arhitektura GPU

Osnovna arhitektura GPU


Nema radikalno novih koncepata u odnosu na moderne tehnike u CPU
više jezgara - MIMD
SIMD izvodenje unutar pojedine jezgre
sklopovski podržana višedretvenost
Jedina bitna razlika – radikalno drukčije konstante
umjesto 4 jezgre (CPU) → 8–16 jezgara (GPU)
umjesto 4× ili 8× SIMD (CPU) → 32× SIMD (GPU)
umjesto 2 hiperdretve po jezgri → 64 dretve po jezgri (GPU)
Zašto isti koncepti s tako različitim konstantama?
16/64
Hrkać Arhitktura grafičkih procesora
Renderiranje 3D scena

Grafička procesna jedinica


Osnovna namjena – prikaz (renderiranje) 3D scena
Ulaz:
model: 3D geometrija površine objekta
materijali
osvjetljenje
položaj kamere
Izlaz – slika
Renderiranje – računanje kako svaki trokut ulaznog modela doprinosi
izgledu svakog piksela izlazne slike

17/64
Hrkać Arhitktura grafičkih procesora
Opis sustava

Kako opisati sustav?


Korak 1: stvari (ključni entiteti) kojima sustav manipulira – imenice
Korak 2: operacije koje sustav provodi nad entitetima – glagoli

Entiteti – grafički primitivi


vrhovi (engl. vertices)
primitivi:
trokuti
točke
linije
fragmenti
pikseli

18/64
Hrkać Arhitktura grafičkih procesora
Opis sustava

Kako opisati sustav?


Korak 1: stvari (ključni entiteti) kojima sustav manipulira – imenice
Korak 2: operacije koje sustav provodi nad entitetima – glagoli

Operacije
Svaki program strukturiran
na sljedeći način:
ulaz – lista vrhova
GPU računa gdje se taj vrh
projicira na ekran (vertex
processing)
grupira vrhove u primitive
(trokute)
za svaki trokut, računa
piksele koje prekriva
za svaki piksel prekriven
primitivom, računa boju tog
piksela

19/64
Hrkać Arhitktura grafičkih procesora
Kratki povijesni pregled programiranja grafičkih jedinica 1/4

Raniji sustavi
nema ISA arhitekture
dajemo GPU-u samo listu vrhova, dalje sve računa sam
problem: modeliranje različitih materijala

APIji za grafičko programiranje


APIji za grafičko programiranje (npr OpenGL) podržavaju parametrizirani
model osvjetljenja i materijala
Programer može postaviti parametre. Npr:
glLight(light id, parameter id, parameter value)
glMaterial(face, parameter id, parameter value)
problem: stalno se množe zahtjevi:
potrebno sve više parametara za opis materijala
model postaje neodrživ – potreba za pisanjem vlastitog koda

20/64
Hrkać Arhitktura grafičkih procesora
Kratki povijesni pregled programiranja grafičkih jedinica 2/4

Rezultat:
Učinimo neke od koraka grafičke protočne strukture programirljivima
koraci za koje su se stalno povećavali zahtjevi: vertex processing, primitive
processing, fragment processing
Programeri sada mogu sami specificirati minijaturne programe – ”shadere”
koji definiraju ”logiku” pojedinih dijelova protočne strukture

Primjer – Program u jeziku za sjenčanje HLSL


sampler mySamp;
Texture2D<float3> myTex;
float3 lightDir;

float4 diffuseShader(float3 norm, float2 uv)


{
float3 kd;
d = myTex.Sample(mySamp, uv);
kd *= clamp(dot(lightDir, norm), 0.0, 1.0);
return float4(kd, 1.0);
}

21/64
Hrkać Arhitktura grafičkih procesora
Kratki povijesni pregled programiranja grafičkih jedinica 3/4

Obzervacija oko 2001-2003:


GPU - vrlo brzi procesori;
identične operacija (shaderi) na
skupu podataka
Velika količina podatkovnog
paralelizma
Hack – računanje pozicija skupa
čestica u fizikalnoj simulaciji
GPU protočnom strukturom
veličina zaslona = veličina
izlaznog niza (M × N)
samo 2 trokuta (pokrivaju
čitav zaslon)
fragment shader: (r,g,b) –
nova pozicija čestice (x,y,z)

22/64
Hrkać Arhitktura grafičkih procesora
Kratki povijesni pregled programiranja grafičkih jedinica 4/4

”GPGPU” 2002-2003

GPGPU = ”general purpose”


computation on GPUs
Primjene
biokemijske simulacije
računanje s rijetkim matricama
ray tracing...

23/64
Hrkać Arhitktura grafičkih procesora
Jezik Brook (2004)

Jezik Brook (2004)


Istraživački projekt
Apstraktna GPU kao podatkovno paralelni procesor
kernel void scale(float amount, float a<>, out float b<>)
{
b= amount * a;
}

// napomena: izostavljena inicijalizacija


float scale_amount;
float input_stream<1000>;
float output_stream<1000>;

// mapiraj jezgru na tokove


scale(scale_amount, input_stream, output_stream);
Brook kompajler pretvara općeniti tokovni program u OpenGl naredbe
(npr. drawTriangles(), ...)

24/64
Hrkać Arhitktura grafičkih procesora
NVIDIA Tesla arhitektura (2007)

Serija GeForce 8xxx


Pruža alternativno, ne grafički-specifično programsko sučelje prema GPU

Višejezgrena CPU arhitektura:


CPU se OS-u prikazuje kao višeprocesorski sustav
ISA pruža instrukcije za upravljanje kontekstom (programsko brojilo,
mapiranje VM, ...) na razini jezgre
GPU arhitektura prije 2007:
GPU programskom sučelju (driveru) pruža sučelje: postavi veličinu ekrana,
postavi program shader u protočnoj strukturi, drawTriangles, ...
GPU arhitektura nakon 2007:
GPU pruža novo podatkovno-paralelno sučelje programu (driveru): postavi
program jezgre, pokreni program jezgre...

25/64
Hrkać Arhitktura grafičkih procesora
Programski jezik CUDA

Programski jezik CUDA


Uveden 2007. godine s NVIDIA Tesla arhitekturom
Jezik sličan C-u za pisanje programa koji se izvršavaju na GPU korištenjem
sklopovskog sučelja koje pruža arhitektura
Jezik relativno niske razine: CUDA apstraktne strukture bliske
mogućnostima karakterističnima za moderne GPU-ove
Opaska: otvorena ”verzija” CUDA-e – OpenCL
CUDA se izvršava samo na NVIDIA-inim GPU-ovima
OpenCL se izvršava na CPU-ovima i GPU-ovima mnogih proizvodača

Plan
CUDA programske apstrakcije
CUDA implementacija na modernim GPU
Detalji GPU arhitekture

CUDA Terminologija – specifičnosti


CUDA dretva – pthread dretva (POSIX)
konceptualno slična apstrakcija; bitno drukčija implementacija
26/64
Hrkać Arhitktura grafičkih procesora
CUDA hijerarhija dretvi – primjer zbrajanja matrica

const int Nx=12;


const int Ny=6;

// kernel definition
__global__ void matrixAdd(float A[Nx][Ny], float B[Nx][Ny],
float C[Nx][Ny])
{
int i = blockIdx.x * blockDim.x + ThreadIdx.x;
int j = blockIdx.y * blockDim.y + ThreadIdx.y;

C[j][i] = A[j][i] + B[j][i];


}

///////////////////////////////////////////////////////

dim3 threadsPerBlock(4,3,1);
dim3 numBlocks(Nx/threadsPerBlock.x, Ny/threadsPerBlock.y, 1);

// assume A, B, C are allocated Nx x Ny float arrays

// this call will cause execution of 72 threads


// 6 blocks of 12 threads each
matrixAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
27/64
Hrkać Arhitktura grafičkih procesora
CUDA hijerarhija dretvi

CUDA hijerarhija dretvi


CUDA programi sastoje se od hijerarhije konkurentnih dretvi
dretvama jednoznačno pridružen ID
ID-ovi mogu biti 1–, 2– i 3–dimenzionalni

Zbrajanje matrica: C=A+B


const int Nx=12;
const int Ny=6;

// kernel definition
__global__ void matrixAdd(float A[Nx][Ny], float B[Nx][Ny],
float C[Nx][Ny])
{
int i = blockIdx.x * blockDim.x + ThreadIdx.x;
int j = blockIdx.y * blockDim.y + ThreadIdx.y;

C[j][i] = A[j][i] + B[j][i];


}

///////////////////////////////////////////////////////

dim3 threadsPerBlock(4,3,1);
dim3 numBlocks(Nx/threadsPerBlock.x, Ny/threadsPerBlock.y, 1);

// assume A, B, C are allocated Nx x Ny float arrays

// this call will cause execution of 72 threads


// 6 blocks of 12 threads each
matrixAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
28/64
Hrkać Arhitktura grafičkih procesora
CUDA hijerarhija dretvi

SPMD izvodenje ”device” koda


Svaka dretva računa vlasitti grid thread ID iz svoje pozicije u svom bloku
(threadIdx) i pozicije bloka unutar grida (blockIdx)
”Device” kod: SPMD izvodenje
jezgra (kernel funkcija), označena s global
izvodi se na GPU uredaju
”Host” kod: serijsko izvodenje
izvodi se kao dio normalnog C/C++ programa na CPU
Zajedničko pokretanje velikog broja dretvi
preciznije: pokrenu mrežu (grid) blokova dretvi
povratak iz poziva nakon što sve pokrenute dretve završe

Jasna odvojenost kodova


Razdvajanje koda na ”host” i ”device” – statički, od strane programera
”Device” kod – izvodi se na vanjskom uredaju (GPU), SPMD izvodenje
”Host” kod – izvodi se na računalu domaćinu, serijsko izvodenje

29/64
Hrkać Arhitktura grafičkih procesora
Broj SPMD dretvi – eksplicitan u programu
broj poziva jezgre nije odreden veličinom skupa podataka
const int Nx=11;
const int Ny=5;

// kernel definition
__global__ void matrixAdd(float A[Nx][Ny], float B[Nx][Ny],
float C[Nx][Ny])
{
int i = blockIdx.x * blockDim.x + ThreadIdx.x;
int j = blockIdx.y * blockDim.y + ThreadIdx.y;

if (i < Nx && j < Ny) // guard against out of bounds array access
C[j][i] = A[j][i] + B[j][i];
}
///////////////////////////////////////////////////////

dim3 threadsPerBlock(4,3,1);
dim3 numBlocks(Nx/threadsPerBlock.x, Ny/threadsPerBlock.y, 1);

// assume A, B, C are allocated Nx x Ny float arrays

// this call will cause execution of 72 threads


// 6 blocks of 12 threads each
matrixAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
30/64
Hrkać Arhitktura grafičkih procesora
CUDA model izvršavanja i memorijski model

31/64
Hrkać Arhitktura grafičkih procesora
Memorijski model

CUDA memorijski model


Različiti mem. adr. prostori računala domaćina (host) i GPU-a (device)
→ Kopirati podatke izmedu dvaju adresnih prostora – poziv cudaMemcpy

float *A = new float[N] // allocate buffer in host mem

// populate host address space pointer A


for (int i=0; i<N; i++)
A[i] = (float)i;

int bytes = sizeof(float) * N;


float* deviceA; // allocate buffer in
cudaMalloc(&deviceA, bytes); // device address space

// populate deviceA
cudaMemcpy(deviceA, A, bytes, cudaMemcpyHostToDevice);

// note: deviceA[i] is an invalid operation here (cannot


// manipulate contents of deviceA directly from host.
// Only from device code.)
32/64
Hrkać Arhitktura grafičkih procesora
CUDA memorijski model GPU

Memorijska hijerarhija
Ne samo da postoje odvojene
memorije računala domaćina i
vanjskog uredaja, nego na vanjskom
uredaju postoji memorijska hijerarhija
Tri ”općenamjenska” tipa
memorije vidljiva GPU kodu
privatna memorija dostupna
samo tekućoj dretvi
dijeljena memorija dostupna
svim dretvama u bloku
globalna memorija dostupna
svim dretvama
(+2 dodatna, specifična za
grafiku – ”constant memory” i
”texture memory” – ne
razmatramo)

33/64
Hrkać Arhitktura grafičkih procesora
CUDA sinkronizacijski konstrukti

CUDA sinkronizacijski konstrukti


Tri načina sinkronizacije
Poziv syncthreads()
barijera: čekaj da sve dretve u bloku dodu do ove točke
Atomičke operacije
npr. float atomicAdd(float* addr, float amount)
postoje i za varijable u globalnoj i dijeljenoj memoriji
Sinkronizacija izmedu računala domaćina i GPU (engl. host/device
synchronization)
implicitna barijera kroz sve dretve na povratku iz jezgre

34/64
Hrkać Arhitktura grafičkih procesora
Primjer: 1D konvolucija

35/64
Hrkać Arhitktura grafičkih procesora
Primjer: 1D konvolucija

#define THREADS_PER_BLOCK 128

__global__ void convolve(int N, float* input, float* output) {

__shared__ float support[threads_PER_BLOCK+2]; // shared accross block


int index = blockIdx.x * blockDim.x + threadIdx.x; // thread local variable

support[threadIdx.x] = input[index];
if (threadIdx.x <2) {
support[THREADS_PER_BLOCK + threadIdx.x] = input[index + THREADS_PER_BLOCK];
}

__syncthreads();

float result = 0.0f; // thread local variable


for (int i=0; i<3; i++)
result += support[threadIdx.x +i];

output[index] = result / 3.f;


}

// host code //////////////////////////////////////////////


int N = 1024 * 1024;
cudaMalloc(&devInput, sizeof(float) * (N + 2)); // allocate array in device memory
cudaMalloc(&devOutput, sizeof(float) * N); // allocate array in device memory

// property initialize contents of devInput here...

concolve<<<N/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(N, devInput, devOutput);

36/64
Hrkać Arhitktura grafičkih procesora
CUDA program za 1D konvoluciju
128 dretvi po bloku – tako smo naprosto odlučili
zadnji redak – pokretanje velikog broja dretvi – po jedne za svaki izlazni
element
koristimo dijeljenu memoriju bloka (polje support)
reduciramo broj pristupa globalnoj memoriji za faktor 3
(svaka dretva treba 3 podatka →) svakom podatku pristupa se 3 puta
dijeljena memorija bitno brža
kooperativno učitavanje odgovarajućeg dijela globalne memorije u dijeljenu
memoriju bloka
barijera ( syncthreads()): svi podatci moraju biti u dijeljenoj memoriji
prije početka računanja
svaka dretva računa jedan element rezultata
upis rezultata u globalnu memoriju (polje output)

37/64
Hrkać Arhitktura grafičkih procesora
Rezime – CUDA apstrakcije

Izvodenje: hijerarhija dretvi


Masovno pokretanje velikog broja dretvi
Dvorazinska hijerarhija: dretve grupirane u blokove
Sinkronizacija dretvi u bloku ”barijerama”
Dretve u bloku dijele vrlo brzu zajedničku memoriju
Raspodijeljen adresni prostor
Ugradeni memcpy primitivi za kopiranje podataka medu adresnim prostorima
računala domaćina i vanjskog uredaja
Tri tipa varijabli u adresnom prostoru: lokalna za dretvu, za blok
(”dijeljena”), za čitav program (”globalna”)
Barijera – sinkronizacijski primitiv za dretve u bloku
Atomički primitivi za dodatnu sinkronizaciju (dijeljene i globalne varijable)

38/64
Hrkać Arhitktura grafičkih procesora
CUDA semantika

Pthreads:
#define THREADS_PER_BLOCK 128

__global__ void convolve(int N, float* input, float* output) {


Poziv
pthread create:
__shared__ float support[threads_PER_BLOCK+2]; Alokacija stanja
int index = blockIdx.x * blockDim.x + threadIdx.x; dretve
support[threadIdx.x] = input[index]; - prostor na stogu za
if (threadIdx.x <2) { dretvu
support[THREADS_PER_BLOCK + threadIdx.x] = - upravljački blok da
input[index + THREADS_PER_BLOCK];
}
bi OS mogao
rasporedivati dretvu
__syncthreads();
Hoće li CUDA
float result = 0.0f; stvoriti 1 milijun
for (int i=0; i<3; i++) primjeraka lokalnih
result += support[threadIdx.x +i];
varijabli / stogova;
output[index] = result / 3.f; 8K primjeraka
} dijeljenih varijabli
(support)?
// host code //////////////////////////////////////////////
int N = 1024 * 1024; Ne; izvesti blokove
cudaMalloc(&devInput, sizeof(float) * (N + 2));
cudaMalloc(&devOutput, sizeof(float) * N);
na jezgri do kraja,
zatim ponovo
// property initialize contents of devInput here... iskoristiti iste
alokacije za sljedeće
concolve<<<N/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(N, devInput, devOutput);
blokove, itd. 39/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje poslova

Razlog za takvo rasporedivanje


Želimo da se CUDA program izvodi na raznim GPU-ovima bez modifikacija
Broj jezgara nepoznat unaprijed programu

40/64
Hrkać Arhitktura grafičkih procesora
Prevodenje CUDA programa

#define THREADS_PER_BLOCK 128

__global__ void convolve(int N, float* input, float* output) {


Prevedena verzija
__shared__ float support[threads_PER_BLOCK+2]; CUDA programa
int index = blockIdx.x * blockDim.x + threadIdx.x;
”Program text”
support[threadIdx.x] = input[index];
if (threadIdx.x <2) { (instrukcije)
support[THREADS_PER_BLOCK + threadIdx.x] = + dodatne informacije
input[index + THREADS_PER_BLOCK];
} o potrebnim resursima
__syncthreads(); 128 dretvi po
float result = 0.0f;
bloku
for (int i=0; i<3; i++)
result += support[threadIdx.x +i];
X bajtova lokalnih
podataka po dretvi
output[index] = result / 3.f;
} 130 podataka tipa
// host code ////////////////////////////////////////////// fload (520
int N = 1024 * 1024; bajtova) dijeljenog
cudaMalloc(&devInput, sizeof(float) * (N + 2));
cudaMalloc(&devOutput, sizeof(float) * N); memorijskog
prostora po bloku
// property initialize contents of devInput here...

concolve<<<N/THREADS_PER_BLOCK, THREADS_PER_BLOCK>>>(N, devInput, devOutput);


41/64
Hrkać Arhitktura grafičkih procesora
Cuda rasporedivanje blokova dretvi

Zahtjevi bloka za resursima sadržani u


prevedenoj binarnoj datoteci (128
dretvi, 520 B dijeljene mem., 128x B
lokalne mem)

Bitna pretpostavka
CUDA-e: izvodenje
blokova dretvi može biti
proizvoljnim redoslijednom
(nema meduovisnosti)
Implementacija dodjeljuje
blokove dretvi (”posao”)
jezgrama korištenjem
dinamičke politike
rasporedivanja koja poštuje
zahtjeve za resursima
42/64
Hrkać Arhitktura grafičkih procesora
Nvidia GeForce GTX 1080

43/64
Hrkać Arhitktura grafičkih procesora
NVIDIA GTX 680 (2012)

SMX jedinica (jedna ”jezgra”) arhitekture NVIDIA Kepler GK104

44/64
Hrkać Arhitktura grafičkih procesora
Nvidia GeForce GTX 285

45/64
Hrkać Arhitktura grafičkih procesora
NVIDIA GTX 680 (2012)

SMX jedinica (jedna ”jezgra”) arhitekture NVIDIA Kepler GK104

46/64
Hrkać Arhitktura grafičkih procesora
NVIDIA GTX 680 (2012)

Arhitektura Kepler GK104

47/64
Hrkać Arhitktura grafičkih procesora
Dodjela CUDA dretvi izvršnim resursima jezgre

#define THREADS_PER_BLOCK 128

__global__ void convolve(int N, float* input,


float* output) {

__shared__ float support[threads_PER_BLOCK+2];


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

support[threadIdx.x] = input[index];
if (threadIdx.x <2) {
support[THREADS_PER_BLOCK + threadIdx.x] =
input[index] + THREADS_PER_BLOCK];
}
__syncthreads();

float result = 0.0f


for (int i=0; i<3; i++)
result += support[threadIdx.x +i];
output[index] = result / 3.f;
}


CUDA blok dretvi dodijeljen jezgri
Kako izvršavamo logiku bloka?

48/64
Hrkać Arhitktura grafičkih procesora
Warp: grupa dretvi sa zajedničkim instrukcijskim tokom

#define THREADS_PER_BLOCK 128

__global__ void convolve(int N, float* input,


float* output) {

__shared__ float support[threads_PER_BLOCK+2];


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

support[threadIdx.x] = input[index];
if (threadIdx.x <2) {
support[THREADS_PER_BLOCK + threadIdx.x] =
input[index] + THREADS_PER_BLOCK];
}
__syncthreads();

float result = 0.0f


for (int i=0; i<3; i++)
result += support[threadIdx.x +i];
output[index] = result / 3.f;
}

CUDA jezgra izvodi SPMD program


Na NVIDIA GPU-ovima, grupe od 32 dretve dijele instrukcijski tok (SIMD izvodenje). Te
grupe zovu se ”warp”-ovi.
Blok dretvi convolve izvodi 4 warpa (4*32 dretve/warpu = 128 CUDA dretvi po bloku)
(Opaska: warpovi su važan implementacijski detajl, a ne CUDA apstrakcija)
Djelovanje SM (”jezgre”) u svakom taktu: (i) odaberi 4 izvodiva ”warpa” od 64 (TLP); (ii)
odaberi do dvije instrukcije po ”warp-u” (ILP)
49/64
Hrkać Arhitktura grafičkih procesora
CUDA semantika izvršavanja

Sustav blokove dretvi može rasporedivati proizvoljnim redoslijedom


Sustav pretpostavlja da nema meduovisnosti
Dretve unutar bloka izvode se konkurentno
Kad se blok počne izvršavati, sve dretve se izvode konkurentno (ova
semantika nameće sustavu ograničenje na rasporedivanje)
CUDA blok je sam po sebi SPMD program
Dretvve u bloku su konkurentne i kooperativne radne dretve
CUDA implementacija:
Svi warpovi u bloku dretvi rasporeduju se na istu jezgru – komunikacija
visoke propusnosti / niske latencije preko varijabli u dijeljenoj memoriji
Kad su sve dretve u bloku gotove, resursi blok poastaju dostupni sljedećem
bloku

50/64
Hrkać Arhitktura grafičkih procesora
Divergencija grananja

– smanjena učinkovitost

51/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa
Primjer: 1000 blokova; 128 CUDA dretvi po bloku
- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Jednostavna GPU sa samo 2 jezgre i podrškom za 12 warpova po jezgri
Korak 1: CPU šalje CUDA napravi (GPU) naredbu ”izvrši ovu jezgru”

52/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 2: Rasporedivač mapira blok 0 na jezgru 0 (rezervira izvršni kontekst za
128 dretvi i 520 B dijeljene memorije)

53/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 3: Rasporedivač nastavlja mapirati blokove na dostupne izvršne
kontekste (prikazano isprepleteno mapiranje)

54/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 3: Rasporedivač nastavlja mapirati blokove na dostupne izvršne
kontekste (prikazano isprepleteno mapiranje)

55/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa
Primjer: 1000 blokova; 128 CUDA dretvi po bloku
- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 3: Rasporedivač nastavlja mapirati blokove na dostupne izvršne
kontekste (prikazano isprepleteno mapiranje) Samo dva bloka mogu stati na
jezgru – treći blok ne stane zbog nedovoljnog prostora u dijeljenoj memoriji
(3 ∗ 520B > 1.5KB)

56/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 4: Blok 0 završava na jezgri 0

57/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 5: Blok 4 se rasporeduje na jezgru 0 (mapiran na izvršni kontekst 0-127)

58/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 4: Blok 2 završava na jezgri 0

59/64
Hrkać Arhitktura grafičkih procesora
Rasporedivanje CUDA programa

Primjer: 1000 blokova; 128 CUDA dretvi po bloku


- Svaka dretva zahtijeva 130*sizeof(float) = 520B dijeljene memorije
Korak 5: Blok 5 se rasporeduje na jezgru 0 (mapiran na izvršni kontekst
128-255)

60/64
Hrkać Arhitktura grafičkih procesora
Implikacije CUDA atomičkih operacija

Primjer: računanje histograma vrijednosti u polju


Primijetimo kako uporaba atomičkih operacija ne utječe na mogućnost
implementacije da rasporeduje blokove proizvoljnim redoslijedom (atomičke
operacije omogućavaju samo medusobno isključivanje i ništa više).

Primijetimo: ne tvrdi se da su CUDA blokovi dretvi neovisni. Tvrdi se


samo da mogu biti rasporedeni u proizvoljnom redoslijedu.
CUDA dozvoljava sinkronizaciju izmedu blokova, operacijama poput
atomic increment.
CUDA dretve mogu atomički ažurirati dijeljene varijable u globalnoj
memoriji
61/64
Hrkać Arhitktura grafičkih procesora
Implikacije CUDA atomičkih operacija

Ali što s ovim?


Razmotrimo jednojezgrenu GPU, resurse za jedan blok po jezgri
Koji su mogući ishodi različitih rasporedivanja

62/64
Hrkać Arhitktura grafičkih procesora
Tehnika ”perzistentnih dretvi”

#define THREADS_PER_BLK 128


#define BLOCKS_PER_CHIP 15*12 // specific to a certain GTX 480 GPU

__device__ int workCounter = 0; // global mem variable


__global__ void convolve(int N, float* input, float* output){
CUDA kod koji
__shared__ int startingIndex;
__shared__ float support[THREADS_PER_BLK+2]; pretpostavlja odreden broj
jezgara implementacije
while(1){ GPU
if (threadIdx.x == 0)
startingIndex = atomicInc(workCounter, THREADS_PER_BLK); Programer pokreće točno
__syncthreads(); onoliko blokova dretvi
if (startingIndex >= N) koliko je potrebno da se
break; popuni GPU
int index = startingIndex + threadIdx.x; // thread local (Iskorištavanje znanja o
support[threadIdx.x] = input[index]; implementaciji: ta GPU će
if(threadIdx.x < 2) zapravo izvršavati sve
support[THREADS_PER_BLK + threadIdx.x] = input[index + THREADS_PER_BLK]; blokove konkurentno)
__syncthreads();
Dodjela posla blokovima
float result = 0.0f; // thread-local variable implementirana od strane
for(int i=0; i<3; i++) aplikacije (Zaobilaženje
result += support[threadIdx.x + i];
GPU rasporedivača blokova
output[index] = result;
__syncthreads(); i ciljane semantike CUDA
} blokova dretvi)
}
Programerov mentalni
// host code ///////////////////////////////////////////////////////////// model je sada da se *sve*
int N = 1024 * 1024; dretve izvode konkurentno
cudaMalloc(&devInput, N+2); // allocate array in device memory na stroju
cudaMalloc(&devOutput, N); // allocate array in device memory
// properly initialize contents of devInput here...

convolve<<<BLOCKS_PER_CHIP, THREADS_PER_BLK>>>(N, devInput, devOutput);


63/64
Hrkać Arhitktura grafičkih procesora
CUDA rezime

Izvršna semantika:
Particioniranje problema u blokove dretvi u duhu podatkovno paralelnog
modela (ciljano da bude neovisno o sklopovlju: sustav rasporeduje blokove
na proizvoljan broj jezgara)
Dretve u bloku izvršavaju se konkurentno (moraju, jer su kooperativne)
Unutar bloka: SPMD programiranje s dijeljenim adresnim prostorom
Postoji suptilna ali uočljiva razlika izmedu ovih modela izvodenja. Bitno
razumjeti!
Semantika memorije:
Raspodijeljeni adresni prostor: memorije računala domaćina i vanjskog
uredaja
Unutar memorije vanjskog uredaja: lokalne varijable dretve, dijeljene
varijable bloka, globalne varijable
Premiještanje varijabli izmedu različitih memorija putem load/store funkcija
→ ispravno je lokalni/dijeljeni/globalni prostor smatrati različitim adresnim
prostorima
Ključni implementacijski detalji:
Dretve u bloku rasporeduju se na istu jezgru GPU kako bi se omogućila brza
komunikaciji preko dijeljene memorije
Dretve u bloku grupirane u warpove za SIMD izvodenje na sklopovlju GPU

64/64
Hrkać Arhitktura grafičkih procesora

You might also like