Calcolo Parallelo e Distribuito Parallel sparse Matrix-Vector and Matrix-Transpose-Vector multiplication using compressed sparse blocks A. Buluc, J. T. Fineman, M. Frigo, J. R. Gilbert, C. E. Leiserson Calcolo Parallelo e Distribuito Anno Accademico 2009/2010 Presentazione a cura di: Marco Cherubini, Andrea De Pirro, David Santucci, Andrea Tersigni, Luca Tracuzzi
Sommario Formati di memorizzazione convenzionali Il nuovo formato CSB Moltiplicazione Matrice-Vettore con CSB Analisi della complessità Sperimentazione
Formati convenzionali: CSR Analizziamo alcuni formati di memorizzazione convenzionali Consideriamo matrici sparse n×n con nnz elementi non nulli CSR - Compressed Sparse Rows Memorizzazione per righe Efficiente: memorizza n+nnz indici o puntatori per matrici sparse n×n con nnz elementi non nulli Adatto per y Ax Non adatto per y ATx
Formati convenzionali: CSR
Ax parallelo con CSR Nota: ATx con CSC è analogo CSR_SPMV (A, x, y) 1 nA.rows 2 for i0 to n−1 in parallel 3 do y[i]0 4 for kA.row_ptr[i] to A.row_ptr[i+1]−1 5 do y[i]y[i]+A.val[k]·x[A.col_ind[k]] val[nnz] : array dei valori non nulli della matrice (ordinati per righe) col_ind[nnz] : indici di colonna degli elementi nell'array val row_ptr[n] : puntatori al'inizio della riga n nell'array val Nota: ATx con CSC è analogo
Formati convenzionali: CSC CSC - Compressed Sparse Columns Memorizzazione per colonne Efficiente: memorizza n + nnz indici o puntatori Adatto per y ATx risoluzione di problemi di programmazione lineare Non adatto per y Ax
Formati convenzionali: CSC
Il nuovo formato CSB β = block-size parameter Consideriamo matrici sparse n×n con nnz elementi non nulli β = block-size parameter valore ideale = circa √n per semplicità si assume β potenza di 2 CSB - Compressed Sparse Blocks Partizionamento della matrice in blocchi quadrati di dimensione β × β Numero di blocchi n2 / β 2 Ordinamento Z-Morton interno ai blocchi Sostiene y Ax e y ATx
Il nuovo formato CSB
Il nuovo formato CSB
Il nuovo formato CSB
Il nuovo formato CSB
Il nuovo formato CSB
Prod. Matrice-Vettore con CSB CSB_SPMV (A, x, y) 1 for i0 to n/β−1 in parallel // riga di blocco 2 do Initialize a dynamic array Ri 3 Ri[0]0 // Array di indici per // gli ultimi blocchi dei chunk 4 count0 // Contatore nnz in un chunk 5 for j0 to n/β−2 6 do countcount+nnz(Ai,j) 7 if count+nnz(Ai,j+1) > O(β) 8 then // Fine chunk 9 append j to Ri // Ultimo blocco del chunk 10 count0 11 append n/β−1 to Ri 12 CSB_BLOCKROWV (A, i, Ri, x, y[(i∙β),…,((i+1)∙β)−1])
Prod. Matrice-Vettore con CSB Divisione in chunk -1
Prod. Matrice-Vettore con CSB Divisione in chunk -1
Prod. Matrice-Vettore con CSB Divisione in chunk -1
Prod. Matrice-Vettore con CSB Divisione in chunk -1
CSB_BLOCKROWV (A, i, R, x, y) 11 if R.length = 2 // Singolo chunk 12 then ℓR[0]+1 // Blocco più a sinistra nel chunk 13 rR[1] // Blocco più a destra nel chunk 14 if ℓ = r 15 then // Il chunk contiene un singolo blocco denso 16 startA.blk_ptr[ f(i,ℓ)] 17 endA.blk_ptr[ f(i,ℓ)+1]−1 18 CSB_BLOCKV (A, start, end, β, x, y) 19 else // Il chunk è sparso 20 multiply y(Ai,ℓ Ai,ℓ+1 … Ai,r)x serially 21 return // Se la riga di blocchi contiene più chunk 22 mid⌈R.length/2⌉−1 // Divide i chunk in due sottoinsiemi // Calcola il punto di split per il vettore x 23 xmid β·(R[mid]−R[0]) 24 Alloca un vettore z di cardinalità β, inizializzati a 0 25 in parallel 26 do CSB_BLOCKROWV(A, i, R[0…mid], x[0…xmid−1], y) 27 do CSB_BLOCKROWV(A, i, R[mid…R.length−1], x[xmid…x.length−1], z) 28 for k0 to β−1 29 do y[k]y[k]+z[k]
// Se la riga di blocchi contiene più chunk CSB_BLOCKROWV (A, i, R, x, y) 11 if R.length = 2 // Singolo chunk 12 then ℓR[0]+1 // Blocco più a sinistra nel chunk 13 rR[1] // Blocco più a destra nel chunk 14 if ℓ = r 15 then // Il chunk contiene un singolo blocco denso 16 startA.blk_ptr[ f(i,ℓ)] 17 endA.blk_ptr[ f(i,ℓ)+1]−1 18 CSB_BLOCKV (A, start, end, β, x, y) 19 else // Il chunk è sparso 20 multiply y(Ai,ℓAi,ℓ+1…Ai,r)x serially 21 return // Se la riga di blocchi contiene più chunk 22 mid⌈R.length/2⌉−1 // Divide i chunk in due sottoinsiemi // Calcola il punto di split per il vettore x 23 xmid β·(R[mid]−R[0]) 24 Alloca un vettore z di cardinalità β, inizializzati a 0 25 in parallel 26 do CSB_BLOCKROWV(A, i, R[0…mid], x[0…xmid−1], y) 27 do CSB_BLOCKROWV(A, i, R[mid…R.length−1], x[xmid…x.length−1], z) 28 for k0 to β−1 29 do y[k]y[k]+z[k]
Prod. Matrice-Vettore con CSB y[β..2β-1] Split ricorsivo dei chunk
Prod. Matrice-Vettore con CSB y[β..2β-1] Split ricorsivo dei chunk
CSB_BLOCKV (A, start, end, dim, x, y) 28 if end−start ≤ O(dim) 29 then // Calcola la computazione seriale y←y+Mx 30 for kstart to end // A.val[start…end] è un blocco dim×dim 31 do y[A.row_ind[k]] y[A.row_ind[k]] + A.val[k]·x[A.col_ind[k]] 32 return 33 // Ricorsione: divide il blocco M in 4 quadranti 34 binary search start, start+1,…,end per il più piccolo s2 tale che (A.row_ind[s2] & dim/2) ≠ 0 35 binary search start, start+1,…,s2−1 per il più piccolo s1 tale che (A.col_ind[s1] & dim/2) ≠ 0 36 binary search s2, s2+1,…,end per il più piccolo s3 tale che (A.col_ind[s3] & dim/2) ≠ 0 37 in parallel 38 do CSB_BLOCKV (A, start, s1−1, dim/2, x, y) // M00 39 do CSB_BLOCKV (A, s3, end, dim/2, x, y) // M11 40 in parallel 41 do CSB_BLOCKV (A, s1, s2−1, dim/2, x, y) // M01 42 do CSB_BLOCKV (A, s2, s3−1, dim/2, x, y) // M10
Prod. Matrice-Vettore con CSB Decomposizione Z-Morton in 4 quadranti
Prod. Matrice-Vettore con CSB Decomposizione Z-Morton in 4 quadranti
Prod. Matrice-Vettore con CSB Decomposizione Z-Morton in 4 quadranti
Prod. Matrice-Vettore con CSB Decomposizione Z-Morton in 4 quadranti
Analisi di complessità Al fine di valutare la complessità dell'algoritmo definiamo: work: denotato con T1, rappresenta il tempo di esecuzione in una macchina monoprocessore monothread span: denotato con T∞, rappresenta il tempo di esecuzione con un infinito numero di processi o thread Viene definito grado di parallelismo il rapporto T1/T∞
Lemma 1 Lemma 1: il formato CSR usa n∙log(nnz) + nnz∙log(n) bit di indici di supporto per una matrice n×n con nnz elementi non nulli. Dimostrazione: per indicizzare x elementi sono necessari log(x) bit. Dal prodotto righe-colonne risultano n∙log(nnz) bit per il row_ptr e nnz∙log(n) bit per col_ind.
Lemma 1 (continua)
Lemma 2 Lemma 2: Il formato CSB usa (n2/β2)∙log(nnz)+2∙nnz∙log(β) bit di indici di supporto per una matrice n×n con nnz elementi non nulli. Dimostrazione: per ogni elemento in val, usiamo log(β) bit per rappresentare l'indice di riga e log(β) bit per rappresentare l'indice di colonna e richiede quindi nnz∙log(β) bit per ciascuno degli indici. Aggiungiamo lo spazio dato dall'array blk_ptr, ossia (n2/β2) ∙ log(nnz) bit.
Lemma 2 (continua)
Lemma 2 (continua)
Corollario 3 Corollario 3: il formato CSB usa (n)∙log(nnz)+nnz∙log(n) bit di indici di supporto per una matrice n×n con nnz elementi non nulli, con β=√n.
Lemma 4 Lemma 4: Su un blocco di dimensioni β×β, contenente r elementi non nulli, CSB_BlockV viene eseguito con work O(r) e span O(β). Dimostrazione (span): lo span relativo alla moltiplicazione di una matrice dim×dim può essere descritto da S(dim)=2∙S(dim/2)+O(log(dim))=O(dim). 2∙S(dim/2): viene invocata 2 volte in parallelo la ricorsione su un singolo blocco di dim/2 O(log(dim)): costo della ricerca binaria dei tre indici di split caso base = O(dim): il caso base è seriale su O(dim) elementi ed è dominante sui casi ricorsivi
Lemma 4 (continua) Dimostrazione (work): Consideriamo l'albero di grado 4 generato dalle chiamate ricorsive della funzione CSB_BlockV; ogni nodo corrisponde alla computazione di un sottoblocco dim×dim, con dim=2h, e 0<h<log(β). Se un nodo è una foglia, allora verifica il caso base ed ha sicuramente al più O(2h)=O(dim) elementi non nulli. Ne deriva quindi che il costo di computazione del nodo è O(r). [..]
CSB_BLOCKV (A, start, end, dim, x, y) 28 if end−start ≤ O(dim) 29 then // Calcola la computazione seriale y←y+Mx 30 for kstart to end // A.val[start…end] è un blocco dim×dim 31 do y[A.row_ind[k]] y[A.row_ind[k]] + A.val[k]·x[A.col_ind[k]] 32 return 33 // Ricorsione: divide il blocco M in 4 quadranti 34 binary search start, start+1,…,end per il più piccolo s2 tale che (A.row_ind[s2] & dim/2) ≠ 0 35 binary search start, start+1,…,s2−1 per il più piccolo s1 tale che (A.col_ind[s1] & dim/2) ≠ 0 36 binary search s2, s2+1,…,end per il più piccolo s3 tale che (A.col_ind[s3] & dim/2) ≠ 0 37 in parallel 38 do CSB_BLOCKV (A, start, s1−1, dim/2, x, y) // M00 39 do CSB_BLOCKV (A, s3, end, dim/2, x, y) // M11 40 in parallel 41 do CSB_BLOCKV (A, s1, s2−1, dim/2, x, y) // M01 42 do CSB_BLOCKV (A, s2, s3−1, dim/2, x, y) // M10
Lemma 4 (continua)
Lemma 4 (continua)
Lemma 4 (continua)
Lemma 4 (continua) [...] Se un nodo è interno, allora ha almeno O(dim) elementi non nulli. Il costo computazionale del nodo, senza considerare nodi figli, è pari a O(log(dim))=O(log(2h))=O(h), dovuto alla ricerca binaria. I nodi generici di livello h sono al più O(r/dim), e quindi concorrono ad un lavoro complessivo per ogni livello pari a O(h∙r/dim). sommando, per ogni h, nodi interni su tali livelli e nodi foglia, otteniamo O(r):
Lemma 5 Questo lemma analizza la moltiplicazione fra una riga di blocchi e un vettore Lemma 5: Su una riga di blocchi contenente n/β blocchi e r elementi non nulli, CSB_BlockrowV viene eseguito con work O(r) e span O(β∙log(n/β)).
Lemma 5 (continua) Dimostrazione (work): consideriamo la chiamata su una riga di blocco partizionata in C chunk e definiamo W(C) il lavoro (work) eseguito. La funzione inizializza un vettore z di O(β) elementi e richiama ricorsivamente se stessa due volte, sulla metà dell'input Il lavoro è descritto dalla disequazione W(C) ≤ 2∙W(⌈C/2⌉)+O(β) da cui deriva W(C)=O(C∙β+r), poiché si hanno C attivazioni di complessità O(β), più i casi base di complessità O(r), ossia la computazione seriale del prodotto. Il numero C di chunk è, al più, pari a O(r/β) nel caso in cui r=O(β)
Lemma 5 (continua) Dimostrazione (span): Lo span può essere descritto da S(C)=S(⌈C/2⌉)+O(β)=O(β∙log(C))+S(1) Abbiamo che il caso base ha uno span pari O(β) sia nel caso della moltiplicazione seriale che in quella nella chiamata CSB_BlockV Il caso base viene eseguito log(C) volte, con C ≤ n/β Lo span complessivo è quindi O(β∙log(n/β))
Teorema 6 Teorema 6: In una matrice n×n contenente r elementi non nulli, CSB_SpMV viene eseguito con un work O(r+n2/β2) e uno span di O(β∙lg(n/β))+n/β). Dimostrazione: CSB_SpMV ricostruisce i chunk e avvia la funzione CSB_BlockrowV. Il costo computazionale, per work e span deriva dal lemma precedente con l'aggiunta del costo necessario alla costruzione dei chunk. [...]
Teorema 6 (continua) Dimostrazione: [...] nel caso del work si aggiunge un costo pari a O(n2/β2) dovuto all'analisi di una singola riga di blocchi di costo O(n/β) per un costo totale di O(r+n2/β2) nel caso dello span si aggiunge un costo pari a O(n/β) dato che è possibile parallelizzare l'operazione per ogni singola riga di blocchi, ottenendo un costo totale di O(β∙lg(n/β))+n/β)
Corollario 7 e 8 Corollario 7: in una matrice n×n, contenente nnz ≥ n valori non nulli, scegliendo β=O(√n), CSB_SpMV lavora con un work di O(nnz) e uno span di O(√n)∙log(n)) raggiungendo un parallelismo di almeno O( nnz / (√n∙log(n)) ) Corollario 8: in una matrice n×n, contenente nnz ≥ n valori non nulli, scegliendo β=O(√n), CSB_SpMV_T lavora con un work di O(nnz) e uno span di O(√n∙log(n)) raggiungendo un parallelismo di almeno
Lemma 9 Lemma 9: In una matrice n×n, scegliendo β = O(√n), la serializzazione di CSB_SpMV richiede uno spazio di O(√n∙log(n)) non contando lo spazio occupato dalla matrice stessa Dimostrazione: lo spazio complessivo utilizzato è dato da due overhead il primo, R, è l'array dei chunk, che, per ogni riga, utilizza uno spazio O(n/β). Dato che β=√n, si ha che lo spazio complessivo utilizzato è O(√n). il secondo, z, è il vettore temporaneo di dimensione pari a β. A causa della profondità della ricorsione, lo spazio utilizzato è O(β∙log(n))=O(√n∙log(n)) la complessità finale è quindi O(√n∙log(n))+O(√n)=O(√n∙log(n))
Corollario 10 Corollario 10: supponiamo un'esecuzione di CSB_SpMV per una matrice n×n con la scelta β=√n in un work-stealing scheduler (preemptive round robin) con la proprietà busy-leaves. Allora l'esecuzione richiede uno spazio di O(P∙√n∙log(n)), con P pari al numero di processi utilizzati
Sperimentazione Scelta valore di β Performance media di Ax e ATx Risultati reali sulla scalabilità degli algoritmi matrici di medie dimensioni matrici di grandi dimensioni
Scelta del valore di β Si dimostra sperimentalmente che il valore ottimale di β deve rispettare la disequazione ⌈log(√n)⌉ ≤ log(β) ≤ 3+⌈log(√n)⌉ L'utilizzo di β=√n per semplicità di calcolo rispetta tale vincolo
Performance media di Ax e ATx Sia CSB_SpMV che CSB_SpMV_T offrono ottime prestazioni all'aumentare del numero di processori fino a 4 processori lo speedup cresce linearmente da 4 a 8 è meno che lineare, ma comunque crescente con più di 8, le prestazioni decadono a causa dei limiti della memoria di sistema
Risultati sperimentali (dim media) I risultati sono stati condotti con matrici relative a problemi reali (e.g. elettroforesi DNA, meccanica strutturale,...)
Risultati sperimentali (dim grande) Per matrici grandi CSB scala in modo ottimale
CSR vs CSB Analizziamo il rapporto tra le performance di CSR e CSB nel caso di esecuzione parallela Per il confronto è stato usato l’algoritmo Star-P che usa CSR
Grazie per l'attenzione