Skoči na vsebino

Anatomija grafičnih procesnih enot1

Da bomo lažje razumeli delovanje in zgradbo sodobnih grafičnih enot, si bomo v nadaljevanju ogledali poenostavljen opis ideje, ki je privedla do njihovega nastanka. GPE so nastale v želji, da bi programsko kodo, ki ima veliko število relativno enostavnih in ponavljajočih se opercij, izvajali na velikem številu manjših procesnih enot in ne na veliki, kompleksni ter energijsko požrešni centralni procesni enoti. V množico problemov, ki so jim sodobne GPE namenjene, sodijo: procesiranje slik in videoposnetkov, operacije nad velikimi vektorji in matrikami, globoko učenje in podobno.

Sodobne CPE so zelo kompleksna digitalna vezja, ki špekulativno izvajajo ukaze v večih cevovodih, ukazi pa podpirajo veliko število raznovrstnih operacij (manj ali bolj kompleksnih). CPE imajo v cevovodih vgrajene enote za napovedovanje vejitev ter ukazne vrste (ang. trace cache), v katerih shranjujejo mikrokode ukazov, ki čakajo na izvajanje. Sodobne CPE imajo več jeder in vsakemu jedru pripada vsaj prvonivojski predpomnilnik L1. Jedra sodobnih CPE skupaj s predpomniniki zaradi prej naštetega zasedajo veliko prostora na čipu ter porabijo veliko energije. Pri vzporednem računalništvu težimo k temu, da imamo čim večje število preprostejših procesnih enot, ki so majhne in energijsko učinkovite. Te manjše procesne enote običajno delajo s taktom, ki je nekajkrat nižji od takta ure CPE. Zato manjše procesne enote potrebujejo nižjo napajalno napetost ter tako bistveno zmanjšajo porabo energije.

Zmanjšanje moči

Zgornja slika prikazuje, kako je v paralelnem sistemu možno zmanjšati moč in porabo energije. Na levi strani slike je CPE, ki procesira s frekvenco f. Za delo s frekvenco f potrebuje energijo, ki jo dobi iz napajalne napetosti V. Notranja kapacitivnost (to je v bistvu nekakšna vzrtrajnost, ki se upira hitrim spremembam napetosti na digitalnih priključkih) take CPE je odvisna predvsem od njene velikosti na čipu in je označena s C. Moč, ki jo za svoje delovanje potrebuje CPE je proporcionalna frekvenci ure, kvadratu napajalne napetosti ter kapacitivnosti.

Na desni strani slike isti problem rešujemo z dvema procesnima enotama CPE', ki sta vezani vzporedno. Predpostavimo, da se naš problem da razbiti na dva popolnoma enaka podproblema, ki ju lahko rešujemo ločeno, vsakega na svoji CPE'. Predpostavimo tudi, da sta procesni enoti CPE' za pol manjši od CPE v smislu velikosti čipa in da delata s frekvenco f/2. Ker delata s polovično frekvenco, potrebujeta tudi manj energije. Izkaže se, da če v digitalnem sistemu razpolovimo frekvenco ure, za delovanje sistema potrebujemo le 60 % napajalne napetosti. Ker sta CPE' za polovico manjši, je tudi njuna kapacitivnost le C/2. Moč P', ki jo za svoje delovanje sedaj potrebuje tak vzporedni sistem je le 0,36 P.

Evolucija GPE

Predpostavimo, da želimo s spodnjo funckijo vectorAdd() v jeziku C sešteti dva vektorja vecA in vecB ter rezultat shraniti v vektor vecC. Vsi vektorji so dolžine 128.

1
2
3
4
5
6
7
void vectorAdd( float *vecA, float *vecB, float *vecC ) {
    int tid = 0; 
    while (tid < 128) {
        vecC[tid] = vecA[tid] + vecB[tid];
        tid += 1; 
    }
}

V zgornji kodi smo namenoma uporabili zanko while, v kateri seštevamo vse iztoležne elemente vektorjev. Indeks trenutnega elementa smo namenoma zapisali s tid, kar je okrajšava od thread index. Zakaj smo izbrali ravno takšno ime, izvemo v nadaljevanju.

Izvajanje na eni CPE

Predpostavimo, da želimo funkcijo vectorAdd() izvajati na eni preprosti CPE, ki je prikazana na spodnji sliki. CPE ima logiko za zajem in dekodiranje ukazov, aritmetićno-logično enoto ter množico registrov, v katerih so operandi za aritmetično-logično enoto.

Preprosta CPE

Poleg CPE je na zgornji sliki prikazana psevdo-zbirniška koda funkcije vectorAdd(). Ne bomo se spuščali v njene podrobnosti, poudarimo le, da se koda v zanki L1 ponovi 128 krat.

Izvajanje na dveh CPE

Sedaj predpostavimo, da želimo funkcijo vectorAdd() izvajati na dveh enakih CPE kot prej. Na spodnji sliki sta prikazani dve CPE ter psevdo-zbirniški kodi, ki se izvajata na vsaki od teh dveh CPE. Spet se ne bomo spuščali v podrobnosti zbirniške kode. Poudarimo le, da se koda v zanki L1 tokrat ponovi le 64 krat, saj vsaka CPE tokrat sešteje le polovico istoležnih elementov v vektorjih (leva CPE sešteva prvih 64 elementov, medtem ko desna CPE sešteva zadnjih 64 elementov). Čez palec lahko ocenimo, da smo izvajanje funkcije vectorAdd() tako dvakrat pohitrili. Pravimo tudi, da se sedaj na dveh CPE hkrati izvajata dve vzporedni niti.

Dve CPE

Računska enota in procesni elementi

Predpostavimo sedaj, da zgornjo CPE razširimo tako, da ima namesto ene same aritmetično-logične enote osem aritmetično logičnih enot. Poleg tega ji dodajmo osem registrskih nizov, zato da bo lahko vsaka aritmetično-logična enota imela svojo množico registrov, v katerih hrani svoje operande. Na ta način lahko aritmetično-logične enote računajo hkrati, neodvisno ena od druge! Torej, namesto da smo osemkrat replicirali celotno CPE, smo tokrat osemkrat replicirali le njene aritmetično-logične enote in registrski niz. Takšna CPE je prikazna na spodnji sliki.

Računska enota

Opazimo pa, da ima taka CPE še vedno le eno enoto za zajem in dekodiranjer ukazov - to pomeni, da lahko v enem taktu prevzame in dekodira le en ukaz! Zato bo taka CPE izstavila isti ukaz v izvajanje vsem aritmetično-logičnim enotam. Vendar so lahko tokrat operandi v ukazu vektorji dolžine 8. To pomeni, da bomo lahko sedaj v enem ciklu (hkrati) sešteli 8 istoležnih elementov v vektorju in bomo zanko ponovili le 16 krat. Takšni procesni enoti bomo rekli računska enota (ang. compute unit, CU). Računska enota lahko izvaja en sam ukaz nad večjim številom podatkov - pravzaprav v našem primeru bo računska enota s samo enim ukazom, ki ga prebere in dekodira, seštela osem istoležnih elementov vektorja. Takemu načinu izvajanja pravimo SIMD (ang. Single Instruction Multiple Data). Ker računska enota vsak ukaz izvaja v osmih aritmetično-logičnih enotah, izgleda, kot da se v aritmetično-logičnih enotah izvajajo različne niti (ang. threads) istega ukaznega niza nad različnimi podatki. Zato tako izvajanje označujemo tudi SIMT (ang. Single Instruction Multiple Threads). Aritmetično-logičnim enotam, ki izvajajo isti ukaz nad različnimi operandi pravimo procesni elementi (ang. processing elements, PE). Spodnja slika prikazuje SIMD (SIMT) izvajanje ukazov.

SIMD (SIMT)

Registri v procesnih elemenih so praviloma privatni za vsak procesni element posebej, kar pomeni, da drugi procesni elementi ne morejo dostopati do podatkov v registrih.Računske enote imajo zato poleg procesnih elementov še manjši skupni ali lokalni pomnilnik (ang. shared/local memory), preko katerega si niti lahko delijo podatke. Zanka L1 se tokrat ponovi le 16 krat!

Grafična procesna enota

Gremo še en korak naprej. Namesto ene računske enote v sistemu uporabimo kar 16 računskih enot, kot prikazuje spodnja slika.

Računska enota

Sedaj nam ni treba zanke ponoviti 16 krat temveč vsaki računski enoti dodelimo le eno iteracijo zanke. Zgornja slika prikazuje poenostavljeno zgradbo grafičnih procesnih enot.

Povzetek

Grafične procesne enote so sestavljene iz večjega števila medseboj neodvisnih računskih enot (ang. computing units). Računske enote so sestavljene iz velikega števila procesnih elementov (ang. processing elements). Malce poenostavljeno lahko vzamemo, da vse računske enote, v nadaljevanju jih bomo označevali s CU, izvajajo isti program (ščepec), procesni elementi (PE) znotraj ene CU pa istočasno iste ukaze. Pri tem lahko različni CU v nekem trenutku izvajajo različne dele ščepcev. Pravimo tudi, da računske enote izvajajo skupine niti, procesni elementi pa izvajajo posamezne niti.




  1. © Patricio Bulić, Univerza v Ljubljani, Fakulteta za računalništvo in informatiko. Gradivo je objavljeno pod licenco Creative Commons Priznanje avtorstva-Nekomercialno-Deljenje pod enakimi pogoji 4.0 Mednarodna