Vzporejanje nalog
V tem razdelku poženemo program count-words.py
na gruči – najprej kot en posel, ki ga nato s pomočjo lupine razbijemo na naloge, ki jih izvajamo vzporedno. Več informacij o Slurmu najdemo v navodilih in Osnovah superračunalništva, tu pa si za ogrevanje ogledamo nekaj primerov.
Izvajanje na gruči
Kot običajno lahko program za štetje besed na gruči poženemo neposredno s srun
. Če imamo na voljo rezervacijo, jo uporabimo s parametrom --reservation=<ID>
. Preštejmo besede v prvih sto recenzijah s seznama pos.list
:
$ head -100 pos.list > pos.100
$ srun python3 count-words.py $(cat pos.100) > pos100.count
srun: job 814213 queued and waiting for resources
srun: job 814213 has been allocated resources
head -100 pos.list > pos.100
srun python3 count-words.py $(cat pos.100) > pos100.count
Spomnimo se, da z >
preusmerimo izpis v datoteko, z $(…)
pa izpis ukaza v oklepajih uporabimo kot argumente drugemu ukazu. Izpišimo najpogostejših pet besed:
$ cat pos100.count | sort -nr -k 2 | head -5
the 1444
be 1016
a 943
and 749
of 727
cat pos100.count | sort -nr -k 2 | head -5
Rezultat ni presenetljiv. Pri analizi naravnega jezika pogosto izpustimo t.i. stop words, kot so a, the in be. Podrobnejšo analizo recenzij prepuščamo bralcu za vajo, tu pa si oglejmo še skripto za sbatch
, ki prešteje besede v vseh datotekah na danem seznamu:
1 2 3 4 5 |
|
Izvorna datoteka: count-simple.sh
Poleg vrstice #!
smo skripto opremili še z argumenti za Slurm. Lupina bo te vrstice med izvajanjem skripte ignorirala, saj se začnejo z #
, ki označuje komentar. Prva vrstica je tako v resnici namenjena operacijskemu sistemu, drugi dve pa Slurmu.
V skripti spremenljivka $1
hrani vrednost prvega argumenta, s katerim bomo podali datoteko s seznamom recenzij, torej npr. pos.list
ali neg.list
. Seznam izpišemo s cat
in izpis uporabimo za argumente programu count-words.py
. Skripto pošljemo v izvajanje s programom sbatch
:
$ sbatch count-simple.sh pos.100
srun: job 916271 queued and waiting for resources
srun: job 916271 has been allocated resources
sbatch count-simple.sh pos.100
Stanje svojih poslov lahko preverimo s squeue --me --long
, podrobnejše poročilo o izvajanju posameznega posla pa si lahko ogledamo s pomočjo programa sacct
. Primer poročila za posel z identifikatorjem 916271:
$ sacct --format JobID,State,Start,Elapsed,AllocCPUS,TotalCPU,MaxRSS --job 916271
JobID State Start Elapsed AllocCPUS TotalCPU MaxRSS
------------ ---------- ------------------- ---------- ---------- ---------- ----------
916271 COMPLETED 2021-09-18T19:05:40 00:00:13 1 00:04.193
916271.bat+ COMPLETED 2021-09-18T19:05:40 00:00:13 1 00:04.191 1772K
916271.ext+ COMPLETED 2021-09-18T19:05:40 00:00:13 1 00:00.001 1392K
sacct --format JobID,State,Start,Elapsed,AllocCPUS,TotalCPU,MaxRSS --job 916271
Zastavica --format
nastavi stolpce, ki jih želimo prikazati; celoten seznam najdemo v priročniku. V zgornjem primeru smo izpisali identifikator posla JobID
, njegovo stanje State
, začetek Start
in trajanje Elapsed
, število uporabljenih jeder AllocCPUS
in količino porabljenega procesorskega časa TotalCPU
ter pomnilnika MaxRSS
. Vrstica bat+
oziroma batch
označuje del posla, ki je uporabljal vire v okviru Slurma (ta nas zanima), medtem ko vrstica ext+
oziroma external
označuje dele posla, ki so uporabljali vire izven nadzora Slurma (npr. seje ssh).
Vzporejanje
Štetje besed je primer nerodno paralelnega problema (angl. embarrassingly parallel), saj je število besed v eni datoteki popolnoma neodvisno od števila besed v ostalih. Zato lahko poljubne podmnožice datotek obdelamo vzporedno in nato seštejemo dobljene frekvence.
Najprej si oglejmo nekaj načinov, kako v poslu Slurm vzporedno zaženemo več nalog. Za demonstracijo uporabimo preprost program, ki počaka dano število sekund in konča:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
chmod +x task.sh
.
S spodnjo skripto za sbatch
zaženemo posel s petimi nalogami. Naloge poženemo eno za drugo, vsaka naloga pa traja eno sekundo dlje.
1 2 3 4 5 6 7 8 9 |
|
Ukazu srun
v skripti moramo dodati stikalo --exclusive
, da s tem prisilimo Slurm, da vsaki nalogi dodeli svoje procesorsko jedro. Okoljsko spremenljivko $SLURM_NTASKS
nastavi Slurm, ko zažene našo skripto. V zanki ustvarimo po eno nalogo za vsako od števil med 1 in 5 , ki jih izpiše ukaz seq $SLURM_NTASKS
.
Posel damo v vrsto s sbatch
in s sacct
preverjamo stanje:
$ sbatch job-seq.sh
Submitted batch job 1193660
$ sacct -j 1193660 --format=jobid,jobname,partition,ncpus,start,end,state
JobID JobName Partition NCPUS Start End State
------------ ---------- ---------- ---------- ------------------- ------------------- ----------
1193660 multitask gridlong 5 2021-09-18T11:49:20 2021-09-18T11:49:36 COMPLETED
1193660.bat+ batch 5 2021-09-18T11:49:20 2021-09-18T11:49:36 COMPLETED
1193660.ext+ extern 5 2021-09-18T11:49:20 2021-09-18T11:49:36 COMPLETED
1193660.0 sleep 1 2021-09-18T11:49:20 2021-09-18T11:49:22 COMPLETED
1193660.1 sleep 1 2021-09-18T11:49:22 2021-09-18T11:49:24 COMPLETED
1193660.2 sleep 1 2021-09-18T11:49:24 2021-09-18T11:49:27 COMPLETED
1193660.3 sleep 1 2021-09-18T11:49:27 2021-09-18T11:49:31 COMPLETED
1193660.4 sleep 1 2021-09-18T11:49:31 2021-09-18T11:49:36 COMPLETED
sbatch job-seq.sh
sacct -j 1193660 --format=jobid,jobname,partition,ncpus,start,end,state
Posamezna naloga v poslu ima identifikator $SLURM_JOB_ID.$SLURM_STEP_ID
. Vidimo, da se je vsaka naloga res začela šele po tem, ko se je prejšnja končala. V nadaljevanju si ogledamo tri načine, kako poslati v obdelavo več nalog hkrati.
Z lupino
Za vzporedno izvajanje nalog v skriptah sbatch
lahko uporabimo kar orodja, ki nam jih za ta namen ponuja lupina. Ukaz zaženemo v ozadju tako, da na konec dodamo &
; z wait
pa počakamo, da se končajo vsi procesi v ozadju.
Naslednja skripta pošlje v obdelavo pet nalog hkrati, pri čemer N‐ta naloga traja N sekund, in počaka, da se vse končajo.
1 2 3 4 5 6 7 8 9 10 |
|
job-for.sh
Skripto poženemo s sbatch
in s sacct
preverimo, da so naloge res bile zagnane hkrati.
$ sbatch job-for.sh
Submitted batch job 1193660
$ sacct -j 1193660 --format=jobid,jobname,partition,ncpus,start,end,state
JobID JobName Partition NCPUS Start End State
------------ ---------- ---------- ---------- ------------------- ------------------- ----------
1193660 multitask gridlong 5 2021-09-20T15:09:12 2021-09-20T15:09:18 COMPLETED
1193660.bat+ batch 5 2021-09-20T15:09:12 2021-09-20T15:09:18 COMPLETED
1193660.ext+ extern 5 2021-09-20T15:09:12 2021-09-20T15:09:18 COMPLETED
1193660.0 task.sh 1 2021-09-20T15:09:13 2021-09-20T15:09:18 COMPLETED
1193660.1 task.sh 1 2021-09-20T15:09:13 2021-09-20T15:09:15 COMPLETED
1193660.2 task.sh 1 2021-09-20T15:09:13 2021-09-20T15:09:14 COMPLETED
1193660.3 task.sh 1 2021-09-20T15:09:13 2021-09-20T15:09:17 COMPLETED
1193660.4 task.sh 1 2021-09-20T15:09:13 2021-09-20T15:09:16 COMPLETED
sbatch job-for.sh
sacct -j 1193660 --format=jobid,jobname,partition,ncpus,start,end,state
Z nizi poslov
Druga in pogosto enostavnejša možnost za vzporejanje so nizi poslov (angl. array jobs). Spodnja skripta zažene enake naloge kot zgornja, le da večino administracije prevzame Slurm, mi pa definiramo samo ukaz za posamezno nalogo.
1 2 3 4 5 6 |
|
job-array.sh
Argument --array=1-5
pove Slurmu, naj skripto zažene za vsako od števil med 1 in 5. Slurm za vsako nalogo nastavi okoljsko spremenljivko $SLURM_ARRAY_TASK_ID
na ustrezno število. Ta spremenljivka ima torej isto vrednost kot prej $task_id
, le da smo slednji morali vrednosti nastavljati sami s pomočjo zanke in ukaza seq
.
Slurm definira še vrsto drugih spremenljivk, npr. $SLURM_ARRAY_TASK_COUNT
s številom vseh nalog. Te spremenljivke lahko uporabljamo kjerkoli v skripti, razen v vrsticah #SBATCH
, ki jih tolmači Slurm in ne lupina. Zato moramo v argumentu --output
namesto $SLURM_ARRAY_JOB_ID
in $SLURM_ARRAY_TASK_ID
uporabiti simbola %A
in %a
z istima vrednostma. Seznam vseh okoljskih spremenljivk in simbolov, ki jih definira Slurm, najdemo v sbatch(1)
.
$ sbatch job-array.sh
Submitted batch job 1193724
$ sacct -j 1193724 --format=jobid,jobname,partition,ncpus,start,end,state
JobID JobName Partition NCPUS Start End State
------------ ---------- ---------- ---------- ------------------- ------------------- ----------
1193724_1 arraytask gridlong 1 2021-09-18T19:25:43 2021-09-18T19:25:44 COMPLETED
1193724_1.b+ batch 1 2021-09-18T19:25:43 2021-09-18T19:25:44 COMPLETED
1193724_1.e+ extern 1 2021-09-18T19:25:43 2021-09-18T19:25:44 COMPLETED
1193724_2 arraytask gridlong 1 2021-09-18T19:25:43 2021-09-18T19:25:45 COMPLETED
1193724_2.b+ batch 1 2021-09-18T19:25:43 2021-09-18T19:25:45 COMPLETED
1193724_2.e+ extern 1 2021-09-18T19:25:43 2021-09-18T19:25:45 COMPLETED
1193724_3 arraytask gridlong 1 2021-09-18T19:25:43 2021-09-18T19:25:46 COMPLETED
1193724_3.b+ batch 1 2021-09-18T19:25:43 2021-09-18T19:25:46 COMPLETED
1193724_3.e+ extern 1 2021-09-18T19:25:43 2021-09-18T19:25:46 COMPLETED
1193724_4 arraytask gridlong 1 2021-09-18T19:25:43 2021-09-18T19:25:47 COMPLETED
1193724_4.b+ batch 1 2021-09-18T19:25:43 2021-09-18T19:25:47 COMPLETED
1193724_4.e+ extern 1 2021-09-18T19:25:43 2021-09-18T19:25:47 COMPLETED
1193724_5 arraytask gridlong 1 2021-09-18T19:25:43 2021-09-18T19:25:48 COMPLETED
1193724_5.b+ batch 1 2021-09-18T19:25:43 2021-09-18T19:25:48 COMPLETED
1193724_5.e+ extern 1 2021-09-18T19:25:43 2021-09-18T19:25:48 COMPLETED
sbatch job-array.sh
sacct -j 1193724 --format=jobid,jobname,partition,ncpus,start,end,state
Vidimo, da je Slurm tokrat namesto enega posla s petimi nalogami ustvaril pet neodvisnih poslov. Za vsakega sacct
izpiše postavki batch
in external
.
Z GNU parallel
Za konec si oglejmo še program parallel
, ki vzporeja naloge na podoben način kot nizi poslov, a ni odvisen od Slurma. Recimo, da želimo s programom gzip
stisniti vsako datoteko v imeniku files
in pri tem zagnati do deset procesov hkrati:
$ parallel --jobs 10 "gzip {}" ::: files/*
parallel --jobs 10 "gzip {}" ::: files/*
V narekovajih smo podali ukaz, ki ga želimo vzporediti. Pri tem bo parallel
simbol {}
zamenjal z imenom posamezne datoteke. Sledi :::
, ki zaznamuje konec ukaza, in seznam datotek. Podobno kot pri nizih poslov lahko v ukazu uporabimo simbol {#}
oziroma spremenljivko $PARALLEL_SEQ
, da dobimo zaporedno številko trenutne naloge.
Poročilo o opravljenih nalogah lahko shranimo v datoteko z argumentom --joblog status.log
. Če dodamo še --resume
, bo parallel
ob ponovnem zagonu obdelavo nadaljeval tam, kjer je bila prekinjena (s Ctrl+C ali kako drugače). Ko želimo cel posel zagnati znova, moramo datoteko status.log
najprej izbrisati.
Splošna skripta sbatch
z uporabo programa parallel
izgleda tako:
1 2 3 4 5 6 7 |
|
job-parallel.sh
V tem primeru zahtevamo pet nalog, pri čemer dobi vsaka štiri procesorska jedra. Število nalog kot $SLURM_NTASKS
podamo ukazu parallel
, ki zažene posamezne naloge s pomočjo srun
. Ta poskrbi, da ima vsaka naloga na voljo $SLURM_CPUS_PER_TASK
jeder.
Optimizacija
Če posel razdelimo na n neodvisnih nalog, bi načeloma lahko pričakovali n‐kratno pohitritev. V praksi to običajno ne drži, saj Slurm tudi za razporejanje nalog potrebuje nekaj časa. Če bi hkrati poslali v obdelavo več deset ali sto tisoč nalog, bi za razporejanje lahko porabili več časa kot za samo štetje. Zato se pri velikem naboru vhodnih datotek splača vzporejati malo pametneje.
Pri nizih poslov lahko argument --array=1-1000
zamenjamo z --array=1-1000%20
, da zaganjamo po največ dvajset nalog hkrati. S tem zmanjšamo obremenitev razporejevalnika, a podaljšamo čas izvajanja. Program parallel
to naredi samodejno glede na argument --jobs
. Tudi skripto iz prvega primera bi lahko razširili na podoben način, a si bomo v naslednji vaji raje ogledali, kako v eni nalogi obdelamo več datotek. Tako bomo lahko za naš posel uporabili poljubno število nalog.
Ogledali si bomo tudi, kako združimo rezultate nalog v končni rezultat in celoten postopek zapisali v skripto. Pri tem nam bo prišla prav možnost --dependency
za srun
/sbatch
. Z njo poskrbimo, da se posel zažene šele, ko je izpolnjen določen pogoj. Na primer:
srun --dependency after:42+10 …
: zaženi posel deset minut po začetku posla42
,srun --dependency afterok:42 …
: zaženi posel, ko se je posel42
zaključil uspešno,srun --dependency afternotok:42 …
: zaženi posel, ko se je posel42
zaključil neuspešno.
Preveri svoje znanje
Vaja
Vaja 03: skripto za štetje besed razdeli na več nalog s pomočjo lupine in Slurma. Zaženi posel in združi rezultate vseh nalog. Skripto predelaj tako, da bo vsaka naloga obdelala določeno število datotek.