2016-05-06 26 views
6

Ho implementato una piccola CNN in RenderScript e desidero profilare le prestazioni su hardware diversi. Sul mio Nexus 7 i tempi hanno senso, ma su NVIDIA Shield non lo fanno.Come eseguire la sincronizzazione corretta del codice Android RenderScript su Nvidia Shield

La CNN (LeNet) è implementata in 9 livelli che risiedono in una coda, il calcolo viene eseguito in sequenza. Ogni livello è cronometrato individualmente.

Ecco un esempio:

 conv1 pool1 conv2 pool2 resh1 ip1 relu1 ip2 softmax 
nexus7 11.177 7.813 13.357 8.367 8.097 2.1 0.326 1.557 2.667 
shield 13.219 1.024 1.567 1.081 0.988 14.588 13.323 14.318 40.347 

La distribuzione dei tempi sono circa proprio per il nesso, con CONV1 e CONV2 (strati di convoluzione) occupa la maggior parte del tempo. Ma sullo scudo, i tempi scendono molto oltre ciò che è ragionevole per i livelli 2-4 e sembrano radunarsi verso la fine. Lo strato di softmax è un lavoro relativamente piccolo, quindi 40 ms è troppo grande. Il mio metodo di cronometraggio deve essere difettoso, o qualcos'altro sta succedendo.

Il codice in esecuzione gli strati simile a questa:

double[] times = new double[layers.size()]; 
int layerindex = 0; 
for (Layer a : layers) { 

    double t = SystemClock.elapsedRealtime(); 
    //long t = System.currentTimeMillis(); // makes no difference 

    blob = a.forward(blob); // here we call renderscript forEach_(), invoke_() etc 

    //mRS.finish(); // makes no difference 

    t = SystemClock.elapsedRealtime() - t; 
    //t = System.currentTimeMillis() - t; // makes no difference 

    times[layerindex] += t; // later we take average etc 

    layerindex++; 
} 

E 'la mia comprensione che una volta forEach_() ritorna, si suppone il lavoro da finire. In ogni caso, mRS.finish() dovrebbe fornire una barriera finale. Ma guardando i tempi, l'unica spiegazione ragionevole è che i lavori vengano ancora elaborati in background.

L'app è molto semplice, ho appena eseguito il test da MainActivity e stampato su logcat. Android Studio crea l'app come una versione e la esegue sul dispositivo che è collegato tramite USB.

(1) Qual è il modo corretto di eseguire i processi di RenderScript? (2) È vero che quando forEach_() restituisce, i thread generati dallo script sono garantiti per essere eseguiti? (3) Nella mia app di test, eseguo semplicemente direttamente da MainActivity. Si tratta di un problema (oltre a bloccare il thread dell'interfaccia utente e rendere l'app non risponde)? Se questo influenza il tempismo o causa la stranezza, qual è il modo corretto per impostare un'app di prova come questa?

+0

CNN sta per? –

+0

Rete neurale involutiva. – frankhond

risposta

3

Ho implementato CNN in RenderScript da solo e, come spiegato, richiede il concatenamento di più processi e chiama forEach_*() varie volte per ogni livello se li si implementa ciascuno come un diverso kernel. In quanto tale, posso assicurarvi che la restituzione di ogni chiamata non garantisce realmente che il processo sia completato. In teoria, questo programma solo il kernel e tutte le richieste in coda verranno effettivamente eseguite ogni volta che il sistema determina che è meglio, soprattutto se vengono elaborate nella GPU del tablet.

Di solito, l'unico modo per essere assolutamente sicuri di avere un qualche tipo di controllo su un kernel veramente in esecuzione è leggere esplicitamente l'output del kernel RS tra livelli, ad esempio usando .copyTo() sull'oggetto di allocazione di output di quello kernel. Questo "impone" a tutti i job RS in coda che non sono ancora stati eseguiti (da cui dipende l'allocazione di output di quel livello), da eseguire in quel momento. Certo, questo potrebbe introdurre i costi di trasferimento dei dati e il vostro tempismo non sarà completamente accurato - in effetti, il tempo di esecuzione dell'intera rete sarà sicuramente inferiore alla somma dei singoli livelli se programmato in questo modo. Ma per quanto ne so, è l'unico modo affidabile per misurare i singoli kernel in una catena e ti darà un feedback per scoprire dove sono i colli di bottiglia e per guidare meglio l'ottimizzazione, se è quello che cerchi.

+0

Grazie! Ho letto le altre tue domande che hanno contribuito alla mia comprensione di RenderScript. Posso prendere ancora il tuo cervello? Quando si prova qualcosa del genere su un tablet con una GPU come Shield, quale sarebbe un buon modo per scoprire se il codice viene effettivamente eseguito sulla GPU? – frankhond

+0

Hmm, come al solito, è un po 'difficile dire dove verrà eseguito un kernel RS, ma se hai la tua configurazione di temporizzazione fissa, puoi semplicemente eseguire test con i tuoi kernel funzionanti normalmente e eseguirli mentre forzando RS ad usare la CPU solo con 'adb shell setprop debug.rs.default-CPU-driver 1' (e 0 per disattivarlo, assicurati di terminare correttamente l'app tra una modifica e l'altra affinché abbia effetto). Se il tuo kernel è ben fatto e usa realmente la GPU, dovresti notare una differenza notevole, specialmente nelle convoluzioni dovresti ottenere almeno un aumento di velocità di 4-6x rispetto alla modalità solo CPU. – monoeci

+0

Bello, ci proverò! Speravo ci fosse un profiler disponibile da qualche parte, ma questo darebbe sicuramente un'indicazione. Grazie ancora per il vostro aiuto! – frankhond

3

Forse un po 'fuori tema, ma per la CNN, se è possibile strutturare l'algoritmo utilizzando matrice moltiplicazione di matrici come blocchi di elaborazione di base si può effettivamente utilizzare RenderScript IntrinsicBLAS, soprattutto BNNM e SGEMM.

Pro:

  1. implementazione ad alte prestazioni di 8bit moltiplicazione di matrici (BNNM), disponibile in N Preview.
  2. Supporto per la schiena per Android 2.3 tramite RenderScript Support lib, quando si utilizza Build-Tools 24.0.0 rc3 e versioni successive.
  3. Accelerazione GPU ad alte prestazioni di SGEMM su Nexus5X e 6P con N Anteprima NPC91K.
  4. Se si utilizza solo RenderScript Intrinsics, è possibile codificare tutto in java.

Contro:

  1. l'algoritmo potrebbe essere necessario essere riscritta, e la necessità di basarsi su 2d moltiplicazione di matrici.
  2. Sebbene disponibile in Android 6.0, le prestazioni di BNNM in 6.0 non sono soddisfacenti. Quindi è meglio usare support lib per BNNM e impostare targetSdkVersion su 24.
  3. L'accelerazione GPEM SGEMM è attualmente disponibile solo in Nexus5X e Nexus6P. E attualmente richiede che la larghezza e l'altezza delle matrici siano multipli di 8.

Vale la pena provare se BLAS si adatta al proprio algoritmo. Ed è facile da usare:

import android.support.v8.renderscript.*; 
    // if you are not using support lib: 
    // import android.renderscript.*; 

    private void runBNNM(int m, int n, int k, byte[] a_byte, byte[] b_byte, int c_offset, RenderScript mRS) { 
     Allocation A, B, C; 
     Type.Builder builder = new Type.Builder(mRS, Element.U8(mRS)); 
     Type a_type = builder.setX(k).setY(m).create(); 
     Type b_type = builder.setX(k).setY(n).create(); 
     Type c_type = builder.setX(n).setY(m).create(); 

     // If you are reusing the input Allocations, just create and cache them somewhere else. 
     A = Allocation.createTyped(mRS, a_type); 
     B = Allocation.createTyped(mRS, b_type); 
     C = Allocation.createTyped(mRS, c_type); 
     A.copyFrom(a_byte); 
     B.copyFrom(b_byte); 

     ScriptIntrinsicBLAS blas = ScriptIntrinsicBLAS.create(mRS); 
     // Computes: C = A * B.Transpose 
     int a_offset = 0; 
     int b_offset = 0; 
     int c_offset = 0; 
     int c_multiplier = 1; 
     blas.BNNM(A, a_offset, B, b_offset, C, c_offset, c_multiplier); 
    } 

SGEMM è simile:

 ScriptIntrinsicBLAS blas = ScriptIntrinsicBLAS.create(mRS); 
     // Construct the Allocations: A, B, C somewhere and make sure the dimensions match. 
     // Computes: C = 1.0f * A * B + 0.0f * C 
     float alpha = 1.0f; 
     float beta = 0.0f; 
     blas.SGEMM(ScriptIntrinsicBLAS.NO_TRANSPOSE, ScriptIntrinsicBLAS.NO_TRANSPOSE, 
        alpha, A, B, beta, C); 
+0

Grazie per gli esempi. Ho già implementato la convoluzione usando la moltiplicazione di im2col e matrice con SGEMM, oltre a una versione del kernel in movimento. La ragione della mia domanda è che sto cercando di profilare questi due algoritmi l'uno contro l'altro. A seconda dell'hardware, sto ottenendo risultati completamente diversi e provo a capire perché. Il tuo esempio BNNM è molto utile visto che stavo per passare a quello successivo. – frankhond

+0

Inoltre, grazie per le informazioni sulla GPU, molto utile. Questo può essere trovato online da qualche parte? – frankhond

+0

Non ancora. Ma ci saranno più tutorial e documentazione su di esso. Lo aggiornerò qui una volta che sarà disponibile, restate sintonizzati. Inoltre, se hai suggerimenti o richieste di funzionalità per RenderScript, faccelo sapere. Posso inoltrarlo al team di RenderScript. Grazie! –