venerdì 23 novembre 2012

GPU su Matlab Primi Passi

In questi giorni mi sto addentrando nel mondo del calcolo parllelo con GPU. Poichè il linguaggio che conosco meglio è Matlab, sto cercando di applicarmi su questo.
Con questa tecnica di programmazione si possono scrivere script molto più veloci in quanto la GPU può processare anche un migliaio processi in parallelo contro i 4 o 8 processi  delle più moderne CPU. Questo significa dividere per 100 o 200 volte il tempo di calcolo. Quindi se uno script per una simulazione per esempio di ottica necessita 3 mesi per dare un risultato , con queta tecnica darebbe il risultato in 1 giorno!
L'utilizzo delle pontenzialità della GPU con Matlab è molto semplice e non richiede la conoscienza della programmazione in parrallelo. Questo perchè una serie di librerie si occupa parallelizzare la compilazione, bisogna solo stare un po' attenti a caricare le variabili nella memoria della GPU piuttosto che quella dell'Host (macchina in cui è montata la GPU).

Configurazione Hardware

  1. Un PC abbastanza recente da montare uno slot PCI express 16X
  2. Una scheda grafica Nvidia (di qualsiai produttore) che abbia le cpacità CUDA. Queste sono le schede dalla GTX9800 in poi.
Nel caso si decida di eseguire una upgrade del proprio PC,bisogna fare molta attenzione alla potenza richiesta dalla nuova scheda grafica. Nella maggior parte dei casi si dovrà cambiare anche l'alimentatore passando ad un modello più potente (600W in genere bastano).

Configurazione software

Nel mio caso ho un PC che monta un dual core Intel con 4GB di RAM e una  Scheda ASUS GeForce GTX550 Ti con 1 GB di RAM. Ho installato Windows 7 64 bit.
Il software da installare è il seguente:
  1. Matlab 32 o 64 bit a seconda del sistema operativo che si sta facendo girare, sia esso Windows, Linux (Ubuntu, Fedora, Suse, ecc...), Mac IOS. E' importante che Matlab e il sistema operativo siano della stessa classe ossia 32 o 64 bit. Importante è che Matlab di una versione successiva alla 2007b o la 2007b stessa
  2.  GPUmat  ossia quell'insieme di librerie e script di Matlab che permettono di scrivere gli script in Matlab occupandosi poi di parallelizzare il calcolo nella GPU. Nel mio caso ho scaricato la versione 0.280.
  3.  Il compilatore C++, nel caso di Windows: Microsoft Visual C++ 2008 Redistributable. Anche qui scaricare ed installare x86 oppure x64 a seconda dell'architettura del proprio sistema operativo. Nel caso di Linux mi pare serva il g++ 4.4 (ma meglio controllare).
  4. I driver CUDA e le libreria CUDA SDK che si occupano di gestire la GPU. Si può scricare un pacchetto che si chiama CUDA toolkit e che installa di tutto e di più. Nel caso della versione GPUmat 0.280 viene richiesta la versione CUDA 4.2 . In ogni caso bisogna stare molto attenti ai driver grafici che sono stati installati. Nel mio caso ad esempio avevo installato gli ultimi driver NVIDIA che però supportano CUDA 5.0. Per ottenere la compatibilità con GPUmat 0.280 ho dovuto effettuare un downgrade dei driver alla versione 4.2. Ovviamente anche in questo caso selezionare i driver per l'architettura corrispondente (32 o 64 bit).
     
 Fatto. Ora il sistema è operativo e pronto al primo script.

Prove di velocità

Nella cartella che contiene GPUmat esistono i manuali per addentrarsi nel calcolo GPU. Il concetto è semplice, almeno inizialmente, basta caricare le variabili nella memoria GPU e quindi scrivere lo script come se la GPU non esistesse. Infatti le prime pagine del manuale "GPUmat User Guide" sono esclusivamente incentrate su esercizi per capire come e dove caricare  variabili, vettori e matrici. Il manuale è molto chiaro comunque.
Una volta capito questo mi sono messo a fare test di velocità e ho scoperto com ia grande sorpresa che la CPU è più veloce della GPU. Dopo un po' di "sgooglaggio" ho capito che la velocità è condizionata dall'accesso alla memoria...ecco perchè tanti esercizi...Quindi una matrice creata nell'Host e trasferita in GPU è più lenta di una matrice creata direttamente in GPU.
Questo non basta però perchè per calcoli semplici i test di bechmark danno sempre lo stesso risultato ossia CPU più veloce della GPU.
Solo in alcuni casi la GPU arriva a 3X la velocità dell CPU. Ma di quanto ci si può aspettare di incrementare la velocità con il calcolo parallelo in GPU?
Semplice,  se avessi 1 CPU e le velocità del clock (CPU e GPU) fossero le stesse mi aspetterei un incremento di velocità pari al numero di CUDA core.
Ma pendiamo il mio esempio. Io monto:
  1. una CPU con 2 core che procesano 2 thead a 3.4 Ghz
  2. una GPU con 192 CUDA cores con il processore a 900Mhz.
Ora l'incremento di velocità atteso in prima approssimazione sarà

(192/2) * 900/3400 =25
eq.1
Quindi mi aspetto un incremento di velocità 25X circa. Serve però tenere anche in considerazione la velocità della RAM. La RAM grafica ha il clock a 1026 Mhz mntre quella della CPU che è una vecchia DDR2 gira a 400 Mhz. Questo mi dice che a pieno carico di calcolo il moltiplicatore potrebbe arrivare ad essere circa 60X.

Per scoprire quando e in che condizioni si può veramente arrivare a moltiplicare X20 la velocità di calcolo ho adattato uno degli script presenti nel manuale "GPUmat User Guide":

clear all
close all
N = 100:100:7000;
timecpu = zeros(1,length(N));
timegpu = zeros(1,length(N));
index=1;

for i=N
    Ah = single(rand(i)); % CPU
    A = rand(i,GPUsingle); % GPU
    %% Execution on GPU
    tic;
    A.*exp(A);
    GPUsync;
    timegpu(index) = toc;
    %% Execution on CPU
    tic;
    Ah.*exp(Ah);
    timecpu(index) = toc;
    % increase index
    index = index +1;
end
 speedup = (timecpu./timegpu);
 

figure
grid on
plot(N,speedup,'*')


Script 1

Questo script genera matrici di dimensione sempre più grande alternativamente nella GPU e nell'Host per evitare il rallentamento dovuto al passaggio di RAM (Host-->GPU). Le moltiplica quindi elemento per elemento esponenziando prima della moltiplicazione. E' un calcolo abbastanza complicato soprattutto quando le matrici vanno verso dimensioni sempre più grandi (massimo consentito dalla memoria GPU del mio sistema 7000X7000). Lo script è in single precision quindi con numeri 4 byte.
Ogni volta che una matrice viene processata, i comandi tic e toc registrano inizio e fine del calcolo.
Figura 1
Infine l'operazione speedup = (timecpu./timegpu);calcola il rapporto fra i tempi di calcolo e quindi il moltiplicatore.

Con l'aumentare della complessità del calcolo la GPU stacca la CPU fino ad arrivare attorno ai 20X. Il grafico mostra come per piccole matrici la CPU rimanga più veloce della GPU ossia il moltiplicare è minore di 1. Tuttavia questo moltiplicatore non mi soddisfa e quindi intendo migliorare lo script.

Accelerazione dello Script

Ora provo a trovare il sistema di accelerare lo script. Creo uno script derivato dal precedente:

clear all
close all
i=5000;
for k=1:100
    Ah = single(rand(i)); % CPU
    A = rand(i,GPUsingle); % GPU
    %% Execution on GPU
    tic;
    A=A.*exp(A);
    GPUsync;
    timegpu = toc;
    %% Execution on CPU
    tic;
    Ah=Ah.*exp(Ah);
    timecpu = toc;
    spup(k) = (timecpu/timegpu);
end
mean(spup)

Script 2

Questo script semplicemente crea per cento volte la  matrice di numeri random 5000X5000 e poi fa delle operazioni matemeatiche con gli elementi della matrice stessa.
Il molteplicatore aumenta visibilmente in questo caso: 40X tuttavia il suo valore non è sempre stabile, 
Figura 2

variando da 20X a 55X.  La media è 41X e la deviazione standard è 4.7.
Il comportamento è strano in quanto da figura 1 per matrici 5000X5000 mi aspetterei un moltiplicatore attorno a 17X.
Potrebbe essere un problema di allocazione delle variabili nella memoria GPU e quindi riscrivo la riga 13 dello Script 1 definendo l'operazione come

...
A=A.*exp(A);
...

Il risultato è una netta accelerazione dello script GPU in accordo con quanto riscontrato in Figura 2
Figura 3
Questo penso sia il massimo raggiungibile dal mio sistema. Il comporatamento è lo stesso rilevato in Figura 1 ossia una crescita costante della differenza di prestazioni tra GPU e CPU all'aumetare dell'esigenza di potenza di calcolo. Ci sono punti anche a 50X per le matrici più grandi e questo è in accordo con quanto detto in eq.1. Ovviamente per piccoli calcoli la CPU resta più veloce in quanto è più veloce il chip stesso.

Linee guida per la scrittura del codice (da GPUmat User Giude)

Per massimizzare le prestazioni dell'esecuzione vanno considerati i seguenti punti:

  • Trasferimenti di memoria: evitare eccessivi trasferimenti di memoria tra GPU/CPU memory.
  • Operazioni vetoriali e for-loops: si possono ottenere le prestazioni migliri per Matlab e GPUmat usandooperazioni tra vettori ed evitando i for-loops. Ulteriori informazioni in: Matlab Code Vectorization Guide
  • Usari funzioni di basso livello per evitare la creazione di troppe variabili intermedie temporanee. Questo può velocizzare ilcodice aiutando inoltre a risolvere errori della memoria GPU.
  • Compilare le funzioni usando GPUmat compiler. Il compilatore può essere usato per registrare le funzioni GPU in nuove funzioni Matlab.
Link utili otre a quelli già scritti:

http://blogs.mathworks.com/loren/2012/02/06/using-gpus-in-matlab/
http://sccn.ucsd.edu/wiki/GPU_and_EEGLAB
http://www.accelereyes.com/download_jacket

Nessun commento:

Posta un commento