Primeros pasos con CUDA -...

Post on 04-Oct-2018

217 views 0 download

Transcript of Primeros pasos con CUDA -...

Primeros pasos con CUDA

Clase 1

Ejemplo: suma de vectores

● Comencemos con un ejemplo sencillo: suma de vectores.– Sean A, B y C vectores de dimensión N, la suma se define como:

– C = A + B donde Ci = Ai + Bi

– Esta operación suma cada elemento de A y B se almacena en C.

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1

C

c0

+

=

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1

C

c0 c1

+ +

=

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1

C

c0 c1 c2

+ + +

=

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1

C

c0 c1 c2 c3 cN-1

+ + + +

=

Suma de vectores solución I/*

Suma de vectores secuencial

*/

vector_ops.cu

/* Suma de vectores (inplace) */int vector_ops_suma_sec(float *v1, float *v2, int dim){ for (int i = 0; i < dim; i++) { v1[i] = v1[i] + v2[i]; }

return 1;}

vector_ops.cu

Suma de vectores solución I/*

Suma de vectores secuencial

*/

vector_ops.cu

/* Suma de vectores (inplace) */int vector_ops_suma_sec(float *v1, float *v2, int dim){ for (int i = 0; i < dim; i++) { v1[i] = v1[i] + v2[i]; }

return 1;}

Complejidad computacional lineal: O(N) donde N es la dimensión de los arreglos.

vector_ops.cu

Suma de vectores solución I

Tiene orden de complejidad lineal, al aumentar el número de elementos aumenta linealmente la cantidad de operaciones.

Suma de vectores solución I

Tiene orden de complejidad lineal, al aumentar el número de elementos aumenta linealmente la cantidad de operaciones.

Para vectores pequeños la solución es correcta.

Suma de vectores solución I

Tiene orden de complejidad lineal, al aumentar el número de elementos aumenta linealmente la cantidad de operaciones.

Para vectores pequeños la solución es correcta.

Para vectores muy grandes este tipo de solución puede ocasionar penalizaciones en el tiempo de cómputo.

Suma de vectores

● ¿Cómo sería una solución paralela?

Se puede paralelizar la suma → el cálculo de cada elemento del vector resultante es independiente de los demás elementos del vector:

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1…

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1…

Si en vez de un único procesador disponemos P procesadores, dividimos los cálculos entre los P procesadores.

● ¿Cómo sería una solución paralela?

Se puede paralelizar la suma → el cálculo de cada elemento del vector resultante es independiente de los demás elementos del vector:

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1…

C

c0 c1 c2 c3 cN-1

=

Si contamos con N procesadores, realizamos todos los cálculos al mismo tiempo. + + + + + + +

● ¿Cómo sería una solución paralela?

Se puede paralelizar la suma, → el cálculo de cada elemento del vector resultante es independiente de los demás elementos del vector:

c0 c1 c2 c3 cN-1…

=

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1…

C

c0 c1 c2 c3 cN-1…

Si contamos con N procesadores, realizamos todos los cálculos al mismo tiempo. + + + + + + +

De orden lineal a orden constante

● ¿Cómo sería una solución paralela?

Se puede paralelizar la suma, → el cálculo de cada elemento del vector resultante es independiente de los demás elementos del vector:

=

Y se resuelve en paralelo todos losresultados del vector.

Entonces...

Tengo una PC re linda...

Entonces...

Tengo una PC re linda...

Tengo una GPU que me dicen que es re linda...

Entonces...

Tengo una PC re linda...

Tengo una GPU que me dicen que es re linda...

Tengo dos vectores para sumar...

A

B…

Entonces...

Tengo una PC re linda...

Tengo una GPU que me dicen que es re linda...

Tengo dos vectores para sumar...

¿¿Cómo utilizo estas arquitecturas para solucionar

mi problema??

A

B…

CUDA (Compute Unified Device Architecture)

En Noviembre de 2006 NVIDIA introduce CUDA que hace referencia tanto a un compilador como a un conjunto de

herramientas de desarrollo creadas por NVIDIA.

CUDA es una arquitectura de software y hardware que permite a GPUs ejecutar programas escritos en C, C++,

Fortran, DirectCompute y otros lenguajes.

CUDA (Compute Unified Device Architecture)

Un programa CUDA es un programa híbrido: – Código secuencial → se ejecuta en CPU

– Código paralelo → se ejecuta en GPU

CUDA (Compute Unified Device Architecture)

Un programa CUDA es un programa híbrido: – Código secuencial → se ejecuta en CPU

– Código paralelo → se ejecuta en GPU

Código secuencialInicializaciones

Lectura de datos de entrada

Código paralelo

Código secuencialMuestra de resultados

Almacenamiento de resultados

….

….

Modelo SIMD - STMD

CUDA ● Cómo es la parte paralela de la aplicación?

● Un programa CUDA invoca a funciones paralelas

llamadas kernels. En CUDA: Kernel = función.● Un kernel se ejecuta en paralelo a

través threads paralelos.

CUDA ● Cómo es la parte paralela de la aplicación?

El programador decide:

“el kernel A será ejecutado por n threads”

● Un programa CUDA invoca a funciones paralelas

llamadas kernels. En CUDA: Kernel = función.● Un kernel se ejecuta en paralelo a

través threads paralelos.

A

B

C…

+ + + + + + +

=

Con n threads que cada uno sumeun elemento del vector resultante, consigo ejecutar la suma de vectores en un único paso !!

CUDA

● Un programa CUDA invoca a kernels paralelos. ● Un kernel se ejecuta en paralelo a través threads paralelos.

Múltiples threads ejecutandoel mismo kernel.

A

B

C…

+ + + + + + +

=

CUDA

● Tenemos programas CUDA híbridos, que se ejecutan en CPU y GPU.

● Tenemos dos arquitecturas que se conectan mediante un conector PCI-Express (no comparten espacio de direccionamiento)

Suma de vectores

¿Cómo lo hacemos con CUDA? ● Este problema ahora implica:

– Inicialización de arreglos en CPU

– Transferencia de datos CPU → GPU

Suma de vectores

¿Cómo lo hacemos con CUDA? ● Este problema ahora implica:

– Inicialización de arreglos en CPU

– Transferencia de datos CPU → GPU

– Cálculo de la suma en paralelo.

Suma de vectores

¿Cómo lo hacemos con CUDA? ● Este problema ahora implica:

– Inicialización de arreglos en CPU

– Transferencia de datos CPU → GPU

– Cálculo de la suma en paralelo.

– Transferencia de datos GPU → CPU.

Suma de vectores

¿Cómo lo hacemos con CUDA? ● Este problema ahora implica:

– Inicialización de arreglos en CPU

– Transferencia de datos CPU → GPU

– Cálculo en GPU.

– Transferencia de datos GPU → CPU.

Modelo de programación

CUDA

Modelo de programación CUDA

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- Organización y manejo de threads concurrentes.

- Manejo de jerarquía de memorias instaladas en la GPU.

● Jerarquía de threads:– thread,

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- Organización y manejo de threads concurrentes.

thread

● Jerarquía de threads:– thread,

– bloque,

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- organización y manejo de threads concurrentes.

thread

bloque 1, 2 o 3 dimensiones

● Jerarquía de threads:– thread,

– bloque

– grilla.

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- organización y manejo de threads concurrentes.

thread

bloque

grilla

1, 2 o 3 dimensiones

1, 2 o 3 dimensiones

Grid 1

Block (0,0)

Block (2,0)

Block (1,0)

Block (0,0)

Block (1,0)

Grid 2

Block (0,1)

Block (1,1)

grilla

● Memorias: – Privada de cada thread.

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- Manejo de jerarquía de memorias instaladas en la GPU

Memoria localde thread

● Memorias: – Privada de cada thread.

– Compartida por bloque.

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- Manejo de jerarquía de memorias instaladas en la GPU

Memoria localde thread

Memoria compartidade bloque

● Memorias: – Privada de cada thread.

– Compartida por bloque.

– Global de toda la aplicación

CUDA extiende al lenguaje C/C++ incluyendo dos característicasprincipales:

- Manejo de jerarquía de memorias instaladas en la GPU

Grid 1

Block (0,0)

Block (2,0)

Block (1,0)

Block (0,0)

Block (1,0)

Grid 2

Block (0,1)

Block (1,1)

Memoria localde thread

Memoria compartidade bloque

Memoria global

- Manejo de jerarquía de memorias instaladas en la GPU

● CUDA ofrece distintas memorias con distintas características:

– Registros

– Memoria compartida

– Memoria global

– Memoria constante.

● Algunas de ellas están en caché.

● Memoria Global: es la más grande y la más lenta. Puede ser leída y escrita por la CPU y por los threads de GPU. Permite comunicar datos entre CPU y GPU. El patrón de acceso a memoria por los threads puede afectar el rendimiento.

● Memoria Constante: es parte de la memoria global. CPU puede leer y escribir, y es sólo de lectura para GPU threads. Ofrece mayor ancho de banda cuando grupos de threads acceden al mismo dato.

● Memoria compartida: es pequeña y muy rápida y es compartida por todos los threads de un bloque. Es de lectura/escritura por los threads. Puede comunicar datos entre threads del mismo bloque. Puede verse afectada por el patrón de acceso de los threads.

● Registros: cada thread utiliza su propio conjunto de registros. El programador no tiene control explícito de los registros, y son utilizados para la ejecución de programas de la misma forma que los registros de propósito general de CPU.

● Memoria local: es usada por el compilador automáticamente para alojar variables cuando hace falta.

● Memoria de textura: es controlada por el programador y puede beneficiar aplicaciones con localidad espacial donde el acceso a memoria global es un cuello de botella.

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

main.cu

int main(){ /* alocacion de memoria en host */ float *h_A = (float *) malloc(N * sizeof(float)); float *h_B = (float *) malloc(N * sizeof(float)); float *h_aux = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_A, *d_B; cudaMalloc((void**)&d_A, sizeof(float) * N); cudaMalloc((void**)&d_B, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_A || !h_B || !d_A || !d_B || !h_aux) { printf("Error alocando vectores \n"); exit(-1); }

main.cu

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

int main(){ /* alocacion de memoria en host */ float *h_A = (float *) malloc(N * sizeof(float)); float *h_B = (float *) malloc(N * sizeof(float)); float *h_aux = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_A, *d_B; cudaMalloc((void**)&d_A, sizeof(float) * N); cudaMalloc((void**)&d_B, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_A || !h_B || !d_A || !d_B || !h_aux) { printf("Error alocando vectores \n"); exit(-1); }

Alocación de memoria dinámica en host

main.cu

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

int main(){ /* alocacion de memoria en host */ float *h_A = (float *) malloc(N * sizeof(float)); float *h_B = (float *) malloc(N * sizeof(float)); float *h_aux = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_A, *d_B; cudaMalloc((void**)&d_A, sizeof(float) * N); cudaMalloc((void**)&d_B, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_A || !h_B || !d_A || !d_B || !h_aux) { printf("Error alocando vectores \n"); exit(-1); }

Alocación de memoria en device

main.cu

Aloca n bytes en memoria globaldel device los cuales serán apunta-dos por d_A (d_B respectivamente)

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

int main(){ /* alocacion de memoria en host */ float *h_A = (float *) malloc(N * sizeof(float)); float *h_B = (float *) malloc(N * sizeof(float)); float *h_aux = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_A, *d_B; cudaMalloc((void**)&d_A, sizeof(float) * N); cudaMalloc((void**)&d_B, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_A || !h_B || !d_A || !d_B || !h_aux) { printf("Error alocando vectores \n"); exit(-1); }

main.cu

Suma de vectores/*

Continuación del código anterior

*/

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

main.cu

Suma de vectores/*

Continúa código anterior

*/

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

main.cu

Suma de vectores/*

Continúa código anterior

*/

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

main.cu

Suma de vectores/*

Continúa código anterior

*/

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

main.cu

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

Copia datos dehost a device

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

main.cu

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

Copia datos dehost a device

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

main.cu

cudaMemcpy(destino, origen, size, DIRECCION_COPIA)

Copia size bytes desde la dirección origen a la direccióndestino en la memoria global. DIRECCION_COPIA indicael sentido de la copia: - cudaMemcpyHostToHost. - cudaMemcpyHostToDevice. - cudaMemcpyDeviceToHost. - cudaMemcpyDeviceToDevice.

Suma de vectores/*

Main.cu: suma de vectores. Código que se ejecuta en host.

*/

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

/* inicializacion de vectores */ printf("Inicializacion vector A \n"); vector_io_initializeRND(h_A, N); printf("Inicializacion vector B \n"); vector_io_initializeRND(h_B, N);

/* transferencia de datos cpu -> gpu (host -> device) */ cudaMemcpy(d_A, h_A, sizeof(float) * N, cudaMemcpyHostToDevice); cudaMemcpy(d_B, h_B, sizeof(float) * N, cudaMemcpyHostToDevice);

Tenemos los datos alocados en memoria principal de la CPU y en memoria global de GPU.

Estamos listos para operar en device.

main.cu

Suma de vectores/*

Main.cu: continuación del código.

*/

main.cu

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Suma de vectores/*

Main.cu: continuación del código.

*/

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Función secuencial, ya vista

main.cu

Suma de vectores/*

Main.cu: continuación del código.

*/

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Función paralela

main.cu

Suma de vectores/*

Función que invoca al kernel (desde CPU se invoca a kernel (GPU))

*//* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

vector_ops.c

Suma de vectores/*

Función que invoca al kernel (desde CPU se invoca a kernel (GPU))

*/

Configuracióndel grid

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

vector_ops.c

Variables de tipo dim3: vector de 3 enteros que se usa para especificar dimensiones. Componentes x, y, z. Si algún componente no se inicializa → 1.

Suma de vectores/*

Función que invoca al kernel (desde CPU se invoca a kernel (GPU))

*/

Configuracióndel grid

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

vector_ops.c

Variables de tipo dim3: vector de 3 enteros que se usa para especificar dimensiones. Componentes x, y, z. Si algún componente no se inicializa → 1.

nThreads → especificará cantidad de threads por bloque.nBlocks → especificará cantidad de bloques en el grid

Suma de vectores/*

Función que invoca al kernel (desde CPU se invoca a kernel (GPU))

*/

Configuracióndel grid

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

vector_ops.c

Variables de tipo dim3: vector de 3 enteros que se usa para especificar dimensiones. Componentes x, y, z. Si algún componente no se inicializa → 1.

nThreads → especificará cantidad de threads por bloque (bloques de 1, 2 o 3 dimensiones).nBlocks → especificará cantidad de bloques en el grid (grids de 1, 2 o 3 dimensiones)

¿Cómo queda configurado el grid?

● Kernel 1:– dim3 dimGrid(3,2)

– dim3 dimBlock(5,3)

● Kernel 2:– dim3 dimGrid(4,3)

– Dim3 dimBlock(?,?,?)

Suma de vectores/*

Función (se ejecuta en CPU) que invoca al kernel (se ejecuta en GPU)

*/

Lanzamientodel kernel

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

vector_ops.c

Suma de vectores/*

Función (se ejecuta en CPU) que invoca al kernel (se ejecuta en GPU)

*/

Lanzamientodel kernel

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

vector_ops.c

nombre_Kernel<<<número de bloques, threads por bloque>>>(parámetros actuales);

Suma de vectores/*

Función (se ejecuta en CPU) que invoca al kernel (se ejecuta en GPU)

*/

Kernel

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

/* suma de cada elemento del vector */__global__ void kernel_suma(float *v1, float *v2, int dim){ int id = threadIdx.x + (blockIdx.x * blockDim.x);

if (id < dim) { v1[id] = v1[id] + v2[id]; }}

vector_ops.c

Suma de vectores/*

Main.cu: continuación del código.

*/

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

/* suma de cada elemento del vector */__global__ void kernel_suma(float *v1, float *v2, int dim){ int id = threadIdx.x + (blockIdx.x * blockDim.x);

if (id < dim) { v1[id] = v1[id] + v2[id]; }}

vector_ops.c

__global__: calificador de función

Calificadores de funciones:

__global__: determina que es una función kernel, se ejecuta en el dispositivo y sólo puede ser invocada desde el host. Su invocación genera un grid de bloques con número fijo e igual de threads.

__device__ : es una función del dispositivo, se ejecuta en él y sólo puede ser invocada desde un kernel u otra función del dispositivo.

__host__ : determina que es una función del host, o simplemente una función de C tradicional a ejecutarse en host y que puede ser invocada desde host. Por omisión.

Suma de vectores/*

Main.cu: continuación del código.

*/

vector_ops.c

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

/* suma de cada elemento del vector */__global__ void kernel_suma(float *v1, float *v2, int dim){ int id = threadIdx.x + (blockIdx.x * blockDim.x);

if (id < dim) { v1[id] = v1[id] + v2[id]; }}

Variables reservadas:

gridDim: contiene las dimensiones del grid.

blockIdx : contiene el identificador del bloque en un grid.

blockDim: contiene las dimensiones del bloque.

threadIdx: contiene el identificador del thread dentro del bloque.

Todas tienen componentes x,y,z.

Grids y bloques de 1, 2 o 3 dimensiones.

Suma de vectores/*

Main.cu: continuación del código.

*/

vector_ops.c

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

/* suma de cada elemento del vector */__global__ void kernel_suma(float *v1, float *v2, int dim){ int id = threadIdx.x + (blockIdx.x * blockDim.x);

if (id < dim) { v1[id] = v1[id] + v2[id]; }}

Suma de vectores/*

Main.cu: continuación del código.

*/

vector_ops.c

/* Suma de vectores. Resultado queda en el primer argumento */int vector_ops_suma_par(float *v1, float *v2, int dim){ dim3 nThreads(512); dim3 nBlocks((dim / nThreads.x) + (dim % nThreads.x ? 1 : 0));

kernel_suma<<<nBlocks, nThreads>>>(v1, v2, dim);

cudaDeviceSynchronize();

return 1;}

/* suma de cada elemento del vector */__global__ void kernel_suma(float *v1, float *v2, int dim){ int id = threadIdx.x + (blockIdx.x * blockDim.x);

if (id < dim) { v1[id] = v1[id] + v2[id]; }}

Cada thread resuelveun único elemento delvector.

Suma de vectores/*

Main.cu: continuación del código.

*/

main.cu

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Suma de vectores/*

Main.cu: continuación del código.

*/

main.cu

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Suma de vectores/*

Main.cu: continuación del código.

*/

main.cu

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Suma de vectores/*

Main.cu: continuación del código.

*/

main.cu

/* suma secuencial */ printf("Suma secuencial (CPU)\n"); suma_secuencial(h_A, h_B, N);

/* suma paralela */ printf("Suma paralela (GPU) \n"); suma_paralela(d_A, d_B, N);

/* traigo los datos desde GPU a CPU para testear la suma */ cudaMemcpy(h_aux, d_A, sizeof(float) * N, cudaMemcpyDeviceToHost);

if(vector_ops_iguales(h_aux, h_A, N)) printf("Test pasado! \n"); else printf("Test no pasado! \n");

/* liberacion de memoria */ free(h_A); free(h_B); free(h_aux); cudaFree(d_A); cudaFree(d_B);

return 0;}

Suma de vectores

A

a0 a1 a2 a3 aN-1

B

b0 b1 b2 b3 bN-1…

C

c0 c1 c2 c3 cN-1…

+ + + + + + +

De orden lineal a orden constante!!

=

Y se resuelve en paralelo todos losresultados del vector.

th0 th1 th2 th3 thN-1…

Modelo de programación CUDA

No todos los problemas pueden ser resueltos usando placas de tipo GPU.

Los más adecuados son los que aplican la misma secuencia de código a los datos de entrada.

Modelo de programación CUDA

Ganaremos con GPU si:

El algoritmo tiene orden de ejecución cuadrático o superior: compensar el tiempo de transferencia de datos CPU – GPU.

Gran carga de cálculo en cada thread (por lo mismo que el itemanterior).

Poca dependencia de datos. Independencia de cómputo. Puede llevar aacceso a su memoria local o compartida y evita acceder a la global (costosa).

Mínima transferencia de datos CPU-GPU: óptimo: principio y final. Evitar tranferencias intermedias, ya sean para resultados parcialeso datos de entrada intermedios.

No existan secciones críticas: lecturas paralelas a datos, pero no escrituras: necesitamos mecanismos de acceso seguro → secuen-cialización de accesos.

Resumen

Hemos visto: - Alocación de memoria en device.- Transferencia de memoria host ↔ device.- Configuración de grid.- Lanzamiento de kernels.

Todas estas operaciones las ofrece CUDA como una libreríaque extiende al lenguaje C (en este caso).

Probando el código

1) Copiar la carpeta /share/apps/codigos/SUMA-Vectores a la carpeta personal:

[mdenham@gpgpu-fisica ~]$ cp -a /share/apps/codigos/SUMA-Vectores/ .

2) Para compilar: los paquetes de compiladores/ bibliotecas se usan via el comando module. Para compilar con CUDA debemos cargar el módulo:

[mdenham@gpgpu-fisica SUMA-Vectores]$ module load cuda

Entrar a la carpeta local SUMA-Vectores y compilar:

[mdenham@gpgpu-fisica SUMA-Vectores]$ cd SUMA-Vectores[mdenham@gpgpu-fisica SUMA-Vectores]$ make

Probando el código

3) Ejecutar la aplicación: encolamos el ejecutable main usando el scriptsubmit_gpuh.sh:

[mdenham@gpgpu-fisica ~]$ qsub submit_gpuh.sh

4) Para consultar el estado de los trabajos lanzados:

[mdenham@gpgpu-fisica SUMA-Vectores]$ qstat -f [mdenham@gpgpu-fisica SUMA-Vectores]$ qstat -u '*'

5) Verificar resultados: ver los resultados en submit_gpuh.sh.o*****

[mdenham@gpgpu-fisica SUMA-Vectores]$ cat submit_gpuh.sh.o***