¿Cómo se organizan los hilos para ser ejecutados por una GPU?
¿Cómo se organizan los hilos para ser ejecutados por una GPU?
Respuestas:
Si un dispositivo GPU tiene, por ejemplo, 4 unidades de multiprocesamiento, y pueden ejecutar 768 subprocesos cada una: en un momento dado, no más de 4 * 768 subprocesos realmente se ejecutarán en paralelo (si planeó más subprocesos, estarán esperando su turno).
Los hilos están organizados en bloques. Un bloque es ejecutado por una unidad de multiprocesamiento. Los hilos de un bloque pueden identificarse (indexarse) usando 1Dimension (x), 2Dimensions (x, y) o 3Dim indexes (x, y, z) pero en cualquier caso x y z <= 768 para nuestro ejemplo (se aplican otras restricciones) a x, y, z, consulte la guía y la capacidad de su dispositivo).
Obviamente, si necesita más de esos hilos 4 * 768, necesita más de 4 bloques. Los bloques también pueden indexarse en 1D, 2D o 3D. Hay una cola de bloques esperando para ingresar a la GPU (porque, en nuestro ejemplo, la GPU tiene 4 multiprocesadores y solo 4 bloques se ejecutan simultáneamente).
Supongamos que queremos que un hilo procese un píxel (i, j).
Podemos usar bloques de 64 hilos cada uno. Entonces necesitamos 512 * 512/64 = 4096 bloques (para tener hilos de 512x512 = 4096 * 64)
Es común organizar (para facilitar la indexación de la imagen) los hilos en bloques 2D que tienen blockDim = 8 x 8 (los 64 hilos por bloque). Prefiero llamarlo threadsPerBlock.
dim3 threadsPerBlock(8, 8); // 64 threads
y 2D gridDim = 64 x 64 bloques (se necesitan los 4096 bloques). Prefiero llamarlo numBlocks.
dim3 numBlocks(imageWidth/threadsPerBlock.x, /* for instance 512/8 = 64*/
imageHeight/threadsPerBlock.y);
El kernel se inicia así:
myKernel <<<numBlocks,threadsPerBlock>>>( /* params for the kernel function */ );
Finalmente: habrá algo como "una cola de 4096 bloques", donde un bloque está esperando que se le asigne uno de los multiprocesadores de la GPU para ejecutar sus 64 subprocesos.
En el núcleo, el píxel (i, j) que procesará un subproceso se calcula de esta manera:
uint i = (blockIdx.x * blockDim.x) + threadIdx.x;
uint j = (blockIdx.y * blockDim.y) + threadIdx.y;
Supongamos una GPU 9800GT:
https://www.tutorialspoint.com/cuda/cuda_threads.htm
Un bloque no puede tener más subprocesos activos que 512, por __syncthreads
lo tanto , solo puede sincronizar un número limitado de subprocesos. es decir, si ejecuta lo siguiente con 600 hilos:
func1();
__syncthreads();
func2();
__syncthreads();
entonces el kernel debe ejecutarse dos veces y el orden de ejecución será:
Nota:
El punto principal __syncthreads
es una operación de todo el bloque y no sincroniza todos los hilos.
No estoy seguro del número exacto de subprocesos que __syncthreads
pueden sincronizarse, ya que puede crear un bloque con más de 512 subprocesos y dejar que la urdimbre se encargue de la programación. Según tengo entendido, es más exacto decir: func1 se ejecuta al menos para los primeros 512 hilos.
Antes de editar esta respuesta (en 2010) midí 14x8x32 hilos sincronizados usando __syncthreads
.
Le agradecería mucho que alguien vuelva a probar esto para obtener una información más precisa.
__syncthreads
es una operación de todo el bloque y el hecho de que en realidad no sincroniza todos los hilos es una molestia para los estudiantes de CUDA. Así que actualicé mi respuesta en función de la información que me diste. Realmente lo aprecio.