Módosítások

Intel Xeon Phi

7 512 bájt hozzáadva, 2017. május 9., 09:15
automatikus kódátvitel
Az oldalon található információk segítséget nyújtanak a HPC felhasználóinknak, hogy alkalmazásaikat miként portolhatják valamint optimalizálhatják a hatékonyabb futást szem előtt tartva Intel Xeon Phi koprocesszor kártyákon.
 
{{INFO|Egy 10 alkalmas, angol nyelvű Xeon Phi programozás tutoriál videóanyaggal és példaprogrammal elérhető a [[http://colfaxresearch.com/how-16-04/ Colfax oldalán]]}}
 
==Hardver bemutatása==
====közvetlen natív használat====
A natív mód arra szolgál, hogy a MIC kártyára lefordított alkalmazásunkat felmásoljuk a kártyára, majd belépünk SSH-n a kártyára és azon futtatjuk a bináris kódunkat. <br />
Ez Biztonsági okokból a legegyszerűbb használati mód, ugyanis nincs más dolgunk, csak a '''-mmic''' kapcsolót használni a fordításnál és a fordító egy máris a MIC architektúrára kész bináris készít nekünkközvetlen natív használat az NIIF HPC infrastruktúrán nem elérhető. <br/>Workflow a következő lenne:# Alkalmazás megírása. például [[Média:hello.cc|''hello.cc'']]# Alkalmazás fordítása <br/> <pre>icpc -o hello-MIC -mmic hello.cc</pre># Másolás a MIC kártyára <br/> <pre>scp hello-MIC mic0:</pre># belépés a kártyára <br/> <pre>ssh mic0</pre># Alkalmazás futtatása <br/> <pre>./hello-MIC</pre>
===b) támogatott, de nem preferált használat===
# Alkalmazás megírása. például [[Média:hello.cc|''hello.cc'']]
# Alkalmazás fordítása <br/> <pre>icpc -o hello-MIC -mmic hello.cc</pre>
# A tool futásához szükséges library-k megadása <br> <pre>export SINK_LD_LIBRARY_PATH=/opt/intel/composerxe-<verzió>/compiler/lib/mic</pre>
# Alkalmazás futtatása <br/> <pre>micnativeloadex hello-MIC</pre>
:{| class="wikitable"
|-
| BUDAPEST2[cn10] phi (0)$ export SINK_LD_LIBRARY_PATH=/opt/intel/composerxe-2011.4.191/compiler/lib/mic <br/>BUDAPEST2[cn10] phi (0)$ micnativeloadex hello-MIC <br/>Hello world! I have 244 logical cores.<br/>BUDAPEST2[cn10] phi (0)$
|}
Intel MKL használat esetén a SINK_LD_LIBRARY_PATH természetesen kiegészítendő az MKL könyvtárak elérésével.
<pre>intelhome=/opt/intel/compilers_and_libraries_2016.1.150/linux
export SINK_LD_LIBRARY_PATH=$intelhome/compiler/lib/mic:$intelhome/mkl/lib/mic</pre>
===c) támogatott módok===
</pre>
Ekkor minden memória területet 2x kell másolni, egyszer fel a kártyára, majd másodszor vissza a hoszt gépre, pedig elegendő lenne csak az eredmény vektort visszamásolni. Ezen optimalizálással egy későbbi fejezetben foglalkozunk. <p />
Futás során kaphatunk részletes riportot is, hogy pontosan milyen adatmozgatások történnek az alkalmazás futása során. Ehhez nincs szükség másra csak az '''OFFLOAD_REPORT''' környezeti változó megfelelő beállítása. A beállított értékek Automatic Offload esetén 1, 2 vagy , míg Compiler Assisted Offload esetén 1, 2 és 3 lehetnek, attól függően mennyire részletes riportot szeretnénk kapni. <br />
beállítás:
<pre>
Abban az esetben, ha 1 számítási node nem elég, MPI segítségével össze tudunk kötni több nodet, heterogén rendszert alkotva, és azokon alkalmazható a fenti Offload + OpenMP modell. <br />
[[Fájl:Mpi+offload+openmp.png|MPI elosztás valamint Offload modell és OpenMP]] <br />
 
 
==Phi használat slurm-mel==
Slurm esetén a mic opciót kell megadni a gres-knél. Amennyiben egynél több kártyára van szükség, akkor azt a ''mic:2''-vel lehetséges megadni. Ekkor két kártyát foglal le az ütemező a futtatandó feladatnak. <br />
Egy lehetséges feladat, amit srun-nal lehet a slurmben beküldeni. debrecen3 esetén a ''prod-phi'' particiót kell használni
 
<pre>
$ cat slurm_job_openmp_phi
 
#!/bin/bash
#SBATCH -A <PROJECT NEVE>
#SBATCH --job-name=<JOB NEVE>
#SBATCH --gres mic:2
#SBATCH --time=1:00:00
#SBATCH --partition=prod
#SBATCH -o slurm-%A.out
export OMP_NUM_THREADS=2
./runme_openmp_2phi
</pre>
Ekkor a feladat beküldése a következő paranccsal lehetséges.
<pre>
srun ./slurm_job_openmp_phi
</pre>
==Alkalmazások optimalizálása a kártyára==
===Párhuzamosítás===
Több féle mód létezik a párhuzamosításra. Korábban említettük a SIMD technikát, ami adatpárhuzamosításra való 1 processzoron belül, de létezik még processzorok közti párhuzamosítás, amit az OpenMP segítségével tudunk hatékonyan megvalósítani. Amennyiben gépek közti párhuzamosításra, üzenek küldésre van szükség, akkor az MPI-t kell segítségül hívni. Általános megoldás nem létezik minden feladatra, mert MPI is alkalmas lehet gépen belüli párhuzamosításra, nem csak az OpenMP. Amit mérlegelni kell, hogy mi az amire az alkalmazásnak szükséges van illetve mekkora kommunikációs overhead keletkezik, ha lecseréljük az OpenMP-t MPI-ra. Általában az egymástól független számításokat OpenMP-vel, míg a kommunikációt igénylő feladatokra MPI-t szoktak használni.
 
====Szálak párhuzamosítása====
Az OpenMP-nek környezeti változók segítségével megadható a párhuzamosan futtatható szálak száma. Ezt a következő paranccsal tudjuk megtenni, ahol 5-re állítjuk.
<pre>
export OMP_NUM_THREADS=5
</pre>
 
Forrásban a párhuzamos szálakat a ''#pragma omp parallel'' segítségével adhatjuk meg.
<pre>
#pragma omp parallel for //külső for szálak párhuzamosítása
for (int i = 0; i < n; i++)
#pragma simd // vektorizáció a belső cikluson
for (int j = 0; j < m; j++)
My_Function(A[i][j]);
</pre>
 
A fordítási direktívák össze is vonhatóak, ilyenkor 1 ciklus is elég.
<pre>
#pragma omp parallel for simd
for (int i = 0; i < n; i++)
My_Function(A[i]);
</pre>
 
=====Változók láthatósága, megosztása=====
Változók hatókörét a programozási nyelven kívül pragma-k segítségével is beállíthatjuk, hogy megosztott legyen a változó vagy a szálra nézve saját példány. Amennyiben nem definiáljuk felül, akkor minden változó megosztott. A felül definiálást a private és a shared paraméterekkel lehet megadni az OpenMP-nek.
<pre>
int A, B, C;
 
#pragma omp parallel private(A) shared(B)
{...}
</pre>
Az itteni példában az B és C megosztott változó a szálak között, míg A-t minden szálnak lemásolja a futás kezdetén.
 
====Párhuzamos szálak ütemezése====
OpenMp a szálak ütemezésére három féle módod kínál fel.
Módok:
* '''static''' (alapértelmezett): egyenletesen arányban egymás után elosztja a processzeket az indított szálakon, ezért a futási idő tetszőlegesen elhúzódhat, ha egyes processzek lassabban futnak le.
* '''dynamic''': minden esetben az első szabad szálra kerül a következő processz ütemezése, folyamatos kihasználtságot elérve, de "költséges" a sok-sok ütemezés
* '''guided''': Az előző két mód ötvözése. Nagyobb procesz számot ütemez az éppen szabad szálakra, melyek számát folyamatosan csökkenti a futás előre haladtával, hogy körülbelül egyszerre fejeződjön be minden szál futása, ezzel csökkentve a dynamic esetén magad ütemezési időráfordítást.
 
Mindhárom esetben megadható az egyszerre ütemezett processzek száma, amit a ''chunk'' értéke határoz meg. Ha megvan adva a chunk értéke, akkor az minden esetben annak megfelelő processzámot ütemez a szálakra.
Használata:
<pre>
#pragma omp parallel [schedule (<mode>,<chunk>) {...}
</pre>
 
====Szálak szinkronizációja====
Szála szinkronizálását többféle módon lehet megvalósítani, de ezen megoldások sajno jelentősen lelassítják a program futását.
* mutexxel: nagyon lassú, állandóan várnak egymásra a párhuzamos szálak
<pre>
#pragma omp parallel for
for (int i = 0; i < n; i++) {
#pragma omp critical
{ // kritikus szekció, csak egy szál hajthatja végre
total = total + i;
}
}
</pre>
 
* atomi művelet definiálása: sajnos még ez is lassú, de itt már nem olyan "erős" a szinkronizáció, ezért rendkívül sok limitációja van az itt használható atomi műveleteknek.
<pre>
#pragma omp parallel for
for (int i = 0; i < n; i++) {
#pragma omp atomic
total += i;
}
</pre>
 
=====Párhuzamos redukció=====
Az itt bemutatott eljárás gyorsabb mint a fenti szinkronizációs megoldás, de sajnos nem alkalmazható vektorokra! A redukciót a ''reduction(operation: scalar)'' formában kell megadni.
<pre>
int sum = 0;
#pragma omp parallel for reduction(+: sum)
for (int i = 0; i < n; i++) {
sum = sum + i;
}
</pre>
 
 
Tulajdonképpen a fenti problémakörre a következő minta ad megoldást. Ekkor egy külön változóban akkumuláltatjuk a szál általi összeget és ezen értékeke összegyűjtését végezzük csak atomi műveletként.
<pre>
int sum = 0;
#pragma omp parallel
{
int sum_thr = 0;
#pragma omp for
for (int i=0; i<n; i++)
sum_thr += i;
 
#pragma omp atomic
sum += sum_thr;
}
</pre>
 
====Ciklusok párhuzamosítása====
Ciklus párhuzamosítására a következő mintát szokás használni.
<pre>
#pragma omp parallel
{
// Az itt definiált forrást minden szál esetén végre fog hajtani
...
#pragma omp for [schedule (<mode>,<chunk>)
for (int i = 0; i < n; i++) {
// Ez a kódrész lesz párhuzamosítva
...
}
// ... és ez a köd is minden szál esetén végrehajtódik
...
}
</pre>
 
====Ciklus kifejtés====
A technika már egy példa erejéig a vektorizációnál is látható, de ott nem került részletezésre. A megoldás lényege, hogy a kellően nagy ciklust szétdaraboljuk kisebb darabokra, amit már külön feldolgozhatóak, ezzel párhuzamosítva a folyamatokat. Az egy darab kiindulási ciklusból két darab egymásba ágyazott ciklust hozunk létre, ami ugyanazon a feladattéren dolgozik.
 
Eredeti állapot:
<pre>
for (int i = 0; i < n; i++) {...}
</pre>
 
Átírt változat:
<pre>
int STRIP=1024;
for (int ii = 0; ii < n; ii += STRIP)
for (int i = ii; i < ii+STRIP; i++) {...}
 
// ha még van maradék elem, akkor azokon is végrehajtjuk a számításokat
if (n%STRIP != 0) {
for (i=n-(n%STRIP)+1; i<n; i++) {...}
}
</pre>
 
A darabok mérete (STRIP értéke) az adott rendszer és feladat függvényében változhat, tehát ez egyfajta "tuning" paraméter.
 
====Automatikus ciklusösszevonás====
Az OpenMP támogatást nyújt több egymásba ágyazott ciklus "összevonására". Ehhez csak a '''collaps''' fordítási direktívát és az összevonandó ciklusok számát kell megadni. <br />
FONTOS: ekkor az automatikus vektorizáció kikapcsolásra kerül!
 
<pre>
#pragma omp parallel for collaps(2)
for (int i=0; i<n; i++)
for( int j=0; j<m; j++){...}
</pre>
 
A fneti példában a 2db for ciklis kerül párhuzamosításra úgy, hogy a fordítás során 1db ciklus generálódik belőle, ami a következő paraméterezéssel ekvivalens:
<pre>
#pragma omp parallel for
for (int c=0; c < n*m; c++) {
int i = c/n;
int j = c%n;
...
}
</pre>
[[Kategória: HPC]]
98
szerkesztés

Navigációs menü