Biuletyn nr 32

Biuletyn KDM
1 | 2 | 3 | 4 | 5
6 | 7 | 8 | 9 | 10
11 | 12 | 13 | 14
15 | 16 | 17 | 18
19 | 20 | 21 | 22
23 | 24 | 25 | 26
27 | 28 | 29 | 30
31 | 32
Lista biuletynów

Biuletyn nr 32 (27 października 2009)

Spis treści

Checkpointing zadań w OpenMPI

Autor: Marcin Stolarek, Maciej Szpindler

Checkpointing

W tym tekście chcemy zwrócić uwagę na nieco zapomniane pojęcie związane z odpornością systemów obliczeniowych na błędy i awarie (ang. fault tolerance). Jest to zagadnienie szczególnie istotne w przypadku aplikacji, które wymagają długich i kosztownych obliczeń. Chcemy uniknąć powtarzania obliczeń w przypadku blędu programu lub awarii systemu, co wymaga nie tylko cennego czasu, ale ostatecznie przekłada się na konkretne koszty. W tym celu, za czasów panowania Superkomputerów przez duże "S", stworzono systemy operacyjne, które potrafiły zapewnić odpowiednie mechanizmy.

Tak zwany checkpoint to miejsce w programie, w którym można zapisać stan programu, opcjonalnie zatrzymać wykonanie, a następnie je wznowić - tzw. restart. Oczywiście programista może tak napisać program, aby takie miejsca były jawnie zaznaczone i program sam zapewniał możliwość wznowienia. Większość programów nie ma takich możliwości. Dlatego systemy operacyjne dedykowane dla systemów obliczeniowych (np. system UNICOS znany z komputera Cray X1 tornado), mogły wykonać tzw. checkpoint dowolnego programu. Z poziomu systemu operacyjnego wykonywany jest tzw. snapshot, w tym kontekście zrzut stanu programu w danej chwili czasowej, który następnie umożliwia odtworzenie stanu programu. W przypadku programów równoległych technicznie zagadnienie to jest bardzo złożone i wymaga odpowiedniego wsparcia systemu operaycjnego.

Ze względu na dominację klastrów w zastosowaniach obliczeniowych, najczęściej używanym dziś systemem jest Linux. Linux natywnie nie wspiera (na dzień dzisiejszy) funkcjonalności typu checkpoint-restart, dlatego też zagadnienie to zostało zapomniane, mimo że wydaje się być bardzo praktyczne.

Na przykładzie biblioteki MPI, w implementacji OpenMPI (http://www.openmpi.org), chcemy opisać jak można powyższą funkcjonalność uzyskać na klastrze obliczeniowym. Wymaga to wkomponowania w system (jądro systemu) biblioteki implementującej funkcjonalność checkpoint-restart. Gdy mamy tak przygotowane jądro, OpenMPI potrafi współpracować z systemem w celu checkpointingu zadań równoległych (także wielo-węzłowych).

Przygotowanie do działania

OpenMPI od wersji 1.3.3 umożliwia zastosowanie funkcjonalności checkpoint/restart (CRS) przy wykorzystaniu biblioteki Berkeley Lab Checkpoint/Restart (BLCR) przeznaczonej do działania z programami nierównoległymi. W celu poprawnego uruchomienia OpenMPI z CRS, należy więc zacząć od poprawnego zainstalowania i uruchomienia BLCR. Ze względu na konieczność załadowania modułu do jądra zazwyczaj niezbędne w tym celu będą prawa administratora. Na etapie konfiguracji OpenMPI należy wskazać na opcje kompilacji dotyczące obsługi BLCR, oraz funkcjonalności CRS. Zazwyczaj dobrym wyborem okaże się kompilacja z dodatkowym wątkiem do obsługi "fault tolerance", w przeciwnym wypadku zrzut aplikacji będzie można wykonać jedynie gdy program znajdzie się wewnątrz funkcji z rodziny MPI.

Zgodnie z informacją zawartą w pliku README OpenMPI w wersji 1.3.3 wspiera komunikacje przez: tcp, mx, openib, sm, self.

Uruchomienie i checkpoint

Po poprawnym przygotowaniu środowiska, możemy przystąpić do pierwszych testów. Jeżeli OpenMPI skompilowaliśmy z obsługą dodatkowego wątku do obsługi "fault tolerance", program może być w zasadzie dowolny. W przypadku kompilacji bez dodatkowego wątku, warto zadbać o dużą liczbę wywołań, choćby MPI_Barrier() wewnątrz której możliwy będzie checkpoint programu.

Kompilację programu wykonujemy bez dodatkowych opcji, swobodnie możemy więc korzystać z wczesniej używanego oprogramowania. W trakcie uruchomienia należy jednak podać dodatkową opcję. Najprostrze uruchomienie ma postać:

$mpirun -am ft-enable-cr ./a.out

Zrzut programu umożliwiający nam późniejsze restartowanie programu wykonujemy poleceniem:

$ompi-checkpoint pid_od_mpirun

Snapshot Ref.: 0 ompi_global_snapshot_pid_od_mpirun.ckpt

W trakcie wykonywania checkpoint'u program zostanie zatrzymany, po jego zakończeniu będzie kontynuował działanie. W efekcie wywołania ompi-checkpoint otrzymamy nazwę katalogu w którym przechowywane są dane niezbędne do wznowienia działania programu (niezbędne są równiez binaria wykonywanego programu, jak i dynamicznie linkowane biblioteki). Tego typu uruchomienie może zostać zastosowane do regularnego przygotowywania checkpointów programów wymagających długiego uruchomienia. Jeśli działanie nie zakończy się poprawnie z przyczyn niezależnych, będziemy mogli je wznowić przy pomocy wykonanych checkpointów.

Zrzuty z procesów uruchomionych przez mpirun znajdują się w podkatalogach zwracanego snapshot'u. Podkatalogi dotyczące różnych zrzutów nazywane są kolejnymi liczbami naturalnymi.

W przypadku, gdy chcemy przerwać wykonywanie programu np. ze względu na zwalnianie zasobów dla innej aplikacji należy podać dodatkową opcję do wywołania:

$ompi-checkpoint --term pid_do_mpirun

Restartowanie

Do wznowienia działania programu posługujemy się poleceniem:

$ompi-restart global_shapshot_pid_od_mpirun.ckpt

Takie wywołanie spowoduje wznowienie pracy programu od ostaniego wykonanego zrzutu. Możemy jednak wznowić pracę od dowolnego przygotowanego wczesniej checkpointu przy pomocy:

$ompi-restart -s <nr_zrzutu> global_shapshot_pid_od_mpirun.ckpt

Numer zrzutu podawany jest zawsze przed nazwą snapshotu, po wywołaniu ompi-checkpoint, jest on kolejną liczbą naturalną (zaczynając od 0) zgodną z nazwą podkatalgu w global_shapshot_pid_od_mpirun.ckpt. Dzialanie programu może również zostać wznowione na wybranych węzłach (tzn. można wykonać migrację zadania na inne węzły) przy pomocy opcji --machinefile lub --hostfile.

Podsumowanie

Funkcjonalność checkpoint-restart w OpenMPI jest w tej chwili w fazie testowej i niestety nie każdy program będzie prawidłowo obsłużony. Jednak widać perspektywy na stabilnie działanie tego rozwiązania, co umożliwi w przyszłości checkpointing zadań na klastrach z Linuxem. OpenMPI nie jest jednyną implementacją biblioteki MPI współpracującą z biblioteką BLCR, podobne możliwości daje biblioteka MVAPICH (http://mvapich.cse.ohio-state.edu).

Analiza programów równoległych za pomocą Paraver

Autor: Maciej Cytowski

Od niedawna na klastrze Halo2 dostępne jest oprogramowanie Paraver wraz z towarzyszącą mu biblioteką MPItrace. Narzędzia te służą do przeprowadzania analizy programów równoległych. Dla klastra Halo2 oznacza to wsparcie dla modeli programowania MPI oraz OpenMP (czy też połączenia tych dwóch). Narzędzie Paraver dostępne jest również na systemie Blader, gdzie może być wykorzystane do analizy programów pisanych w oparciu o model CellSuperScalar (artykuł w kolejnym numerze Biuletynu).

Artykuł ten opisuje podstawowy sposób użycia narzędzi MPItrace i Paraver. Szersza ich funkcjonalność opisana została w cytowanych na końcu dokumentach.

Przykład wykorzystania

W katalogu /opt/paraver/examples/ na klastrze Halo2 umieszczony został przykładowy program MPI, tzw. ping-pong. Program ten jest standardowym testem wydajności biblioteki MPI i polega na wykonywaniu pomiarów czasowych przesyłu paczki danych pomiędzy dwoma procesami w dwie strony (od procesu 0 do procesu 1 oraz od procesu 1 do procesu 0). Pomiary te wykonywane są wielokrotnie przy zwiększającym się rozmiarze paczki danych.

Uruchomienie i kompilacja programu:

ssh halo2
mkdir paraver_test
cd paraver_test/
cp /opt/paraver/examples/ping_pong.c .
cp /opt/paraver/examples/ping_pong.pbs .
module load openmpi_gnu64
mpicc -o ping_pong.exe ping_pong.c
qsub ping_pong.pbs

Po zakończeniu zadania możemy obejrzeć jego wynik zaglądając do pliku ping_pong.oN, gdzie N to identyfikator zadania PBS. Oto tabela wynikowa:

      8 bajtow         2 usec (   7.418 MB/sek)
     16 bajtow         2 usec (  19.478 MB/sek)
     32 bajtow         2 usec (  36.244 MB/sek)
     64 bajtow         2 usec (  74.533 MB/sek)
    128 bajtow         2 usec ( 143.765 MB/sek)
    256 bajtow         2 usec ( 239.441 MB/sek)
    512 bajtow         3 usec ( 354.224 MB/sek)
   1024 bajtow         4 usec ( 475.156 MB/sek)
   2048 bajtow         7 usec ( 562.410 MB/sek)
   4096 bajtow        13 usec ( 615.973 MB/sek)
   8192 bajtow        18 usec ( 915.878 MB/sek)
  16384 bajtow        38 usec ( 866.183 MB/sek)
  32768 bajtow        69 usec ( 947.446 MB/sek)
  65536 bajtow       122 usec (1078.085 MB/sek)
 131072 bajtow       205 usec (1276.507 MB/sek)
 262144 bajtow       383 usec (1369.228 MB/sek)
 524288 bajtow       754 usec (1390.687 MB/sek)

Jak widzimy wydajność komunikacji, mierzona tutaj za pomocą ilości MB przesyłanych w przeciągu 1 sekundy, rośnie wraz ze wzrostem wielkości wiadomości. Spróbujemy teraz dokonać analizy programu za pomocą Paraver i MPITrace.

Dużą zaletą prezentowanych narzędzi jest to, że aby skorzystać z ich funkcjonalności nie musimy dokonywać rekompilacji kodu. Schemat działania można opisać w następujących krokach:

  • ustawienie środowiska w celu utworzenia tzw. śladu programu (z ang. trace), czyli zbioru informacji o użyciu określonych fragmentów aplikacji w przekroju czasowym
  • uruchomienie aplikacji, po jej zakończeniu w katalogu roboczym utworzone zostaną pliki ze śladem programu (w ilości takiej jak liczba procesów)
  • połączenie plików śladu programu w jeden plik programu Paraver, do późniejszej analizy
  • analiza za pomocą programu Paraver

Zacznijmy zatem od początku.

Ustawienie środowiska

Do ustawienia środowiska przed uruchomieniem diagnozowanego programu należy po pierwsze wczytać moduł programu Paraver:

module load paraver

a następnie ustawić dwie zmienne środowiskowe:

setenv LD_PRELOAD ${MPITRACE_HOME}/lib/libmpitrace.so
setenv MPITRACE_ON 1

Zmienna LD_PRELOAD i ustawiona na niej ścieżka do biblioteki MPITrace powodują, iż wszelkie odwołania do biblioteki MPI przechwytywane są przez specjalne wrapper-y, które raportują działanie funkcji MPI wypisując odpowiednie komunikaty do plików śladu programu, a następnie wywołują właściwe wersje funkcji MPI.


Należy pamiętać aby po zakończeniu działania aplikacji zlikwidować zdefiniowaną zmienną LD_PRELOAD za pomocą polecenia:

unsetenv LD_PRELOAD

Uruchomienie aplikacji

Poniżej prezentujemy zmodyfikowaną wersję skryptu PBS wstawiającego zadanie do kolejki. Program możemy uruchomić w standardowy sposób używając mpiexec, należy jednak pamiętać o ustawieniach środowiska związanych z narzędziem MPItrace. Nowe linie w skrypcie (w porównaniu z wersją oryginalną) oznaczone zostąły kolorem zielonym.

#!/bin/csh
#PBS -N ping_pong
#PBS -l nodes=1:ppn=2
#PBS -l walltime=0:02:00
module load openmpi_gnu64
module load paraver
setenv LD_PRELOAD ${MPITRACE_HOME}/lib/libmpitrace.so
setenv MPITRACE_ON 1
cd $PBS_O_WORKDIR
mpiexec ./ping_pong.exe

Należy tutaj pochwalić miłą cechę MPItrace, jaką jest brak konieczności rekompilacji kodu. Pamiętajmy jednak, że wersja MPItrace musi być zgodna z wersją używanej przez nas implementacji MPI. Zainstalowana na klastrze Halo2 wersja MPItrace zgodna jest z biblioteką OpenMPI w wersji 1.2. Zachowanie MPItrace przy użyciu innych wersji/implementacji MPI jest niezdefiniowane.

Łączenie trace-ów

Po zakończeniu zadania w katalogu roboczym powinny znajdować się pliki o nazwie TRACE*. Plików powinno być tyle ile uruchamianych procesów/wątków. W przypadku programu ping_pong są to dwa pliki. Dodatkowo MPItrace wypisuje informacje diagnostyczne na standardowym wyjściu:

mpitrace: Intermediate files will be stored in /home/users/$USER/paraver_test
mpitrace: Tracing buffer can hold 100000 events
mpitrace: Tracing enabled for process 18561.
mpitrace: Tracing enabled for process 18562.
mpitrace: Successfully initiated with 2 tasks
..
..
..
mpitrace: Intermediate raw trace file created : /home/users/$USER/paraver_test/TRACE.0000018561000000000000.mpit
mpitrace: Intermediate raw trace file created : /home/users/$USER/paraver_test/TRACE.0000018562000001000000.mpit
mpitrace: Application has ended. Tracing has been terminated.

Aby analizować powstałe dane diagnostyczne w programie Paraver, musimy połączyć wszystkie pliki TRACE* w jeden plik o nazwie trace.prv. Dokona tego skrypt mpitrace_merge.tcsh, który znajduje się w katalogu /opt/paraver/bin/ i do którego mamy dostęp jeśli ustawiliśmy środowisko dla Paraver (module load paraver). Aby dokonać połączenia należy w katalogu roboczym wywołać:

 mpitrace_merge.tcsh

Analiza Paraver

Aby wczytać plik trace.prv programem Paraver wywołajmy:

module load paraver
paraver trace.prv

Pierwszym widokiem programu Paraver będzie widok procesów, który w przypadku programu ping-pong jest bardzo mało czytelny (bardzo wiele komunikatów i brak obliczeń):


Paraver0.png


Istnieje możliwość zbliżenia interesującego nas fragmentu aplikacji (Prawy klawisz myszy -> ZoomXY). Widzimy wówczas już wyraźnie dwa procesy, które komunikują się ze sobą za pomocą wywołań MPI. Skrócona legenda kolorów i elementów:

  • kolor czerwony procesu - oczekiwanie na zakończenie komunikacji (proces oczekujący)
  • kolor różowy procesu - oczekiwanie na zakończenie komunikacji (proces wysyłający) - blocking send
  • kolor niebieski procesu - obliczenia lub inne operacje nie związane z komunikacją
  • żółta linia - schematycznie przedstawiona komunikacja MPI


Paraver1.png


Po wybraniu (kliknięciu) odpowiedniej komunikacji możemy otrzymać trochę informacji dotyczących jej rodzaju. Widać to na prezentowanym obrazie:


Paraver2.png


Paraver i MPItrace mają dużo więcej możliwości. My (dział KDM) sami nadal uczymy się tych narzędzi. Postaramy się raportować ciekawostki, które odkryjemy. Zachęcamy Państwa do analizowania własnych aplikacji równoległych za pomocą Paraver!

Linki