Preskoči na sadržaj

rocALUTION: ROCm SPARSE Linear Algebra PACkage

U nastavku koristimo kod iz repozitorija rocALUTION (službena dokumentacija).

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 (poveznica na kod) koristi 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 dio službene dokumentacije koji govori o solverima.)

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 službenu dokumentaciju.)

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(), 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 API dokumentaciju, a a više informacija o funkciji atoi() pogledajte cppreference.

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 službenu dokumentaciju.

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 izgrađuje putem funkcije Build(). 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]'. '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.

Author: Mia Doričić, Vedran Miletić