Comprensione delle dimensioni della griglia CUDA, delle dimensioni dei blocchi e dell'organizzazione dei thread (spiegazione semplice) [chiuso]


161

Come sono organizzati i thread per essere eseguiti da una GPU?


La Guida alla programmazione CUDA dovrebbe essere un buon punto di partenza per questo. Consiglierei anche di dare un'occhiata all'introduzione alla CUDA da qui .
Tom

Risposte:


287

Hardware

Se un dispositivo GPU ha, ad esempio, 4 unità multiprocessore e può eseguire 768 thread ciascuno: in un determinato momento non più di 4 * 768 thread funzioneranno in parallelo (se hai pianificato più thread, saranno in attesa il loro turno).

Software

le discussioni sono organizzate in blocchi. Un blocco viene eseguito da un'unità multiprocessore. I thread di un blocco possono essere identificati (indicizzati) usando 1Dimension (x), 2Dimensions (x, y) o 3Dim indexs (x, y, z) ma in ogni caso x y z <= 768 per il nostro esempio (si applicano altre restrizioni a x, y, z, consultare la guida e le funzionalità del dispositivo).

Ovviamente, se hai bisogno di più di quei thread 4 * 768 hai bisogno di più di 4 blocchi. I blocchi possono anche essere indicizzati 1D, 2D o 3D. C'è una coda di blocchi in attesa di entrare nella GPU (perché, nel nostro esempio, la GPU ha 4 multiprocessori e solo 4 blocchi vengono eseguiti contemporaneamente).

Ora un caso semplice: elaborazione di un'immagine 512x512

Supponiamo di volere che un thread elabori un pixel (i, j).

Possiamo usare blocchi di 64 thread ciascuno. Quindi abbiamo bisogno di 512 * 512/64 = 4096 blocchi (quindi per avere 512x512 thread = 4096 * 64)

È comune organizzare (per facilitare l'indicizzazione dell'immagine) i thread nei blocchi 2D con blockDim = 8 x 8 (i 64 thread per blocco). Preferisco chiamarlo threadPerBlock.

dim3 threadsPerBlock(8, 8);  // 64 threads

e 2D gridDim = 64 x 64 blocchi (i 4096 blocchi necessari). Preferisco chiamarlo numBlocks.

dim3 numBlocks(imageWidth/threadsPerBlock.x,  /* for instance 512/8 = 64*/
              imageHeight/threadsPerBlock.y); 

Il kernel viene lanciato in questo modo:

myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );       

Infine: ci sarà qualcosa come "una coda di 4096 blocchi", in cui un blocco è in attesa di essere assegnato a uno dei multiprocessori della GPU per ottenere i suoi 64 thread eseguiti.

Nel kernel il pixel (i, j) da elaborare con un thread viene calcolato in questo modo:

uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;

11
Se ogni blocco può eseguire 768 thread, perché usare solo 64? Se usi il limite massimo di 768, avrai meno blocchi e prestazioni migliori.
Aliza,

10
@Aliza: i blocchi sono logici , il limite di 768 thread è per ogni unità di elaborazione fisica . I blocchi vengono utilizzati in base alle specifiche del problema per distribuire il lavoro ai thread. Non è probabile che tu possa sempre usare blocchi di 768 thread per ogni problema che hai. Immagina di dover elaborare un'immagine 64x64 (4096 pixel). 4096/768 = 5.333333 blocchi?
cibercitizen1,

1
i blocchi sono logici, ma ogni blocco è assegnato a un core. se ci sono più blocchi che core, i blocchi vengono messi in coda fino a quando i core diventano liberi. Nel tuo esempio puoi usare 6 blocchi e fare in modo che i thread extra non facciano nulla (2/3 dei thread sul sesto blocco).
Aliza,

3
@ cibercitizen1 - Penso che il punto di Aliza sia valido: se possibile, si desidera utilizzare quanti più thread per blocco possibile. Se esiste un vincolo che richiede meno thread, meglio spiegare perché ciò potrebbe essere il caso in un secondo esempio (ma spiegare ancora il caso più semplice e desiderabile, in primo luogo).

6
@thouis Sì, forse. Ma il caso è che la quantità di memoria necessaria per ogni thread dipende dall'applicazione. Ad esempio, nel mio ultimo programma, ogni thread invoca una funzione di ottimizzazione del minimo quadrato, che richiede "molta" memoria. Al punto che i blocchi non possono essere più grandi dei thread 4x4. Anche così, l'accelerazione ottenuta è stata drammatica, rispetto alla versione sequenziale.
cibercitizen1,

9

Supponiamo che una GPU 9800GT:

  • ha 14 multiprocessori (SM)
  • ogni SM ha 8 processori thread (processori stream, SP o core AKA)
  • consente fino a 512 thread per blocco
  • warpsize è 32 (il che significa che ciascuno dei 14x8 = 112 processori di thread può programmare fino a 32 thread)

https://www.tutorialspoint.com/cuda/cuda_threads.htm

Un blocco non può avere più thread attivi di 512 quindi __syncthreadspuò solo sincronizzare un numero limitato di thread. vale a dire se si esegue quanto segue con 600 thread:

func1();
__syncthreads();
func2();
__syncthreads();

quindi il kernel deve essere eseguito due volte e l'ordine di esecuzione sarà:

  1. func1 viene eseguito per i primi 512 thread
  2. func2 viene eseguito per i primi 512 thread
  3. func1 viene eseguito per i thread rimanenti
  4. func2 viene eseguito per i thread rimanenti

Nota:

Il punto principale __syncthreadsè un'operazione a livello di blocco e non sincronizza tutti i thread.


Non sono sicuro del numero esatto di thread che è __syncthreadspossibile sincronizzare, poiché è possibile creare un blocco con più di 512 thread e lasciare che l'ordito gestisca la pianificazione. Secondo la mia comprensione è più preciso dire: func1 viene eseguito almeno per i primi 512 thread.

Prima di modificare questa risposta (nel 2010), ho misurato che i thread 14x8x32 sono stati sincronizzati usando __syncthreads.

Gradirei molto se qualcuno lo testasse nuovamente per un'informazione più accurata.


Cosa succede se func2 () dipende dai risultati di func1 (). Penso che sia sbagliato
Chris

@Chris L'ho scritto sette anni fa, ma se ricordo bene ho fatto un test su questo e ho ottenuto la conclusione che i kernel con più thread di gpu si comportano in questo modo. Se ti capita di provare questo caso e hai raggiunto un risultato diverso, dovrò eliminare questo post.
Bizhan,

Mi dispiace, penso che sia sbagliato anche che la GPU possa eseguire contemporaneamente 112 thread.
Steven Lu,

@StevenLu l'hai provato? inoltre, non credo che 112 thread simultanei abbiano alcun senso per una GPU. 112 è il numero di processori stream. Non riesco quasi a ricordare CUDA ora :)
Bizhan,

1
@StevenLu il numero massimo di thread non è il problema qui, __syncthreadsè un'operazione a livello di blocco e il fatto che non sincronizzi effettivamente tutti i thread è un fastidio per gli studenti CUDA. Quindi ho aggiornato la mia risposta in base alle informazioni che mi hai fornito. Lo apprezzo molto.
Bizhan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.