2012-08-09 3 views
41

quando è necessaria la chiamata alla funzione cudaDeviceSynchronize ?.Quando chiamare cudaDeviceSynchronize?

Per quanto ho capito dalla documentazione CUDA, i kernel CUDA sono asincroni, quindi sembra che dovremmo chiamare cudaDeviceSynchronize dopo ogni avvio del kernel. Tuttavia, ho provato lo stesso codice (formazione reti neurali) con e senza qualsiasi cudaDeviceSynchronize, tranne uno prima della misurazione del tempo. Ho scoperto che ottengo lo stesso risultato ma con una velocità tra 7-12x (a seconda delle dimensioni della matrice).

Quindi, la domanda è se ci sono dei motivi per utilizzare cudaDeviceSynchronize oltre alla misurazione del tempo.

Ad esempio:

  • E 'necessario prima di copiare i dati dalla GPU all'host con cudaMemcpy?

  • se faccio moltiplicazione di matrici come

    C = A * B 
    D = C * F 
    

devo mettere cudaDeviceSynchronize tra i due?

Dal mio esperimento Pare che non lo faccia.

Perché il programma cudaDeviceSynchronize rallenta così tanto?

+0

Un'istanza potrebbe essere se si dispone di istruzioni di stampa nel kernel, il buffer non verrà stampato fino a un evento di sincronizzazione. –

risposta

12

Una situazione in cui l'utilizzo di cudaDeviceSynchronize() è appropriato sarebbe quando si eseguono più cudaStream s e si desidera scambiare alcune informazioni. Un caso reale di questo è il parallelismo parallelo nelle simulazioni Monte Carlo quantistiche. In questo caso, vorremmo assicurarci che ogni stream abbia terminato di eseguire alcune istruzioni e ottenuto alcuni risultati prima che inizino a passare i messaggi l'un l'altro, altrimenti finiremmo per passare informazioni inutili. La ragione per cui questo comando rallenta il programma è tale che cudaDeviceSynchronize() impone al programma di attendere che tutti i comandi precedentemente emessi in tutti i flussi sul dispositivo siano terminati prima di continuare (dalla Guida alla programmazione CUDA C). Come hai detto, l'esecuzione del kernel è normalmente asincrona, quindi mentre il dispositivo GPU sta eseguendo il tuo kernel, la CPU può continuare a lavorare su altri comandi, impartire più istruzioni al dispositivo, ecc., Invece di aspettare. Tuttavia, quando si utilizza questo comando di sincronizzazione, la CPU viene invece forzata a rimanere inattiva fino al completamento di tutte le operazioni GPU prima di eseguire qualsiasi altra operazione. Questo comportamento è utile quando si esegue il debug, poiché si potrebbe avere un segfault che si verifica in momenti apparentemente "casuali" a causa dell'esecuzione asincrona del codice del dispositivo (sia in un flusso che in molti). cudaDeviceSynchronize() imporrà al programma di garantire che i kernel/memcpys dello stream (s) siano completi prima di continuare, il che può rendere più facile scoprire dove si verificano gli accessi illegali (poiché l'errore verrà visualizzato durante la sincronizzazione).

43

Sebbene il kernel CUDA sia avviato in modo asincrono, tutte le attività relative alla GPU inserite in un flusso (che è il comportamento predefinito) vengono eseguite in sequenza.

Così, per esempio,

kernel1<<<X,Y>>>(...); // kernel start execution, CPU continues to next statement 
kernel2<<<X,Y>>>(...); // kernel is placed in queue and will start after kernel1 finishes, CPU continues to next statement 
cudaMemcpy(...); // CPU blocks until ememory is copied, memory copy starts only after kernel2 finishes 

Quindi nel tuo esempio, non v'è alcuna necessità di cudaDeviceSynchronize. Tuttavia, potrebbe essere utile per il debug per rilevare quale del kernel ha causato un errore (se ce n'è uno).

cudaDeviceSynchronize potrebbe causare un rallentamento, ma 7-12x sembra troppo.Potrebbe esserci qualche problema con la misurazione del tempo, o i kernel potrebbero essere veramente veloci, e il sovraccarico della sincronizzazione esplicita è enorme rispetto al tempo di calcolo effettivo.

+0

Il "singolo flusso GPU predefinito se non diversamente specificato" non è sempre tenuto da nvcc. Ho appena eseguito il debug di un programma in cui ho suddiviso un lungo calcolo su un kernel in un calcolo a tratti che ha lanciato i kernel uno alla volta in un ciclo for(). Il successivo ciclo di loop() avvia il punto in cui il precedente ciclo di ciclo for() ha lasciato il lato dispositivo. Il bug era che il compilatore nvcc non poteva vederlo solo dal codice host e provato a lanciare ogni kernel allo stesso tempo. Ciò significava che tutti i kernel, ma il primo kernel, stavano calcolando spazzatura. – opetrenko

+2

@opetrenko Non è così che funziona CUDA. –

+0

@AleksandrDubinsky Si prega di leggere il mio commento più attentamente. Ho esplicitamente messo giù "non è sempre tenuto da nvcc". Ho quindi fornito un esempio di un bug specifico che ho inseguito usando cuda-gdb che serve come esempio dimostrando esattamente questo. Sono assolutamente d'accordo sul fatto che sulla base della letteratura di Nvidia questo non è il modo in cui CUDA dovrebbe funzionare ... ma quello che stavo affermando non era un'opinione: era un'osservazione fatta durante il debug su come funzionava in un'istanza specifica. – opetrenko

3

Quando si desidera che la GPU avvii l'elaborazione di alcuni dati, si esegue tipicamente un'invocazione kernal. Quando lo fai, il tuo dispositivo (la GPU) inizierà a fare qualunque cosa tu gli abbia detto di fare. Tuttavia, a differenza di un normale programma sequenziale sul tuo host (la CPU) continuerà a eseguire le prossime righe di codice nel tuo programma. cudaDeviceSynchronize fa in modo che l'host (la CPU) attenda fino a quando il dispositivo (La GPU) ha terminato di eseguire TUTTI i thread che hai avviato, e quindi il tuo programma continuerà come se fosse un normale programma sequenziale.

In piccoli programmi semplici si utilizza in genere cudaDeviceSynchronize, quando si utilizza la GPU per eseguire calcoli, per evitare disallineamenti temporali tra la CPU che richiede il risultato e la GPU che finiscono il calcolo. Usare cudaDeviceSynchronize rende molto più facile programmare il tuo programma, ma c'è un grosso svantaggio: la tua CPU è inattiva continuamente, mentre la GPU fa il calcolo. Pertanto, nel calcolo ad alte prestazioni, si cerca spesso di fare in modo che la CPU esegua calcoli mentre attende che la GPU termini.