Section author: Mia Doričić, Vedran Miletić

rocALUTION: ROCm SPARSE Linear Algebra PACkage

Kod: https://github.com/ROCmSoftwarePlatform/rocALUTION

Dokumentacija: https://rocalution.readthedocs.io/en/master/index.html

rocALUTION je biblioteka rijetke linearne algebre koja ima istu ulogu za rijetke matrice koju rocSOLVER ima za obične matrice.

Glavni načini vršenja računanja sa rocALUTION su (prema službenoj dokumentaciji):

  • Single-node Computation (jednočvorno računanje)

  • Multi-node Computation (višečvorno računanje)

  • Solvers

    • Fixed-Point iteracija – Jacobi, Gauss-Seidel, …

    • Krylove potprostorne metode – CR, CG, BiCGStab, GMRES, IDR, …

    • Mixed-precision sheme korekcije defekata

    • Chebysheve iteracije

    • MultiGrid sheme – geometrijske i algebarske

  • Preconditioners

    • Dijeljenje matrice – Jacobi, (Višeobojiv) Gauss-Seidel, …

    • Faktorizacija – ILU(0), ILU(p), Multi-Eliminacija ILU (rekurzivno), ILUT, …

    • Aproksimativna inverzija – Chevyshev matrični polinom, SPAI, FSAI, …

    • Blok-dijagonalni preconditioner za rješavanje problema Minimax točke

    • Blok tip podpreconditionera/solvera

    • Zbrajajuća Schwarz i restriktirana zbrajajuća Schwarz metoda

    • Preconditioners tipova varijabli

  • Backends

    • Host (Domaćin) – rezervni backend za CPU

    • GPU/HIP – ubrzani backend za AMD GPU-ove koji podržavaju HIP

    • OpenMP – dizajnirano za višejezgrene procesore

    • MPI – dizajnirano za višečvorne i multi-GPU konfiguracije

Primjeri

Primjer cg.cpp

Službeni primjer clients/samples/cg.cpp: https://github.com/ROCmSoftwarePlatform/rocALUTION/blob/develop/clients/samples/cg.cpp

U ovom primjeru promatramo jednu od Krylovih potprostornih metoda CR (Conjugate Residual Method). To je iterativna metoda za riješavanje rijetkih simetričnih polu-pozitivnih određenih linearnih sustava Ax=b. (Za više informacija o ovoj metodi pogledajte ovdje: https://rocalution.readthedocs.io/en/master/solvers.html#cr)

Kod je oblika:

int main(int argc, char* argv[])
{
 // Check command line parameters
 if(argc == 1)
 {
     std::cerr << argv[0] << " <matrix> [Num threads]" << std::endl;
     exit(1);
 }

 // Initialize rocALUTION
 init_rocalution();

 // Check command line parameters for number of OMP threads
 if(argc > 2)
 {
     set_omp_threads_rocalution(atoi(argv[2]));
 }

 // Print rocALUTION info
 info_rocalution();

 // rocALUTION objects
 LocalVector<double> x;
 LocalVector<double> rhs;
 LocalVector<double> e;
 LocalMatrix<double> mat;

 // Read matrix from MTX file
 mat.ReadFileMTX(std::string(argv[1]));

 // Move objects to accelerator
 mat.MoveToAccelerator();
 x.MoveToAccelerator();
 rhs.MoveToAccelerator();
 e.MoveToAccelerator();

 // Allocate vectors
 x.Allocate("x", mat.GetN());
 rhs.Allocate("rhs", mat.GetM());
 e.Allocate("e", mat.GetN());

 // Linear Solver
 CG<LocalMatrix<double>, LocalVector<double>, double> ls;

 // Preconditioner
 Jacobi<LocalMatrix<double>, LocalVector<double>, double> p;

 // Initialize rhs such that A 1 = rhs
 e.Ones();
 mat.Apply(e, &rhs);

 // Initial zero guess
 x.Zeros();

 // Set solver operator
 ls.SetOperator(mat);
 // Set solver preconditioner
 ls.SetPreconditioner(p);

 // Build solver
 ls.Build();

 // Verbosity output
 ls.Verbose(1);

 // Print matrix info
 mat.Info();

 // Start time measurement
 double tick, tack;
 tick = rocalution_time();

 // Solve A x = rhs
 ls.Solve(rhs, &x);

 // Stop time measurement
 tack = rocalution_time();
 std::cout << "Solver execution:" << (tack - tick) / 1e6 << " sec" << std::endl;

 // Clear solver
 ls.Clear();

 // Compute error L2 norm
 e.ScaleAdd(-1.0, x);
 double error = e.Norm();
 std::cout << "||e - x||_2 = " << error << std::endl;

 // Stop rocALUTION platform
 stop_rocalution();

 return 0;
}

Krenimo od funkcije main().

Prvo se pojavljuje uvjet if sa postavljenim ispitivanjem je li broj argumenata argc jednak 1. U slučaju da jest, provodi se ispis operatorom << na na standardni izlaz za greške std::cerr (za više infromacija proučite std::cerr na cppreference.com). Dakle, u slučaju bez pojavljivanja greške, vrši se output za argv[0], amtricu i broj niti:

int main(int argc, char* argv[])
{
 if(argc == 1)
 {
     std::cerr << argv[0] << " <matrix> [Num threads]" << std::endl;
     exit(1);
 }

Poziva se funkcija init_rocalution(). Ova funkcija definira backend deskriptor sa informacijama o hardveru i njegovim specifikacijama. (Za više detalja pogledajte ovdje: https://rocalution.readthedocs.io/en/master/basics.html?highlight=init#initialization-of-rocalution) Slijedi uvjetovanje if gdje u slučaju da je argc veći od 2, poziva se funkcija set_omp_threads_rocalution(), kojom se postavlja broj niti koji će rocALUTION koristiti. To će se također postići pomoću funkcije atoi(), s obzirom da je argv tipa char.

Iza toga stoji poziv funkciji info_rocalution() koja ispisuje informacije o rocALUTION platformi.

Za više informacija o funkciji za postavljanje broja niti pogledajte ovdje: https://rocalution.readthedocs.io/en/master/api.html?highlight=set_omp#_CPPv4N10rocalution26set_omp_threads_rocalutionEi Za više informacija o funkciji atoi() pogledajte ovdje: http://www.cplusplus.com/reference/cstdlib/atoi/

init_rocalution();

if(argc > 2)
{
    set_omp_threads_rocalution(atoi(argv[2]));
}

info_rocalution();

Kreće inicijalizacija objekata/vektora i matrice koji će se koristiti u nastavku. Iza inicijalizacije stoji funkcija za čitanje MTX (Matrix Market Format) datoteke, te se pomoću novog objekta za matricu otvara ta datoteka.

LocalVector<double> x;
LocalVector<double> rhs;
LocalVector<double> e;
LocalMatrix<double> mat;

mat.ReadFileMTX(std::string(argv[1]));

Sve objekte koje smo u prijašnjem koraku stvorili sada pomoću funkcije MoveToAccelerator() mičemo u akcelerator. Nakon toga slijedi alokacija novih imena i veličine za vektore. Funkcija GetN() vraća broj stupaca u matrici, a GetM() broj redaka u matrici.

mat.MoveToAccelerator();
x.MoveToAccelerator();
rhs.MoveToAccelerator();
e.MoveToAccelerator();

x.Allocate("x", mat.GetN());
rhs.Allocate("rhs", mat.GetM());
e.Allocate("e", mat.GetN());

U CG slover učitavamo matricu na kojoj će biti vršene operacije, vektor i njegov tip i tip nove varijable. Jednako tako radimo za Jacobi preconditioner. Jacobi riješava linearni sustav u kojem dominiraju dijagonale (Za više informacija o ovoj metodi pogledajte ovdje: https://rocalution.readthedocs.io/en/master/precond.html?highlight=jacobi#jacobi-method).

CG<LocalMatrix<double>, LocalVector<double>, double> ls;

Jacobi<LocalMatrix<double>, LocalVector<double>, double> p;

Na vektoru e putem funkcije Ones() postavljamo sve jedinice, te putem Apply() funkcije primjenjujemo te promjene na matricu. Na sličan način na vektor x postavljaju se nule funkcijom Zeros().

e.Ones();
mat.Apply(e, &rhs);

x.Zeros();

Pomoću varijable prije stvorene putem CG solvera, postavljamo operator i prekondicioner. Zatim se solver ‘build’-a putem Build() funkcije. U to ulaze alokacija podataka, struktura i numerička komputacija. Verbose() pruža opširniji ispis slovera. Ako je postavljen na 0, neće ispisati ništa. Ako je postavljen na 1, ispisati će informacije o solveru, početak i kraj. Ako je postavljen na 2, ispisati će informacije o iteratoru. Zatim, ispisuju se informacije o određenom objektu, u ovom slučaju matrici mat.

ls.SetOperator(mat);
ls.SetPreconditioner(p);

ls.Build();

ls.Verbose(1);

mat.Info();

Kreće mjerenje vremena izvođenja operacija. Iza toga se pokreće operacija riješavanja Solve() koju riješava solver. Nakon toga završava mjerenje vremena.

double tick, tack;
tick = rocalution_time();

ls.Solve(rhs, &x);

tack = rocalution_time();

Slijedi ispis vremena potrebnog kroz operaciju, te se oslobađa memorija zauzeta sa strane solvera.

std::cout << "Solver execution:" << (tack - tick) / 1e6 << " sec" << std::endl;

ls.Clear();

Ažurira se vektor e sa navedenim vrijednostima unutar funkcije ScaleAdd(). Također, računa se i norma vektora u obliku novog objekta error.

e.ScaleAdd(-1.0, x);
double error = e.Norm();
std::cout << "||e - x||_2 = " << error << std::endl;

Zaustavlja se rocALUTION, i program završava.

 stop_rocalution();

 return 0;
}

Kraj programa.

Primjer async.cpp

S ‘using namespace’ uvodi se ‘rocalution’. Kreće main(). Ponovno se definiraju ‘argc’ i ‘argv’. Kreće ‘if’ kojemu je uvjet ako je argc jednake vrijednosti kao 1, putem std::cerr (character error stream) funkcije se ispisuje vrijednost argv[0] i ‘<matrix> [Num threads]’ (ne znam točno što ovo ispisuje, program mi ne radi). ‘Izlaz’ je true.

Inicijalizira se rocAlution. ‘If’-om se kontrolira broj OMP threadova; ako je argc veći od 2, funkcijom ‘set_omp_threads_rocalution’ se postavlja broj threadova u ‘atoi(argv[2])’.

Onda se inicijalizira funkcija za ispis infa rocalution-a.

Kreiraju se objekti klase LocalVector. (LocalVector se zove local jer uvijek ostaje na jednom sistemu, a sistem može sadržavati više CPU-a putem UMA ili NUMA sistema memorije). Kreiraju se ‘x’ i ‘y’ tipa double, i ‘LocalMatrix double’ objekt ‘mat’. (Za LocalMatrix vrijedi sve isto kao i za LocalVector).

Još se stvaraju objekti tipa double nazvani ‘tick, tack, tickg, tackg’.

Sada apparently čita matricu iz MTX file-a koristeći objekt koji predstavlja matricu ‘mat’, i pohranjuje ju u ‘argv[1]’ u tipu ‘string’.

Koristeći objekte ‘x’ i ‘y’, alociraju se vektori, koristeći i objekt ‘mat’ za funkcije ‘GetN’ i ‘GetM’ (što su brojevi redova (M) i stupaca (N)).

‘X’-eve postavlja na 1, ‘y’-one na 0. Objekt ‘tickg’ će nositi rezultat funkcije ‘rocalution_time()’ koja vraća trenutačno vrijeme u mikrosekundama. (S obzirom na ovaj ‘g’, zaključila bih da se ovo odnosi na GPU, jer se sljedeći odnosi na CPU).

Ispisuje se ‘info’ o sva tri objekta (x, y i mat).

Ponovno se računa trenutačno vrijeme i sprema u objekt ‘tick’, ali ovaj put se to odnosi na CPU.

Ide ‘fr’ petlja koja kaže za svaki i manji od 100, pokreće se funkcija za matricu ‘mat’ imena ‘ApplyAdd’ koji objektu ‘y’ pridodaje umnožak objekata ‘mat’ i ‘x’.

Objekt ‘tack’ ponovno računa vrijeme, i ispod toga se ispisuje koliko je CPU-u trebalo vremena da executa, na način da se objekti tack i tick oduzmu i podijele sa brojem 1e6. Isto tako se ispisuje ‘Dot product’ odnosno Skalarni umnožak ‘x’ i ‘y’.

Objekt ‘tick’ opet mjeri vrijeme izvođenja.

Funkcijom ‘MoveToAccelerator()’ vrijednosti/memorija objekata ‘mat, x i y’ se pomiče na Accelerator Backend.

Ponovno, ispisuje se info o sva tri objekta.

‘Tack’ ponovno mjeri vrijeme, i ispisuje se vrijeme koje je bilo potrebno za Sync transfer prije, na isti način kao i za Execute CPU-a.

‘Y’-one se postavlja na 0, i ponovno ‘tick’ računa vrijeme, ali ovaj put za Accelerator.

Opet se ponavlja ista ‘for’ petlja kao i prije, i ‘tack’ računa vrijeme. Ispisuje se vrijeme koje je bilo potrebno Akceleratoru da executa na isti način kao i prijašnje računice, i ponovno se računa i Skalarni produkt ‘x’ i ‘y’.

Iza toga se računa vrijeme za ‘tackg’ koje se koristi za ispis vremena cijelog executa + transfera bez funkcije ‘Async’ tako da se ‘tackg’ i ‘tickg’ oduzmu i opet podijele sa 1e6.

Za sva tri objekta ‘mat, x i y’ se pomiče data na host-a. ‘Y’-oni se opet ponstavljaju na 0.

Kroz komentar kaže da se sada ovo odnosi na Async, i vidi se razlika jer se sada u isto vrijeme računaju vremena za i ‘tickg’ i ‘tick’.

Sada se vrši pomicanje vrijednosti/memorije objekata ‘mat’ i ‘x’ na Accelerator, ali sada sa opcijom Async.

Ispisuje se info o sva tri objekta. Računa s vrijeme za ‘tack’ i ispisuje koliko je vremena Async transfer trebao, i to na isti način kao i prije.

Računa se vrijeme ‘tick’ za CPU. Opet ide ista ‘for’ petlja kao i prije.

Računa se vrijeme za ‘tack’ i ispisuje se opet koliko je CPU execute trajao i Skalarni produkt.

Sada se objekti ‘mat’ i ‘x’ sinkroniziraju. I ‘y’ se pomiče na Accelerator.

Ispisuje se opet info sva tri objekta.

‘Y’-oni se postavljaju na 0, i računa se vrijeme ‘tick’ za Accelerator.

Ponovno ista ‘for’ petlja. Računa se vrijeme za ‘tack’, i ispisuje Accelerator execution vrijeme, i Skalarni produkt.

Iza toga se računa ponovno vrijeme ‘tackg’ i ispisuje vrijeme kompletnog executa + transfera (async).

Zaustavlja se rocAlution, i kraj programa.