introducción a cudadada una señal discreta x[n] y un flitro h[n], la convolución se define como:...

Post on 28-Mar-2020

8 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Introducción a CUDA

Ejemplo: convolución 1D

● Dada una señal discreta x[n] y un flitro h[n], la convolución

se define como:

– Considerar un array x de números reales de tamaño N que representa la señal en un dominio discreto y un array h de tamaño M que describe el filtro h, M << N.

La convolución es un operador matemático que transforma dos funciones f y g en una tercera función que en cierto sentido representa la magnitud en la que se superponen f y una versión trasladada e invertida de g

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

h0 h1 hM-1…

…x1 x2xN-1

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

y

y0

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

y

y0

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

y

y0 y1

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

y

y0 y1

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

y

y0 y1 y2

Convolución 1D

● ¿Cómo se calcula la convolución?

x

x0

h

…x1 x2xN-1

* * * * *

y

y0 …y1 y2yN-1

Correlación (o convolución) 1D

/* convolucion en la cpu: requiere dos loops … */void conv_cpu(float *input, float *output, float *filter) {

float temp;for(int j = 0; j < N; j++){

temp=0.0;for(int i = 0; i < M; i++){

temp += filter[i]*input[i+j];}output[j] = temp;

}}

Versión secuencial simple

Ojo...

Correlación (o convolución) 1D

/* convolucion en la cpu: requiere dos loops … */void conv_cpu(float *input, float *output, float *filter) {

float temp;for(int j = 0; j < N; j++){

temp=0.0;for(int i = 0;i < M; i++){

temp += filter[i]*input[i+j];}output[j] = temp;

}}

Versión secuencial simple

Ojo...

Complejidad computacional cuadrática: O(NxM)

Convolución 1D

Tiene orden de complejidad cuadrático, al aumentar el número de elementos aumenta cuadráticamente la cantidad de operaciones.

void conv_gpu(const FLOAT* d_input, FLOAT* d_output, const FLOAT* d_filter) {

}

Convolución 1D

/* convolucion en la cpu: requiere dos loops … */void conv_cpu(const FLOAT* input, FLOAT* output, const FLOAT* filter) {

FLOAT temp;for(int j = 0; j < N;j++){

temp=0.0;for(int i = 0; i < M; i++){

temp += filter[i]*input[i+j];}output[j] = temp;

}}

Versión simple serial

¿ Versión paralela ?

¿....?

Ojo...

Convolución 1D

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

Se puede paralelizar convolución→ el cálculo de cada elemento de la señal resultante es independiente de los demás elementos de la señal:

x

x0

h

…x1 x2xN-1

* * * * *

y

y0 …y1 y2yN-1

Cada sumatoria se puede realizaren paralelo!!

Convolución 1D

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

Se puede paralelizar convolución→ el cálculo de cada elemento de la señal resultante es independiente de los demás elementos de la señal:

x

h

* * * * *

y…

Cada sumatoria se puede realizar

en paralelo!!

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

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

De orden lineal a orden constante

Y se resuelve en paralelo todos losresultados del vector.

Entonces...

Tengo una PC re linda...

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

Tengo una convolución...

¿¿Cómo utilizo estas arquitecturas para solucionar

mi problema??

x

h…

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 - SIMT

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.

x

h

y…

=

Con n threads que cada uno calculeun elemento del vector resultante, consigo ejecutar la convolución muchomás rápido!!

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.

X

h

y…

* * *

=

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)

Convolucion 1D

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

– Inicialización de arreglos en CPU

– Transferencia de datos CPU → GPU

Convolución 1D

¿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 convolución en paralelo.

Convolución 1D

¿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 convolución en paralelo.

– Transferencia de datos GPU → CPU.

Convolución 1D

¿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

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

- Manejo 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 global

Convolución 1D/*

conv.cu: Convolución 1D. Código que se ejecuta en host.

*/

conv.cu

int main(){ /* alocacion de memoria en host */ float *h_input = (float *) malloc((N+M) * sizeof(float)); float *h_filter = (float *) malloc(M * sizeof(float)); float *h_output = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_input, *d_filter, *d_output; cudaMalloc((void**)&d_input, sizeof(float) * (N+M)); cudaMalloc((void**)&d_filter, sizeof(float) * M); cudaMalloc((void**)&d_output, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_input || !h_filter || !h_output || !d_input || !d_filter || !d_output) { printf("Error alocando arreglos \n"); exit(-1); }

Convolución 1D/*

conv.cu: Convolución 1D. Código que se ejecuta en host.

*/

conv.cu

int main(){ /* alocacion de memoria en host */ float *h_input = (float *) malloc((N+M) * sizeof(float)); float *h_filter = (float *) malloc(M * sizeof(float)); float *h_output = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_input, *d_filter, *d_output; cudaMalloc((void**)&d_input, sizeof(float) * (N+M)); cudaMalloc((void**)&d_filter, sizeof(float) * M); cudaMalloc((void**)&d_output, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_input || !h_filter || !h_output || !d_input || !d_filter || !d_output) { printf("Error alocando arreglos \n"); exit(-1); }

Alocación de memoria dinámica en host

Convolución 1D/*

conv.cu: Convolución 1D. Código que se ejecuta en host.

*/

conv.cu

int main(){ /* alocacion de memoria en host */ float *h_input = (float *) malloc((N+M) * sizeof(float)); float *h_filter = (float *) malloc(M * sizeof(float)); float *h_output = (float *) malloc(N) * sizeof(float));

/* alocacion de memoria en device */ float *d_input, *d_filter, *d_output; cudaMalloc((void**)&d_input, sizeof(float) * (N+M)); cudaMalloc((void**)&d_filter, sizeof(float) * M); cudaMalloc((void**)&d_output, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_input || !h_filter || !h_output || !d_input || !d_filter || !d_output) { printf("Error alocando arreglos \n"); exit(-1); }

Alocación de memoria dinámica en device

Alocación en memoria globaldel device. Arreglos apuntadospor d_input, d_output y d_filter (punteros en host).

Convolución 1D/*

conv.cu: Convolución 1D. Código que se ejecuta en host.

*/

conv.cu

int main(){ /* alocacion de memoria en host */ float *h_input = (float *) malloc((N+M) * sizeof(float)); float *h_filter = (float *) malloc(M * sizeof(float)); float *h_output = (float *) malloc(N * sizeof(float));

/* alocacion de memoria en device */ float *d_input, *d_filter, *d_output; cudaMalloc((void**)&d_input, sizeof(float) * (N+M)); cudaMalloc((void**)&d_filter, sizeof(float) * M); cudaMalloc((void**)&d_output, sizeof(float) * N);

/* chequeo de alocacion de memoria */ if (!h_input || !h_filter || !h_output || !d_input || !d_filter || !d_output) { printf("Error alocando arreglos \n"); exit(-1); }

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < N+M ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

Copia datos dehost a device

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

Copia datos dehost a device

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.

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

CPU

Memoriaprincipal

GPU

Memoriaglobal

I/O

Copia datos dehost a device

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

Estamos listos para operar en

device.

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

conv_par<<<gridSize, blockSize>>>(d_input, d_output, d_filter);

cudaDeviceSynchronize();

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

conv_par<<<gridSize, blockSize>>>(d_input, d_output, d_filter);

cudaDeviceSynchronize();

Configuracióndel grid

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device*/cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

Configuracióndel grid

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.

blockSize → especificará cantidad de threads por bloque (bloques de 1, 2 o 3 dimensiones).gridSize → 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(?,?,?)

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

conv_par<<<gridSize, blockSize>>>(d_input, d_output, d_filter);

cudaDeviceSynchronize();

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

conv_par<<<gridSize, blockSize>>>(d_input, d_output, d_filter);

cudaDeviceSynchronize();

Lanzamientodel kernel

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Inicializacíon del filtro */SetupFilter(h_filter, M, 0);

/* Inicilizacion (padded periodico) array de entrada con datos random */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copia senial al device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copia filtro al device */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

conv_par<<<gridSize, blockSize>>>(d_input, d_output, d_filter);

cudaDeviceSynchronize();

Lanzamientodel kernel

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

Convolución 1D/*

Función Kernel

*/

conv.cu

/* convolucion usando indexado unidimensional de threads/blocks un thread por cada elemento del output todo en memoria global lanzamiento: la grilla se puede elegir independiente de N */

__global__ void conv_par(float *input, float *output, float *filter) { int j = blockIdx.x * blockDim.x + threadIdx.x;

float temp; while(j<N) { temp=0.0; for(int i=0;i<M;i++){ temp += filter[i]*input[i+j]; } output[j]=temp; j+=gridDim.x*blockDim.x; } }

Convolución 1D/*

Función Kernel

*/

conv.cu

/* convolucion usando indexado unidimensional de threads/blocks un thread por cada elemento del output todo en memoria global lanzamiento: la grilla se puede elegir independiente de N */

__global__ void conv_par(float *input, float *output, float *filter) { int j = blockIdx.x * blockDim.x + threadIdx.x;

float temp; while(j<N) { temp=0.0; for(int i=0;i<M;i++){ temp += filter[i]*input[i+j]; } output[j]=temp; j+=gridDim.x*blockDim.x; } }

Convolución 1D/*

Función Kernel

*/

conv.cu

/* convolucion usando indexado unidimensional de threads/blocks un thread por cada elemento del output todo en memoria global lanzamiento: la grilla se puede elegir independiente de N */

__global__ void conv_par(float *input, float *output, float *filter) { int j = blockIdx.x * blockDim.x + threadIdx.x;

float temp; while(j<N) { temp=0.0; for(int i=0;i<M;i++){ temp += filter[i]*input[i+j]; } output[j]=temp; j+=gridDim.x*blockDim.x; } }

__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.

Convolución 1D/*

Función Kernel

*/

conv.cu

/* convolucion usando indexado unidimensional de threads/blocks un thread por cada elemento del output todo en memoria global lanzamiento: la grilla se puede elegir independiente de N */

__global__ void conv_par(float *input, float *output, float *filter) { int j = blockIdx.x * blockDim.x + threadIdx.x;

float temp; while(j<N) { temp=0.0; for(int i=0;i<M;i++){ temp += filter[i]*input[i+j]; } output[j]=temp; j+=gridDim.x*blockDim.x; } }

__global__: calificador de función

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.

Convolución 1D/*

Función Kernel

*/

conv.cu

/* convolucion usando indexado unidimensional de threads/blocks un thread por cada elemento del output todo en memoria global lanzamiento: la grilla se puede elegir independiente de N */

__global__ void conv_par(float *input, float *output, float *filter) { int j = blockIdx.x * blockDim.x + threadIdx.x;

float temp; while(j<N) { temp=0.0; for(int i=0;i<M;i++){ temp += filter[i]*input[i+j]; } output[j]=temp; j+=gridDim.x*blockDim.x; } }

Cada thread resuelve uno o varioselementos de la salida (si N > cantidad de threads en la grilla 1D(gridDim.x * blockDim.x)

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Setup the filter */SetupFilter(h_filter, M, 0);

/* Fill (padded periodico) input array with random data */for(int i = 0 ; i < N ; i++) h_input[i] = (float)(rand() % 100); for(int i = N ; i < (N+M) ; i++) h_input[i] = h_input[i-N];

/* Copy input array to device */cudaMemcpy(d_input, h_input, (N+M) * sizeof(float), cudaMemcpyHostToDevice);

/* Copy the filter to the GPU */cudaMemcpy(d_filter, h_filter, M * sizeof(float), cudaMemcpyHostToDevice);

dim3 blockSize(512); dim3 gridSize((N / blockSize.x) + (N % blockSize.x ? 1 : 0));

conv_par<<<gridSize, blockSize>>>(d_input, d_output, d_filter);

cudaDeviceSynchronize(); Barrera hasta que terminande ejecutar todos los threads del kernel en GPU

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

/* Copia resultado al host */ cudaMemcpy(h_output, d_output, N * sizeof(float), cudaMemcpyDeviceToHost);

/* Free memory on host */ free(h_input); free(h_output); free(h_filter);

/* Free memory on device */ cudaFree(d_input); cudaFree(d_output); cudaFree(d_filter);

}

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

Copia de resultado GPU → CPU

/* Copia resultado al host */ cudaMemcpy(h_output, d_output, N * sizeof(FLOAT), cudaMemcpyDeviceToHost);

/* Free memory on host */ free(h_input); free(h_output); free(h_filter);

/* Free memory on device */ cudaFree(d_input); cudaFree(d_output); cudaFree(d_filter);

}

Convolución 1D/*

Continuación del código anterior

*/

conv.cu

Liberación de memoria en CPU y GPU

/* Copia resultado al host */ cudaMemcpy(h_output, d_output, N * sizeof(FLOAT), cudaMemcpyDeviceToHost);

/* Free memory on host */ free(h_input); free(h_output); free(h_filter);

/* Free memory on device */ cudaFree(d_input); cudaFree(d_output); cudaFree(d_filter);

}

Convolución 1D

De orden cuadrático a orden N/p (donde p es la cantidad de cores)

Y se resuelve en paralelo todos losresultados del vector.

th0 th1 th2 th3 thN-1…

x

h

* * * * *

y

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/alumnos_icnpg2016/clase_01/convolucion a la carpeta personal:

[mdenham@gpgpu-fisica ~]$ cp -a /share/apps/codigos/alumnos_icnpg2016/Clase_01/convolucion/ .

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 ~]$ module load cuda

Entrar a la carpeta local Convolucion y compilar:

[mdenham@gpgpu-fisica ~]$ cd Convolucion[mdenham@gpgpu-fisica Convolucion]$ nvcc convolucion.cu -o convolucion

Probando el código

3) Ejecutar la aplicación: encolamos el ejecutable convolucion usandoel script submit_gpuh.sh:

[mdenham@gpgpu-fisica Convolucion]$ qsub submit_gpu.sh

4) Para consultar el estado de los trabajos lanzados:

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

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

[mdenham@gpgpu-fisica Convolucion]$ cat submit_gpu.sh.o***

top related