0% encontró este documento útil (0 votos)
16 vistas38 páginas

Clase 5 - ProgramacionCUDA - 2025

El documento presenta una introducción a la programación en CUDA para procesadores gráficos (GPUs), destacando su modelo de programación y la ejecución de kernels en múltiples threads. Se explican conceptos clave como la jerarquía de threads, la asignación de memoria, y las funciones intrínsecas necesarias para la sincronización y operaciones matemáticas. Además, se incluyen ejemplos de código para ilustrar la suma de filas en matrices utilizando la programación paralela en CUDA.

Cargado por

Vale Diaz
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
16 vistas38 páginas

Clase 5 - ProgramacionCUDA - 2025

El documento presenta una introducción a la programación en CUDA para procesadores gráficos (GPUs), destacando su modelo de programación y la ejecución de kernels en múltiples threads. Se explican conceptos clave como la jerarquía de threads, la asignación de memoria, y las funciones intrínsecas necesarias para la sincronización y operaciones matemáticas. Además, se incluyen ejemplos de código para ilustrar la suma de filas en matrices utilizando la programación paralela en CUDA.

Cargado por

Vale Diaz
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd

Programación masivamente paralela

en procesadores gráficos (GPUs)

E. Dufrechou , P. Ezzatti y M. Pedemonte

Clase 5– Programación CUDA


Clase 5
Programación CUDA

Clase 5– Programación CUDA


Contenido

• Modelo de programación CUDA


– Introducción
• Programación en CUDA
– Conceptos básicos

Clase 5– Programación CUDA


Modelo de programación CUDA

Clase 5– Programación CUDA


Introducción

El device:

• Es un coprocesador de la CPU (host).


• Posee su propia memoria DRAM (memoria del device) –
lógicamente ya no, hay dispositivos que comparten la memoria
físicamente).
• Ejecuta “muchos” threads en paralelo.
• Generalmente es una GPU, pude ser otro tipo de procesador.

Una porción de los cálculos con paralelismo de datos se computan con


kernels en el device que corren en “muchos” threads.

Clase 5– Programación CUDA


Introducción

Diferencias entre los threads de la GPU y CPU

• Los threads en GPU son extremadamente livianos :

Muy poco overhead para crearlos.


• La GPU necesita 1000s de threads para alcanzar la
eficiencia:
Generalmente las CPU (multi-core ) necesitan unos pocos.

Clase 5– Programación CUDA


Introducción

Se integra código en el host con código en la tarjeta:


• El código del host puede ser secuencial o paralelo.
• En la tarjeta el código es altamente paralelo (SPMT).

Código en el host
(sequencial)

Kernel device (paralelo)


KernelA<<< nBlk, nTid >>>(args); ...

Código en el host
(paralelo)

Kernel device (paralelo)


KernelB<<< nBlk, nTid >>>(args); ...

Clase 5– Programación CUDA


Introducción

Algoritmo básico

1. Instrucciones en el host
2. Se envían los datos del host a la GPU
3. Se procesa en GPU
4. Se recuperan los datos
5. Continúan instrucciones en el host

Clase 5– Programación CUDA


Introducción

Array de threads

• Un kernel es ejecutado por un array de threads


– Todos los threads corren el mismo código (SPMT)
– Cada thread tiene un ID que se puede utilizar para computar
direcciones de memoria y tomar decisiones de control
threadID 0 1 2 3 4 5 6 7


float x = input[threadID];
float y = func(x);
output[threadID] = y;

Clase 5– Programación CUDA


Introducción
Bloques de threads
• Facilita la escalabilidad.
• Permite dividir un array de threads en múltiples bloques:
• Threads en un mismo bloque cooperan via memoria
shared, operaciones atomic y barrier de sincronización.
• Threads en diferentes bloques no pueden cooperar
directamente (operaciones atomic en mem global).
Thread Block 0 Thread Block 1 Thread Block N - 1
threadID 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

… … …
float x = float x = float x =
input[threadID]; input[threadID]; input[threadID];
float y = func(x); float y = func(x); float y = func(x);
output[threadID] = y; output[threadID] = y; output[threadID] = y;
… … …

Clase 5– Programación CUDA


Introducción
Jerarquía de threads

Clase 5– Programación CUDA


Introducción

IDs bloque e IDs de thread


• Cada thread usa el IDs para
decidir sobre que datos
trabajar
– Block ID: 1D, 2D o 3D (3D desde
Fermi)
– Thread ID: 1D, 2D o 3D

• Puede simplificar el
direccionamiento de memoria
– Procesamiento de imágenes.
– Resolución de PDEs (regulares)
en 3D.

Clase 5– Programación CUDA


Introducción
Jerarquía de threads

• Los bloques dentro de un grid tienen que ser


independientes.
• Los bloques de threads son ejecutados en cualquier
orden y en diferentes multiprocesadores.
• En las tarjetas nuevas hay ejecución concurrente de
kernels (en el curso no lo vamos a tener en
cuenta !!!).

Clase 5– Programación CUDA


Programación en CUDA

Clase 5– Programación CUDA


Programación en CUDA

Las funciones
Ejecutable Invocable
en: desde:
__device__ float DeviceFunc() device device
__global__ void KernelFunc() device host
__host__ float HostFunc() host host

• __global__ define funciones “kernel”


• __device__ y __host__ pueden ser utilizados
juntas (con compilación condicional).
• __host__ puede no ponerse.

Clase 5– Programación CUDA


Programación en CUDA

• Para invocar un kernel es necesario determinar “una


configuración de ejecución”.

__global__ void KernelFunc(...);

dim3 DimGrid(100, 50); // 5000 bloques


dim3 DimBlock(4, 8, 8); // 256 threads por block
size_t SharedMemBytes = 64; // 64 bytes de memoria shared
KernelFunc<<< DimGrid, DimBlock, SharedMemBytes >>>(...);

• Las llamadas a kernels son asincrónicas !!


Es necesario utilizar sincronizaciones explícitas.

Clase 5– Programación CUDA


Programación en CUDA
Espacios de declaración de variables

Se dispone de distintos espacios (algunos


unificados) de declaración:
• global
• _device_ residen en la memoria global del device.
• _shared_ reside en la memoria shared del device.
extern _shared_ para allocamiento dinámico.
• _constant_ reside en memoria constante del device.

__device__ float filter[N];


__global__ void convolve (float *image)
__shared__ float region[M];
extern __shared__ float region[];
Clase 5– Programación CUDA
Programación en CUDA

CUDA posee diversas keywords

– threadIdx (uint3) se accede con .x/.y/.z


– blockIdx (uint3) se accede con .x/.y/.z
– blockDim (dim3) se accede con .x/.y/.z
– gridDim (dim3) se accede con .x/.y/.z

region[threadIdx.x] = image[i];

Clase 5– Programación CUDA


Programación en CUDA
Funciones intrínsecas

• cudaDeviceSynchronize(): sincroniza todos los threads.


Ejecuta en el host.
• __syncthreads: permite sincronizar threads del mismo
bloque.
Se invoca dentro de un kernel.
• clock_t clock(): permite medir tiempos en el device.

Funciones matemáticas (más rápidas pero con posible diferencia


en el valor):
__sinf(x), __cosf(x), __log2f(x) …. __powf(x,y)
Funciones atómicas:
atomicAdd(), … atomicMin(), …
Clase 5– Programación CUDA
Programación en CUDA

La API ofrece diversas funciones

• Para manejo de memoria


• Para transferencias
• Para manejo de las ejecuciones (lanzamiento)

// Allocate memoria en GPU


cudaMalloc((void**)& SDEVPTR, bytes)

Clase 5– Programación CUDA


Programación en CUDA

CUDA Device Memory Allocation


• cudaMalloc() Grid

– Reserva espacio en la memoria Block (0, 0) Block (1, 0)

global del device Shared Memory Shared Memory

– Dos parámetros Registers Registers Registers Registers

• Puntero
• Tamaño de la memoria Thread (0, 0) Thread (1, 0) Thread (0, 0) Thread (1, 0)

reservada Host Global


Memory
• cudaFree()
– Libera la memoria asociada a un
puntero en la mem. global del device
La reserva de memoria global se realiza en el host !!!
Clase 5– Programación CUDA
Programación en CUDA

• Código de ejemplo

– Reservar una matriz de 64 * 64 floats (precisión


simple).
TILE_WIDTH = 64;
float* SDEVPTR
int size = TILE_WIDTH *TILE_WIDTH* sizeof(float);

cudaMalloc((void**)& SDEVPTR, size);

…..

cudaFree(SDEVPTR);

Clase 5– Programación CUDA


Programación en CUDA

Transferencias de datos Host-Device


• cudaMemcpy()
– Transferencia de datos
Grid
– 4 parámetros
• Puntero destino Block (0, 0) Block (1, 0)

• Puntero origen Shared Memory Shared Memory

• Número de bytes a copiar Registers Registers Registers Registers


• Tipo de transferencia
– Host to Host Thread (0, 0) Thread (1, 0) Thread (0, 0) Thread (1, 0)

– Host to Device
– Device to Host Host Global
Memory
– Device to Device

• Transferencias asincrónicas
Clase 5– Programación CUDA
Programación en CUDA

• Código de ejemplo

– Transferir una matriz de 64 x 64 floats (precisión


simple)
– cudaMemcpyHostToDevice y cudaMemcpyDeviceToHost
son constantes simbólicas.

cudaMemcpy(SDEVPTR, SHPTR, size, cudaMemcpyHostToDevice);

cudaMemcpy(SHPTR, SDEVPTR, size, cudaMemcpyDeviceToHost);

Clase 5– Programación CUDA


Programación en CUDA
Lanzamiento
• Dimensiones de la grilla (3D): variable de tipo dim3

• Dimensiones de los bloques de threads (3D): variable de tipo


dim3

• Opcionales:
• cantidad de memoria compartida por bloque
• identificador de stream

dim3 gridSize( 16, 16 );


dim3 blockSize( 32, 32 );
kernel<<<gridSize, blockSize, 0, 0>>>( ... );
kernel<<<32, 512>>>( ... );
Clase 5– Programación CUDA
Programación en CUDA
Retomando los ejemplos de programación paralela básica :

• Consideramos que se tiene una matriz con


número grande de filas y se quiere obtener la
suma por filas de la matriz
(Almacenado por filas).

Clase 5– Programación CUDA


Programación en CUDA
• Ejemplo, suma por fila de los elementos de una matriz.
void SumaFilaMatriz(int M, int N, float * Mh, float* Xh){
int size = M * N * sizeof(float), size2 = M*sizeof(float);
float* Md, *Xd;

// Allocate en device
cudaMalloc((void**)&Md, size);
cudaMalloc((void**)&Xd, size2);

// Inicializo matrices en el device


cudaMemcpy(Md, Mh, size, cudaMemcpyHostToDevice);
cudaMemset(Xd,0, size2);

// Invocar el kernel que suma en GPU

// Traer resultado;
cudaMemcpy(Xh, Xd, size2, cudaMemcpyDeviceToHost);

// Free matrices en device


cudaFree(Md); cudaFree(Xd);
}

Clase 5– Programación CUDA


Programación en CUDA
Kernel
// Suma por filas de una matriz
__global__ void SumaFilaMatrizKernel(int N, float* Md, float* Xd){
// Pvalue es usado para el valor intermedio
float Pvalue = 0;
int aux = threadIdx.x*N;
for (int k = 0; k < N; ++k) {
Pvalue = Pvalue + Md[aux+k];
}
Xd[threadIdx.x] = Pvalue;
}

Clase 5– Programación CUDA


Programación en CUDA

Lanzamiento del kernel


// configuración de la ejecución
dim3 tamGrid(1, 1); //Grid dimensión
dim3 tamBlock(M,1,1); //Block dimensión
// lanzamiento del kernel
SumaFilaMatrizKernel<<<tamGrid, tamBlock>>>(N, Md, Xd);

Restricciones:
• Solo se pueden procesar matrices de tantas filas como
threads en un bloque !!!!
• Un solo bloque, un solo multiprocesador computando.

Clase 5– Programación CUDA


Programación en CUDA

Lanzamiento del kernel (2)

// configuración de la ejecución
int bloques = M / 128;
dim3 tamGrid(bloques, 1); //Grid dimensión
dim3 tamBlock(128, 1, 1); //Block dimensión
// lanzamiento del kernel
SumaFilaMatrizKernel<<<tamGrid, tamBlock>>>(N, Md, Xd);

Hay que hacer cambios en el Kernel !!!!

Clase 5– Programación CUDA


Programación en CUDA
Kernel (2)

// Suma por filas de una matriz


__global__ void SumaFilaMatrizKernel(int N, float* Md, float* Xd){

// Pvalue es usado para el valor intermedio


float Pvalue = 0;
int aux = blockIdx.x*blockDim.x + threadIdx.x;
int aux2 = aux*N;
for (int k = 0; k < N; ++k) {
Pvalue = Pvalue + Md[aux2+k];
}
Xd[aux] = Pvalue;
}

Clase 5– Programación CUDA


Programación en CUDA

Más threads …

• Que pasa si la matriz tiene “muchas” columnas y “pocas”


filas ?!

Clase 5– Programación CUDA


Programación en CUDA

Lanzamiento del kernel (3)

// configuración de la ejecución
int chunk = 32; // Se asume N múltiplo de 32

dim3 tamGrid(M, 1); //Grid dimensión


dim3 tamBlock(N/chunk,1,1); //Block dimensión
// lanzamiento del kernel
SumaFilaMatrizKernel<<<tamGrid, tamBlock>>>(N, Md, Xd);

Clase 5– Programación CUDA


Programación en CUDA
Kernel (3)
// Suma por filas de una matriz
__global__ void SumaFilaMatrizKernel(int N, float* Md, float* Xd){
// Pvalue es usado para el valor intermedio
float Pvalue = 0;

int aux = blockIdx.x*N + threadIdx.x*(N/blockDim.x);


int aux2 = aux + (N/blockDim.x);

for (int k = aux; k < aux2; ++k) {


Pvalue = Pvalue + Md[k];
}

Xd[blockIdx.x] = Xd[blockIdx.x] + Pvalue;


}

Clase 5– Programación CUDA


Programación en CUDA

Recordemos ….

__ atomicAdd(float* address, float val);

atomicAdd()funciona a partir de compute capabilities 2.0 !!!

Clase 5– Programación CUDA


Programación en CUDA
Kernel (3)
// Suma por filas de una matriz
__global__ void SumaFilaMatrizKernel(int N, float* Md, float* Xd){
// Pvalue es usado para el valor intermedio
float Pvalue = 0;

int aux = blockIdx.x*N + threadIdx.x*(N/blockDim.x);


int aux2 = aux + (N/blockDim.x);

for (int k = aux; k < aux2; ++k) {


Pvalue = Pvalue + Md[k];
}

atomicAdd(&(Xd[blockIdx.x]), Pvalue);
}

Clase 5– Programación CUDA


Programación en CUDA

Lanzamiento del kernel (4)

Si quiero que un bloque procese más de una fila:

// configuración de la ejecución
int chunk = 32; // Se asume M y N múltiplos de 32
dim3 tamGrid(M/chunk,1); //Grid dimensión
dim3 tamBlock(chunk, N/chunk, 1); //Block dimensión
// lanzamiento del kernel
SumaFilaMatrizKernel<<<tamGrid, tamBlock>>>(M, N, Md, Xd);

Clase 5– Programación CUDA


Programación en CUDA
Kernel (4)
// Suma por filas de una matriz
__global__void SumaFilaMatrizKernel (int M,int N,float*Md,float* Xd){
// Pvalue es usado para el valor intermedio
float Pvalue = 0;

int fila = blockIdx.x*(M/gridDim.x)+threadIdx.x;


int pasos = N/ blockDim.y;
int posIni = fila * N + threadIdx.y * pasos;

for (int k = 0; k < pasos; ++k) {


Pvalue = Pvalue + Md[posIni + k];
}

atomicAdd(&(Xd[fila]), Pvalue);
}

Clase 5– Programación CUDA

También podría gustarte