download - Cuda gridDim y blockDim




toolkit gpu (4)

Obtengo lo que es BlockDim ... Pero tengo un problema con gridDim. Blockdim da el tamaño del bloque, pero ¿qué es gridDim? En Internet dice gridDim.x da la cantidad de bloques en la coordenada x.

¿Cómo puedo saber qué proporciona blockDim.x * gridDim.x ?

¿Cómo puedo saber cuántos valores de gridDim.x hay en la línea x?

Por ejemplo, considere el siguiente código:

int tid = threadIdx.x + blockIdx.x * blockDim.x;
int cacheIndex = threadIdx.x;
double temp = a[tid];
tid += blockDim.x * gridDim.x;
while (tid < count) {
if (a[tid] > temp)
temp = a[tid];
tid += blockDim.x * gridDim.x;
}

Sé que tid comienza con 0. El código tiene tid+=blockDim.x * gridDim.x . ¿Qué es tid ahora después de esta operación?


Answers

Parafraseado de la Guía de Programación de CUDA :

gridDim: esta variable contiene las dimensiones de la grilla.

blockIdx: esta variable contiene el índice de bloque dentro de la grilla.

blockDim: esta variable y contiene las dimensiones del bloque.

threadIdx: esta variable contiene el índice de subprocesos dentro del bloque.

Parece que estás un poco confundido acerca de la jerarquía de hilos que tiene CUDA; en pocas palabras, para un núcleo habrá 1 cuadrícula, (que siempre visualizo como un cubo tridimensional). Cada uno de sus elementos es un bloque, de modo que una grilla se declara como dim3 grid(10, 10, 2); tendría 10 * 10 * 2 bloques en total. A su vez, cada bloque es un cubo tridimensional de hilos.

Dicho esto, es común usar solo la dimensión x de los bloques y las cuadrículas, que es lo que parece que está haciendo el código de tu pregunta. Esto es especialmente importante si está trabajando con matrices 1D. En ese caso, su línea tid+=blockDim.x * gridDim.x en efecto sería el índice único de cada subproceso dentro de su grilla. Esto es porque tu blockDim.x sería el tamaño de cada bloque, y tu gridDim.x sería la cantidad total de bloques.

Entonces, si lanzas un kernel con parámetros

dim3 block_dim(128,1,1);
dim3 grid_dim(10,1,1);
kernel<<<grid_dim,block_dim>>>(...);

luego, en su kernel tenía threadIdx.x + blockIdx.x*blockDim.x que efectivamente tendría:

threadIdx.x range from [0 ~ 128)

blockIdx.x range from [0 ~ 10)

blockDim.x equal to 128

gridDim.x equal to 10

Por lo tanto, al calcular threadIdx.x + blockIdx.x*blockDim.x , tendría valores dentro del rango definido por: [0, 128) + 128 * [1, 10) , lo que significaría que sus valores de tid van desde {0 , 1, 2, ..., 1279}. Esto es útil para cuando quiere mapear hilos a tareas, ya que esto proporciona un identificador único para todos sus hilos en su kernel.

Sin embargo, si tiene

int tid = threadIdx.x + blockIdx.x * blockDim.x;
tid += blockDim.x * gridDim.x;

entonces esencialmente tendrás: tid = [0, 128) + 128 * [1, 10) + (128 * 10) , y tus valores de tid van desde {1280, 1281, ..., 2559} Estoy no estoy seguro de dónde sería relevante, pero todo depende de su aplicación y de cómo asigne sus hilos a sus datos. Este mapeo es bastante central para cualquier lanzamiento de kernel, y tú eres quien determina cómo se debe hacer. Cuando ejecuta su kernel, especifica las dimensiones de la cuadrícula y del bloque, y usted es quien debe imponer la asignación a sus datos dentro de su kernel. Siempre y cuando no excedas los límites de tu hardware (para tarjetas modernas, puedes tener un máximo de 2 ^ 10 hilos por bloque y 2 ^ 16 - 1 bloque por hilo)


En este código fuente, incluso tenemos 4 threds, la función kernel puede acceder a las 10 matrices. ¿Cómo?

#define N 10 //(33*1024)

__global__ void add(int *c){
    int tid = threadIdx.x + blockIdx.x * gridDim.x;

    if(tid < N)
        c[tid] = 1;

    while( tid < N)
    {
        c[tid] = 1;
        tid += blockDim.x * gridDim.x;
    }
}

int main(void)
{
    int c[N];
    int *dev_c;
    cudaMalloc( (void**)&dev_c, N*sizeof(int) );

    for(int i=0; i<N; ++i)
    {
        c[i] = -1;
    }

    cudaMemcpy(dev_c, c, N*sizeof(int), cudaMemcpyHostToDevice);

    add<<< 2, 2>>>(dev_c);
    cudaMemcpy(c, dev_c, N*sizeof(int), cudaMemcpyDeviceToHost );

    for(int i=0; i< N; ++i)
    {
        printf("c[%d] = %d \n" ,i, c[i] );
    }

    cudaFree( dev_c );
}

¿Por qué no creamos 10 subprocesos? Ex) add<<<2,5>>> or add<5,2>>> Porque tenemos que crear un número de subprocesos razonablemente pequeño, si N es mayor que 10 ex) 33 * 1024 .

Este código fuente es un ejemplo de este caso. los arrays son 10, los hilos cuda son 4. Cómo acceder a los 10 arrays solo por 4 hilos.

vea la página sobre el significado de threadIdx, blockIdx, blockDim, gridDim en el detalle de cuda.

En este código fuente,

gridDim.x : 2    this means number of block of x

gridDim.y : 1    this means number of block of y

blockDim.x : 2   this means number of thread of x in a block

blockDim.y : 1   this means number of thread of y in a block

Nuestro número de hilos es 4, porque 2 * 2 (bloques * hilo).

En la función agregar núcleo, podemos acceder a 0, 1, 2, 3 índice de hilo

-> tid = threadIdx.x + blockIdx.x * blockDim.x

①0 + 0 * 2 = 0

②1 + 0 * 2 = 1

③0 + 1 * 2 = 2

④1 + 1 * 2 = 3

Cómo acceder al resto del índice 4, 5, 6, 7, 8, 9. Hay un cálculo en while loop

tid += blockDim.x + gridDim.x in while

** primera llamada de kernel **

-1 bucle: 0 + 2 * 2 = 4

-2 bucle: 4 + 2 * 2 = 8

-3 loop: 8 + 2 * 2 = 12 (¡pero este valor es falso, mientras está afuera!)

** segunda llamada de kernel **

-1 bucle: 1 + 2 * 2 = 5

-2 bucle: 5 + 2 * 2 = 9

-3 loop: 9 + 2 * 2 = 13 (¡pero este valor es falso, mientras está afuera!)

** tercera llamada de kernel **

-1 bucle: 2 + 2 * 2 = 6

-2 loop: 6 + 2 * 2 = 10 (¡pero este valor es falso, mientras está afuera!)

** cuarta llamada de kernel **

-1 bucle: 3 + 2 * 2 = 7

-2 bucle: 7 + 2 * 2 = 11 (¡pero este valor es falso, mientras está fuera!)

Entonces, todos los índices de 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 pueden acceder por valor de tid.

consulte esta página. http://study.marearts.com/2015/03/to-process-all-arrays-by-reasonably.html No puedo cargar imágenes, porque son de baja reputación.


  • blockDim.x,y,z da la cantidad de hilos en un bloque, en la dirección particular
  • gridDim.x,y,z da la cantidad de bloques en una grilla, en la dirección particular
  • blockDim.x * gridDim.x da la cantidad de hilos en una grilla (en la dirección x, en este caso)

las variables de bloque y cuadrícula pueden ser de 1, 2 o 3 dimensiones. Es una práctica común cuando se manejan datos 1-D para solo crear bloques y cuadrículas 1-D.

En particular, cuando el total de hilos en la dimensión x (gridDim.x * blockDim.x) es menor que el tamaño de la matriz que deseo procesar, entonces es una práctica común crear un bucle y hacer que la grilla de hilos se mueva a través de toda la matriz En este caso, después de procesar una iteración de bucle, cada hilo debe moverse a la siguiente ubicación no procesada, que viene dada por tid+=blockDim.x*gridDim.x; En efecto, toda la grilla de hilos está saltando a través de la matriz de datos 1-D, un ancho de cuadrícula a la vez. Este tema, a veces llamado un "ciclo de cuadrícula", se analiza con más detalle en este artículo de blog .

Es posible que desee considerar tomar algunos de los seminarios web introductorios de CUDA disponibles en la página webinar de NVIDIA . Por ejemplo, estos 2:

  • Cómputo de GPU usando CUDA C - Una introducción (2010) Una introducción a los conceptos básicos de computación de GPU usando CUDA C. Los conceptos se ilustrarán con tutoriales de ejemplos de código. No se requiere experiencia previa en computación GPU
  • Computación de GPU utilizando CUDA C - Advanced 1 (2010) Técnicas de optimización de primer nivel como la optimización de la memoria global y la utilización del procesador. Los conceptos se ilustrarán usando ejemplos de código real

Serían dos horas bien invertidas, si desea comprender mejor estos conceptos.

El tema general de los bucles de cuadrícula se trata aquí con más detalle.


Me resulta más fácil de leer. Es inmediatamente obvio que el propósito de toda la declaración es establecer el valor de a .

La intención es asignar uno a uno de dos valores, y la sintaxis del operador condicional ternario le permite tener solo un a = en su declaración.

Creo que un estándar si / o todo en una línea es feo (independientemente de para qué se usa).





cuda