Cómo funcionan los subprocesos CUDA
Frecuentes
Visto 1,401 equipos
3
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.
3 Respuestas
6
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
1
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
0
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 cuda or haz tu propia pregunta.
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