Skoči na vsebino

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:

  1. videoposnetek najprej na enem jedru razdelimo na množico kosov,
  2. nato vsak kos ločeno obdelamo na svojem jedru in
  3. na koncu na enem jedru obdelane kose sestavimo nazaj v celoto.

Vzporedna 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.

Če še nismo, si na gručo prenesimo videoposnetek llama.mp41.

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 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
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
#!/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 to vrstico izbrišemo

srun ffmpeg -y -i part-0.mp4 -codec:a copy -filter:v scale=640:360 out-part-0.mp4
srun ffmpeg -y -i part-1.mp4 -codec:a copy -filter:v scale=640:360 out-part-1.mp4
srun ffmpeg -y -i part-2.mp4 -codec:a copy -filter:v scale=640:360 out-part-2.mp4
srun ffmpeg -y -i part-3.mp4 -codec:a copy -filter:v scale=640:360 out-part-3.mp4
srun ffmpeg -y -i part-4.mp4 -codec:a copy -filter:v scale=640:360 out-part-4.mp4

⇩ Prenesi 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 --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
#!/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 --overlap ffmpeg -y -i part-0.mp4 -codec:a copy -filter:v scale=640:360 out-part-0.mp4 &
srun --ntasks=1 --overlap ffmpeg -y -i part-1.mp4 -codec:a copy -filter:v scale=640:360 out-part-1.mp4 &
srun --ntasks=1 --overlap ffmpeg -y -i part-2.mp4 -codec:a copy -filter:v scale=640:360 out-part-2.mp4 &
srun --ntasks=1 --overlap ffmpeg -y -i part-3.mp4 -codec:a copy -filter:v scale=640:360 out-part-3.mp4 &
srun --ntasks=1 --overlap ffmpeg -y -i part-4.mp4 -codec:a copy -filter:v scale=640:360 out-part-4.mp4 &

wait

⇩ Prenesi 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=640:360 \
out-part-$SLURM_ARRAY_TASK_ID.mp4

⇩ Prenesi 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, 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
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

⇩ Prenesi 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-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 program apptainer,
  • program apptainer zažene vsebnik ffmpeg-alpine.sif, in
  • znotraj vsebnika ffmpeg-alpine.sif zažene program ffmpeg.
$ 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.


  1. Videoposnetek je nastal v okviru projekta Blender