Skoči na vsebino

Vzporedna obdelava videoposnetkov

Veliko problemov se da še posebej enostavno razdeliti na neodvisne podprobleme, ki jih lahko obdelujemo hrati. Govorimo tudi o nerodno paralelnih 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.

Vzpotredna obdelava videa

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.

Najprej si na gručo prenesimo videoposnetek bbb.mp41.

Korak 1: razkosanje

Videoposnetek bbb.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 130 sekund z naslednjim ukazom:

$ srun --ntasks=1 ffmpeg \
-y -i bbb.mp4 -codec copy -f segment -segment_time 130 \
-segment_list parts.txt part-%d.mp4
srun --ntasks=1 ffmpeg -y -i bbb.mp4 -codec copy -f segment -segment_time 130 -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 130 poda želeno trajanje vsakega kosa v sekundah,
  • -segment_list parts.txt shrani seznam ustvarjenih kosov v datoteko parts.txt,
  • part-%d.mp4 poda ime izhodnih datotek, pri čemer je %d nastavek, ki ga program ffmpeg 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
bbb.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 zmanjšati dimenzije kosa part-0.mp4 za polovico in rezultat shraniti v datoteko out-part-0.mp4, uporabimo ukaz iz prejšnjega razdelka:

$ srun --ntasks=1 ffmpeg \
-y -i part-0.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-0.mp4
srun --ntasks=1 ffmpeg -y -i part-0.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-0.mp4

Kot zmeraj 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
#!/bin/sh
#SBATCH --job-name=ffmpeg1    # ime posla
#SBATCH --output=ffmpeg1.txt  # datoteka za beleženje izvajanja
#SBATCH --time=00:10:00       # časovna omejitev izvajanja posla ure:minute:sekunde
#SBATCH --reservation=fri     # rezervacija, če jo imamo; drugače vrstico zbrišemo

srun ffmpeg -y -i part-0.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-0.mp4
srun ffmpeg -y -i part-1.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-1.mp4
srun ffmpeg -y -i part-2.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-2.mp4
srun ffmpeg -y -i part-3.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-3.mp4
srun ffmpeg -y -i part-4.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-4.mp4
ffmpeg1.sh

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 --exclusive; 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
#!/bin/sh
#SBATCH --job-name=ffmpeg2
#SBATCH --output=ffmpeg2.txt
#SBATCH --time=00:10:00
#SBATCH --ntasks=5            # število nalog v poslu, ki se izvajajo hkrati

srun --ntasks=1 --exclusive ffmpeg -y -i part-0.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-0.mp4 &
srun --ntasks=1 --exclusive ffmpeg -y -i part-1.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-1.mp4 &
srun --ntasks=1 --exclusive ffmpeg -y -i part-2.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-2.mp4 &
srun --ntasks=1 --exclusive ffmpeg -y -i part-3.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-3.mp4 &
srun --ntasks=1 --exclusive ffmpeg -y -i part-4.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out-part-4.mp4 &

wait
ffmpeg2.sh

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
#!/bin/sh
#SBATCH --job-name=ffmpeg3
#SBATCH --time=00:10:00 
#SBATCH --output=ffmpeg3-%a.txt   # %a je nastavek za oznako naloge
#SBATCH --array=0-4               # območje spreminjanja vrednosti

srun ffmpeg \
-y -i part-$SLURM_ARRAY_TASK_ID.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 \
out-part-$SLURM_ARRAY_TASK_ID.mp4
ffmpeg3.sh

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, podanem 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 pravtako 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.mp4out-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
file out-part-0.mp4
file out-part-1.mp4
file out-part-2.mp4
file out-part-3.mp4
file out-part-4.mp4
out-parts.txt

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-bbb.mp4.

$ srun --ntasks=1 ffmpeg -y -f concat -i out-parts.txt -c copy out-bbb.mp4
srun --ntasks=1 ffmpeg -y -f concat -i out-parts.txt -c copy out-bbb.mp4

Na koncu lahko z orodjem za prenos podatkov odstranimo začasne datoteke.

Vaja

Na povezavi najdete naloge za utrditev znanja o postopku paralelizacije 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 zmeraj, 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 singularity exec ffmpeg_alpine.sif. V ukaz so zdaj vključeni trije programi:

  • srun pošlje posel v Slurm in zažene program singularity,
  • program singularity zažene vsebnik ffmpeg-alpine.sif, in
  • znotraj vsebnika ffmpeg-alpine.sif zažene program ffmpeg.
$ srun --ntasks=1 --cpus-per-task=5 singularity exec ffmpeg_alpine.sif ffmpeg \
-y -i bbb.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out123-bbb.mp4
srun --ntasks=1 --cpus-per-task=5 singularity exec ffmpeg_alpine.sif ffmpeg -y -i bbb.mp4 -codec:a copy -filter:v scale=w=iw/2:h=ih/2 out123-bbb.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=17717 fps=125 q=29.0 size=   48128kB time=00:09:50.48 bitrate= 665.7kbits/s speed=4.16x

Podatek speed nam pove, da kodiranje poteka 4,06-krat hitreje od predvajanja v realnem času. Povedano drugače, če videoposnetek traja 10 minut, bomo za kodiranje porabili 10,5/4,16≈2,5 minute.

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.


  1. Videoposnetek je nastal v okviru projekta Blender