Vzporedna obdelava videoposnetkov
Veliko problemov se da enostavno razdeliti na neodvisne podprobleme, ki jih lahko obdelujemo hrati. Govorimo o nerodno vzporednih problemih (angl. embarrassingly parallel problems).
Eden takih problemov je obdelava videoposnetkov. Obdelavo videoposnetkov lahko pohitrimo tako da:
- videoposnetek najprej na enem jedru razdelimo na množico kosov,
- nato vsak kos ločeno obdelamo na svojem jedru in
- na koncu na enem jedru obdelane kose sestavimo nazaj v celoto.
Prednost takega pristopa je, da posamezne kose obelujemo vzporedno, vsakega na svojem jedru. Če smo v prvem koraku videoposnetek razdelili na N enakih kosov, je čas obdelave v drugem koraku približno enak N-temu delu časa, potrebnega za obdelavo celotnega videoposnetka na enem jedru. Tudi če v čas obdelave štejemo še koraka razkosanja in sestavljanja, ki jih pri obdelavi na enem jedru nimamo, lahko na koncu še vedno precej pridobimo.
Če še nismo, si na gručo prenesimo videoposnetek llama.mp4
1.
Korak 1: razkosanje
Videoposnetek llama.mp4
želimo razbiti na manjše kose, ki jih bomo nato obdelovali vzporedno. Najprej naložimo ustrezni modul, če ga še nismo.
$ module load FFmpeg
module load FFmpeg
Videoposnetek nato razdelimo na kose dolge 20 sekund z naslednjim ukazom:
$ srun --ntasks=1 ffmpeg \
-y -i llama.mp4 -codec copy -f segment -segment_time 20 \
-segment_list parts.txt part-%d.mp4
srun --ntasks=1 ffmpeg -y -i llama.mp4 -codec copy -f segment -segment_time 20 -segment_list parts.txt part-%d.mp4
kjer
-codec copy
pove, naj se avdio in video zapis nespremenjena kopirata v izhodne datoteke,-f segment
izbere opcijo segmentiranja, ki razkosa vhodno datoteko,-segment_time 20
poda želeno trajanje vsakega kosa v sekundah,-segment_list parts.txt
shrani seznam ustvarjenih kosov v datotekoparts.txt
,part-%d.mp4
poda ime izhodnih datotek, pri čemer je%d
nastavek, ki ga programffmpeg
med razbijanjem zamenja z zaporedno številko kosa.
Ko se proces konča, imamo v delovnem imeniku prvotni videoposnetek, seznam kosov parts.txt
in zaporedne kose part-0.mp4
do part-4.mp4
:
$ ls
llama.mp4 part-0.mp4 part-1.mp4 part-2.mp4 part-3.mp4 part-4.mp4 parts.txt
ls
Korak 2: obdelava
Vsak kos obdelamo na enak način. Če želimo na primer spremeniti ločljivost kosa part-0.mp4
na 640 × 360 in rezultat shraniti v datoteko out-part-0.mp4
, uporabimo ukaz:
$ srun --ntasks=1 ffmpeg \
-y -i part-0.mp4 -codec:a copy -filter:v scale=640:360 out-part-0.mp4
srun --ntasks=1 ffmpeg -y -i part-0.mp4 -codec:a copy -filter:v scale=640:360 out-part-0.mp4
Kot vedno bo srun
zahteval vire in pognal našo nalogo na dodeljenem računskem vozlišču. Na ta način bi lahko enega za drugim obdelali vse kose, a s precej ročnega dela. Zato si raje oglejmo preprosto skripto sbatch
, ki naredi to namesto nas.
1 2 3 4 5 6 7 8 9 10 11 |
|
Skripto shranimo v datoteko ffmpeg1.sh
in poženemo z ukazom:
$ sbatch ./ffmpeg1.sh
Submitted batch job 389552
sbatch ./ffmpeg1.sh
S tem zaenkrat še nismo pohitrili izvajanja, saj skripta ob vsakem klicu srun
počaka, da se ta konča, preden zažene naslednjega. Če želimo vse kose poslati v obdelavo hkrati, na koncu vsake vrstice srun
dodamo znak &
, s katerim zahtevamo, da se ukaz izvaja v ozadju. Zdaj skripta ne čaka, da se ukaz zaključi, temveč takoj nadaljuje z izvajanjem naslednjega ukaza. Na koncu skripte moramo zato dodati še ukaz wait
, ki počaka, da se zaključijo vse naloge, ki smo jih zagnali v ozadju. S tem poskrbimo, da se skripta ne zaključi, dokler niso vsi kosi videa obdelani do konca.
Vsak klic srun
predstavlja eno nalogo v našem poslu. Zato v glavi skripte zahtevamo, koliko nalog naj se izvaja hkrati. Nastavitev --ntasks=5
pomeni, da bo Slurm hkrati izvajal največ pet nalog, tudi če je nalog več. Pri tem pazimo, da vsakemu klicu srun
dodamo argumenta --ntasks=1
in --overlap
; brez tega bi Slurm nalogo za vsak kos ponovil petkrat, kar ni najbolj uporabno.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Nizi poslov
Zgornji način deluje, a je precej nepriročen. Če spremenimo število kosov, moramo dodati oziroma popravljati vrstice v skripti, pri čemer se lahko hitro zmotimo. Vidimo tudi, da se posamezni koraki med seboj razlikujejo le po številki v imenih datotek. K sreči ima Slurm rešitev za točno take situcije: nize poslov (angl. array jobs). Poglejmo, kako bi predelali zgornji primer:
1 2 3 4 5 6 7 8 9 |
|
Dodali smo stikalo --array=0-4
, ki pove Slurmu, naj ukaze v skripti požene za vsako izmed števil od 0 do 4. Slurm bo zagnal toliko nalog, koliko je števil v območju, podanim s stikalom --array
, v našem primeru 5. Če želimo število nalog, ki se izvajajo hkrati, omejiti, na primer na 3, napišemo --array=0-4%3
.
Vsak ukaz srun
se bo izvedel za eno nalogo, zato lahko --ntasks=1
izpustimo. V ukazu ne podamo dejanskega imena datoteke, temveč uporabimo nastavek $SLURM_ARRAY_TASK_ID
. Slurm bo za vsako nalogo nastavek zamenjal z enim od števil iz območja, podanega s stikalom --array
. V ime datoteke za beleženje smo dodali nastavek %a
, ki ga Slurm prav tako nadomesti s številom iz območja, podanega s stikalom --array
. Tako bo vsaka naloga zapisovala v svojo datoteko. Nastavek $SLURM_ARRAY_TASK_ID
je v bistvu okoljska spremenljivka, ki jo Slurm ustrezno nastavi za vsako nalogo. Ko Slurm izvaja ukaze #SBATCH
, ta spremenljivka še ne obstaja, zato moramo za stikala v Slurmu uporabljati nastavek %a
.
Po tem koraku dobimo v delovnem imeniku datoteke out-part-0.mp4
do out-part-4.mp4
z obdelanimi kosi prvotnega posnetka.
Korak 3: sestavljanje
Preostane nam le še, da datoteke out-part-0.mp4
, ..., out-part-4.mp4
združimo v en posnetek. Za to moramo ffmpeg
podati seznam kosov, ki jih želimo združiti. Navedmo jih v datoteki out-parts.txt
z naslednjo vsebino:
1 2 3 4 5 |
|
Izdelamo jo lahko iz obstoječega seznama kosov prvotnega posnetka parts.txt
. Datoteko najprej preimenujemo v out-parts.txt
. Datoteko out-parts.txt
odpremo v urejevalniku besedila in poiščemo ter zamenjamo vse nize part
z nizom file out-part
.
Bolj elegantno lahko ustvarimo seznam posameznih kosov videa s pomočjo ukazne vrstice in programa sed
(angl. stream editor):
$ sed 's/part/file out-part/g' < parts.txt > out-parts.txt
sed 's/part/file out-part/g' < parts.txt > out-parts.txt
Na koncu iz seznama kosov v datoteki out-parts.txt
sestavimo izhodni posnetek out-llama.mp4
.
$ srun --ntasks=1 ffmpeg -y -f concat -i out-parts.txt -c copy out-llama.mp4
srun --ntasks=1 ffmpeg -y -f concat -i out-parts.txt -c copy out-llama.mp4
Z orodjem za prenos podatkov lahko odstranimo začasne datoteke.
Vaja
Na povezavi najdete naloge za utrditev znanja o postopku vzporedne obdelave videoposnetkov na gruči.
Koraki 1, 2, 3 na en mah
V prejšnjih razdelkih smo pohitrili obdelavo videa tako, da smo nalogo razdelili na več kosov, ki smo jih izvajali kot več vzporednih nalog. Nad vsakim kosom smo zagnali ffmpeg
, vsaka naloga je uporabljala eno jedro in ni vedela ničesar o preostalih kosih. Tak pristop lahko uberemo vedno, kadar lahko problem razdelimo na neodvisne kose. Pri tem nam ni treba spreminjati programa za obdelavo.
Načeloma je vsak posel omejen na eno procesorsko jedro. Z uporabo programskih niti (angl. threads) pa lahko en posel uporabi več jeder. Program ffmpeg
zna uporabljati niti za mnoge operacije. Zgornje tri korake obelave lahko izvedemo tudi z enim samim ukazom. Zdaj zaženemo posel z eno samo nalogo za celo datoteko, saj bo program ffmpeg
glede na število jeder, ki mu jih dodelimo, sam razdelil obdelavo na več kosov.
Do sedaj smo vse korake naredili z uporabo modula FFmpeg. Tokrat uporabimo vsebnik ffmpeg-alpine.sif
.
Če si še nismo, si iz povezave prenesemo vsebnik za FFmpeg na gručo. Pri uporabi vsebnika pred klic programa ffmpeg
dodamo apptainer exec ffmpeg_alpine.sif
. V ukaz so zdaj vključeni trije programi:
srun
pošlje posel v Slurm in zažene programapptainer
,- program
apptainer
zažene vsebnikffmpeg-alpine.sif
, in - znotraj vsebnika
ffmpeg-alpine.sif
zažene programffmpeg
.
$ srun --ntasks=1 --cpus-per-task=5 apptainer exec ffmpeg_alpine.sif ffmpeg \
-y -i llama.mp4 -codec:a copy -filter:v scale=640:360 out123-llama.mp4
srun --ntasks=1 --cpus-per-task=5 apptainer exec ffmpeg_alpine.sif ffmpeg -y -i llama.mp4 -codec:a copy -filter:v scale=640:360 out123-llama.mp4
Postopek, ki smo ga prej v treh korakih naredili sami, zdaj program ffmpeg
naredi namesto nas približno enako hitro. S stikalom --cpus-per-task
smo zahtevali, da Slurm za vsako nalogo v našem poslu rezervira 5 procesorskih jeder.
Med delom ffmpeg
izpisuje status v zadnji vrstici:
frame= 2160 fps=304 q=-1.0 Lsize= 4286kB time=00:01:30.00 bitrate= 390.1kbits/s speed=12.7x
Podatek speed
nam pove, da kodiranje poteka 12,7-krat hitreje od predvajanja v realnem času. Povedano drugače, če videoposnetek traja 90 sekund, bomo za kodiranje porabili 90/12,7 ≈ 7,1 sekunde.
Namig
Če bi pred uporabo programa ffmpeg
dobro prebrali dokumentacijo, bi si precej olajšali delo. Ampak potem ne bi spoznali mnogih zelo uporabnih načinov rabe računalniške gruče.