Cómo funcionan los subprocesos CUDA

Tengo muchas dudas sobre la forma en que se forman y ejecutan los hilos.

En primer lugar, la documentación describe los subprocesos de la GPU como subprocesos ligeros. Supongamos que deseo multiplicar dos 100*100 matrices. Esto requeriría 100*100 subprocesos si cada elemento fue calculado por un subproceso diferente. Sin embargo, las especificaciones de mi GPU (NVIDIA GT 640M LE) muestran dos SM, cada uno de los cuales puede admitir solo 2048 subprocesos. ¿Cómo es posible calcular el resto de los elementos en paralelo dado que mi GPU no puede soportar tantos hilos?

Considere también el código básico de adición de vectores. Supongamos que invoco un kernel con 1 bloque y 64 subprocesos para agregar dos matrices de 100 elementos cada una de la siguiente manera:

    __global__ void add(int* a,int* b,int* c)
    {
        int i = threadIdx.x;
        for(i<100)
        {
            c[i] = a[i] + b[i];
        {    
     }

Dado que solo se inicializaron 64 subprocesos, supongo que se agregan 64 elementos en paralelo.

  • ¿Cómo se agregan los elementos restantes?
  • ¿Cómo decide el programador warp qué subprocesos asignar para agregar los últimos 36 elementos?

Mi principal problema es:

No entiendo cómo un hilo sabe en qué elementos operar.

preguntado el 12 de febrero de 14 a las 07:02

3 Respuestas

Su tarjeta tiene capacidad de cómputo 3.0, consulte nuestra página, aquí.

De la Tabla 12 de la Guía de programación de CUDA C, el número de 2048 los subprocesos que menciona para su capacidad informática se refieren al número máximo de subprocesos residentes por multiprocesador. Esto no quiere decir que no puedas lanzar más de 2048 hilos en general. Por ejemplo, desde unas pocas filas arriba de esa tabla, puede leer que el máximo máximo x-dimensión de una cuadrícula de bloques de hilos es 2^31-1. Esto significa que es perfectamente legal lanzar, por ejemplo, un 1d rejilla de hilos de, por ejemplo, 8192 hilos. La razón es que la tarjeta realizará un cambio de contexto entre deformaciones de hilo como se indica en esta publicación: ¿Cuál es el mecanismo de cambio de contexto en la GPU?.

Con respecto a la segunda parte de su pregunta, su implementación de la add la función es conceptualmente incorrecta. Estás usando el índice i como índice de subprocesos y como for índice de bucle Una implementación más correcta es la siguiente

__global__ void add(int* a,int* b,int* c)
{
    int i = threadIdx.x;
    c[i] = a[i] + b[i];
}

La escritura anterior significa lo siguiente: cada subproceso ejecutará las dos asignaciones, a saber

    int i = threadIdx.x;
    c[i] = a[i] + b[i];

Ahora, por ejemplo, para hilo #3 el valor de la threadIdx.x la variable será 3. Así, hilo #3 tratará con una variable local i, privado a su espacio de memoria, cuyo valor será asignado a 3. Además, se cargará a[3] y b[3] de la memoria global, sumarlos, asignar el resultado a c[3] y luego almacenar el resultado final en la memoria global. En consecuencia, cuando inicia la cuadrícula, por supuesto, no puede llenar toda la matriz de 100 elementos por solo 64 hilos y necesitarás 100 roscas.

Tenga en cuenta que la explicación anterior está demasiado simplificada. Te recomiendo leer algún libro de texto básico como el famoso CUDA By Example.

contestado el 23 de mayo de 17 a las 11:05

Gracias por la respuesta. Entendí la parte sobre el lanzamiento de muchos hilos. - mastercheif141

Pasando a la segunda parte, todavía no entiendo cómo se agregan los elementos restantes. Dado que solo se lanzaron 64 subprocesos, solo dos deformaciones están presentes en el bloque. Después de agregar los 64 elementos, ¿se agregarán los elementos restantes? Si es así, ¿por qué subprocesos? - mastercheif141

@ mastercheif141 Intenté explicar mejor su segunda pregunta. Ver la respuesta editada. - Vitalidad

Le dará una ilustración del programa de suma de matrices 4*4 en CUDA. Podría darle una idea de cómo se inician y operan los subprocesos.

int main()
    {
     int *a, *b, *c;            //To store your matrix A & B in RAM. Result will be stored in matrix C
     int *ad, *bd, *cd;         // To store matrices into GPU's RAM. 
     int N =16;   

          //No of rows and columns.

 size_t size=sizeof(float)* N * N;

 a=(float*)malloc(size);     //Allocate space of RAM for matrix A
 b=(float*)malloc(size);     //Allocate space of RAM for matrix B

//allocate memory on device
  cudaMalloc(&ad,size);
  cudaMalloc(&bd,size);
  cudaMalloc(&cd,size);

//initialize host memory with its own indices
    for(i=0;i<N;i++)
      {
    for(j=0;j<N;j++)
         {
            a[i * N + j]=(float)(i * N + j);
            b[i * N + j]= -(float)(i * N + j);
         }
      }

//copy data from host memory to device memory
     cudaMemcpy(ad, a, size, cudaMemcpyHostToDevice);
     cudaMemcpy(bd, b, size, cudaMemcpyHostToDevice);

//calculate execution configuration 
   dim3 grid (1, 1, 1); 
   dim3 block (16, 1, 1);

//each block contains N * N threads, each thread calculates 1 data element

    add_matrices<<<grid, block>>>(ad, bd, cd, N);

   cudaMemcpy(c,cd,size,cudaMemcpyDeviceToHost);  
   printf("Matrix A was---\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",a[i*N+j]);
        printf("\n");
    }

   printf("\nMatrix B was---\n");
   for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",b[i*N+j]);
        printf("\n");
    }

    printf("\nAddition of A and B gives C----\n");
    for(i=0;i<N;i++)
    {
        for(j=0;j<N;j++)
            printf("%f ",c[i*N+j]);   //if correctly evaluated, all values will be 0
        printf("\n");
    }



    //deallocate host and device memories
    cudaFree(ad); 
    cudaFree(bd); 
    cudaFree (cd);

    free(a);
    free(b);
    free(c);

    getch();
    return 1;
}

/////Kernel Part

__global__ void add_matrices(float *ad,float *bd,float *cd,int N)
{
  int index;
  index = blockIDx.x * blockDim.x + threadIDx.x            

  cd[index] = ad[index] + bd[index];
}

Tomemos un ejemplo de suma de matrices de 16*16... tienes dos matrices A y B, que tienen una dimensión de 16*16...

En primer lugar, debe decidir la configuración de su hilo. Se supone que debe iniciar una función del núcleo, que realizará el cálculo paralelo de la adición de su matriz, que se ejecutará en su dispositivo GPU.

Ahora, se inicia una cuadrícula con una función de kernel. Una cuadrícula puede tener un máximo de 65,535 bloques que se pueden organizar en formas tridimensionales. (3 * 65535 * 65535).

Cada bloque en la cuadrícula puede tener un máximo de 1024 hilos. Esos hilos también se pueden organizar en formas tridimensionales (3 * 1024 * 1024)

Ahora nuestro problema es la suma de matrices de 16 * 16.

A | 1  2  3  4 |        B | 1  2  3  4 |      C| 1  2  3  4 |
  | 5  6  7  8 |   +      | 5  6  7  8 |   =   | 5  6  7  8 | 
  | 9 10 11 12 |          | 9 10 11 12 |       | 9 10 11 12 |  
  | 13 14 15 16|          | 13 14 15 16|       | 13 14 15 16|

Necesitamos 16 hilos para realizar el cálculo.

i.e. A(1,1) + B (1,1) = C(1,1)
     A(1,2) + B (1,2) = C(1,2) 
     .        .          .
     .        .          . 
     A(4,4) + B (4,4) = C(4,4) 

Todos estos hilos se ejecutarán simultáneamente. Entonces necesitamos un bloque con 16 hilos. Para nuestra comodidad, organizaremos los subprocesos en (16 * 1 * 1) en un bloque. Como el número de subprocesos es 16, necesitamos un bloque solo para almacenar esos 16 subprocesos.

entonces, la configuración de la red será dim3 Grid(1,1,1) es decir, la red tendrá un solo bloque y la configuración del bloque será dim3 block(16,1,1) es decir, el bloque tendrá 16 subprocesos dispuestos en columnas.

El siguiente programa le dará una idea clara sobre su ejecución. Comprender la parte de indexación (es decir, threadIDs, blockDim, blockID) es la parte importante. Debe revisar la literatura de CUDA. Una vez que tenga una idea clara sobre la indexación, ¡ganará la mitad de la batalla! Así que pasa un tiempo con los libros cuda... :-)

Respondido 12 Feb 14, 13:02

porque está muy mal aquí: algunos subprocesos con threadid <100 se ejecutarían antes. Para los novatos, podría explicarse de esta manera: el threadid está predefinido por el valor del sistema, lo que muestra el número de hilo actual. El hilo actual toma su valor de a, de b y lo escribe en c para que sea

int i = threadIdx.x;
c[i] = a[i] + b[i];

Si tiene un tamaño de matriz de 100 que no coincide con el tamaño de bloque de 64x, para que algunos subprocesos no lean/escriban fuera de los límites, haga lo siguiente:

int i = threadIdx.x;
    if(i < 100){

        c[i] = a[i] + b[i];
    }

Tendrá divergencia solo en el último bloque. Probablemente querías eso

Respondido el 15 de enero de 16 a las 19:01

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.