Documentos de Académico
Documentos de Profesional
Documentos de Cultura
ETSI Telecomunicación
Universidad de Valladolid
Mario Martínez-Zarzuela
Febrero de 2015
CONTENIDO
• Introducción
• Diferencias entre CPU y GPU
• Desarrollo de aplicaciones para GPU
• Ejemplo: suma de vectores
• Ejercicios: suma de vectores, suma de
matrices
• Conclusiones
1
03/02/2015
INTRODUCCIÓN
2
03/02/2015
3
03/02/2015
GPGPU y hardware
Tesla Tegra
GeForce/Quadro (GPU + ARM)
Tegra
(GPU + ARM)
4
03/02/2015
Rendimiento computacional
y ancho de banda GPU vs. СPU
5
03/02/2015
6
03/02/2015
Evolución de arquitecturas
7
03/02/2015
8
03/02/2015
9
03/02/2015
X1 – NVIDIA drive
Enero de 2015 (CES Las Vegas)
• Potencia superior a la del mayor
supercomputador de hace
15 años
– GPU Maxwell de 256 núcleos
– 8 núcleos de CPU ARM
– Procesamiento de 12 cámaras
60 fps
• NVIDIA DRIVE PX:
– Autonomous drive
– Deep Neural Network
Computer Vision
10
03/02/2015
LENGUAJES Y LIBRERÍAS
DESARROLLO DE APLICACIONES
11
03/02/2015
Aplicaciones
Lenguajes de
Directivas Programación
Librerías
OpenACC (CUDA)
Directivas OpenACC
...
// Perform SAXPY on 1M elements
DRAM I/F
DRAM I/F
L2
Giga Thread
DRAM I/F
DRAM I/F
DRAM I/F
12
03/02/2015
Librería Thrust
#include <thrust/device_vector.h>
#include <thrust/transform.h>
#include <thrust/functional.h>
#include <iostream>
int main(void) {
thrust::device_vector<float> X(3);
thrust::device_vector<float> Y(3);
thrust::device_vector<float> Z(3);
13
03/02/2015
14
03/02/2015
kernel_routine<<<gridDim, blockDim>>>(args,…);
15
03/02/2015
CUDA Kernel
• Función especial, punto de entrada para el código ejecutado
en GPU
• No devuelve nada (void)
• Se declara con el calificador __global__
• Solo puede accede a la memoria GPU
– Excepción: memoria host mapeada
• Sin variables estáticas
• Declaración de parámetros y mismo uso que para funciones
normales
__global__ void kernel (int * ptr) {
ptr = ptr + 1;
ptr[0] = 100;
….; //other code for GPU
}
16
03/02/2015
17
03/02/2015
EJEMPLO
SUMA DE VECTORES
18
03/02/2015
Hilos
…
Vector A ld ld ld ld ld ld ld ld ld ld
Vector B ld ld ld ld ld ld ld ld ld ld
Vector C st st st st st st st st st st
Código en el device
__global__ void sum_kernel(int *A, int *B, int *C)
{
int tidx =
threadIdx.x + blockIdx.x * blockDim.x; //calcular índice
int elemA = A[tidx]; //leer elemento de A
int elemB = B[tidx]; //leer elemento de B
C[tidx] = elemA + elemB; //calcular y escribir elemento en C
}
19
03/02/2015
Código en el host
• 1. …
• 2. …
• 3. …
• 4. Especificar el tamaño del grid y de cada
bloque
• 5. Lanzar el kernel
• 6. …
Código en el host
• 4. Especificar el tamaño del grid y de cada bloque
Depende el tamaño del problema
• 5. Lanzar el kernel
– Supondremos vectores de longitud 1024 elementos
– Tamaño de bloque: 256 (múltiplo de tamaño de warp)
– Tamaño de grid = 1024 / 256 = 4 bloques
20
03/02/2015
Código en el host
• 1. …
• 2. Reservar memoria en la GPU
• 3. Copiar datos de entrada a la GPU
• 4. Especificar el tamaño del grid y de cada
bloque
• 5. Lanzar el kernel
• 6. Copiar los datos de salida de vuelta al host
21
03/02/2015
Transferencia de datos
• cudaError_t cudaMemcpy ( void* dst, const
void* src, size_t count, cudaMemcpyKind kind)
– Copia count bytes de la memoria apuntada por src a la memoria apuntada por dst
– kind especifica la dirección de la transferencia
• cudaMemcpyHostToHost– transferencia de datos entre host
• cudaMemcpyHostToDevice – transferencia de datos de host a device
• cudaMemcpyDeviceToHost – transferencia de datos de device a host
• cudaMemcpyDeviceToDevice – transferencia de datos entre devices
– Una llamada a cudaMemcpy() con kind inconsistente con punteros dst y src, puede
provocar resultados impredecibles
• GPU no sabe si los punteros pertenecen a la CPU o la GPU (excepción: UVA)
Comprobación de Errores
• En donde cudaError_t es una enumeración para control de
errores
– En caso de error el código de error recogido por el sistema no está
necesariamente asociado a la última llamada de biblioteca de CUDA
• Funciones útiles para conocer el último error ocurrido
– cudaError_t cudaPeekAtLastError() – devuelve el último error
ocurrido en cualquiera de las llamadas
– cudaError_t cudaGetLastError() – devuelve el último error ocurrido y
resetea el estado de error a cudaSuccess
– const char* cudaGetErrorString (cudaError_t error) – devuelve la
cadena del mensaje del código de error
• La lista de posibles errores puede consultarse en la documentación
de CUDA
22
03/02/2015
Control de errores
• La mayoría de funciones de biblioteca devuelven en
caso de error una variable de tipo enumeración enum
cudaError_t
Control de errores
• Una macro muy útil para gestión de errores es la siguiente
– Traduce el código de error devuelto a un mensaje de
texto
23
03/02/2015
Código en el host
#include <stdio.h>
#include <cuda_runtime.h>
int main(void)
{
int N = 1024;
size_t size = N * sizeof(float);
Código en el host
//Copiar los vectores de entrada h_A y h_B al dispositivo
CUDA_CALL(cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice));
CUDA_CALL(cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice));
24
03/02/2015
Código en el host
//Comprobar cálculos realizados
for (int i = 0; i < N; ++i)
{
if (fabs(h_A[i] + h_B[i] - h_C[i]) > 1e-5)
{
fprintf(stderr, "Result verification failed at element %d!\n", i);
exit(EXIT_FAILURE);
}
}
printf("Test PASSED\n");
return 0;
}
25
03/02/2015
EJERCICIO
¿Y si la longitud de los vectores no es múltiplo de el tamaño de bloque?
Por ejemplo, bloques de tamaño 512 y vectores de longitud 10E6.
Cambia el código del host:
- Para asegurarte que lanzas exactamente los bloques que son necesarios
Cambia el código del device:
- Para asegurarte de que hay hilos en el último bloque que permanecen en
estado idle.
SUMA DE VECTORES
Código en el host
• ¿Qué ocurre si longitud vectores es N, no
necesariamente múltiplo de 256?
– Parámetros de invocación del kernel genéricos
– Necesario restringir cálculo al dominio de cómputo
– Algunos threads deberán estar en estado de reposo
(idle)
26
03/02/2015
Código en el device
• ¿Qué ocurre si longitud vectores es N, no
necesariamente múltiplo de 256?
– Necesario restringir cálculo al dominio de cómputo
– Algunos threads deberán estar en estado de reposo
(idle)
__global__ void sum_kernel(int *A, int *B, int *C, int N)
{
int tidx =
blockIdx.x * blockDim.x + threadIdx.x; //calcular índice
if(tid<N){
int elemA = A[tidx]; //leer elemento de A
int elemB = B[tidx]; //leer elemento de B
C[tidx] = elemA + elemB; //calcular y escribir elemento en C
}
}
EJERCICIO
Extiende el ejemplo anterior a suma de matrices indexando los threads
dentro del bloque y los bloques dentro del grid usando dos dimensiones
SUMA MATRICES
27
03/02/2015
Conclusiones
• Arquitectura GPU muy distinta de CPU
– Arquitecturas masivamente paralelas
– Incremento ámbitos de aplicación de GPUs
• Aumentar rendimiento aplicaciones paralelas
– Directivas OpenACC / OpenMP
– Librerías ‘drop in’
– Programación CUDA
• Curva más elevada de aprendizaje
• Asequible con conocimientos previos C/C++, Fortran,…
• Programa en CUDA
– Código en el device
• CUDA Kernel
– Código en el host
• Invoca el CUDA Kernel
Información adicional
• Página web de
NVIDIA
– www.nvidia.es/cud
a
– Controladores
– SDK
– IDE Nsight
• Visual Studio
• Eclipse
– Tutoriales y
documentación
28
03/02/2015
Libros
• CUDA by Example by Jason Sanders and Edward Kandrot
• Programming Massively Parallel Processors: A Hands-on
Approach by David Kirk, Wen-mei Hwu (2nd ed.)
• The CUDA Handbook by Nicholas Wilt
• CUDA Application Design and Development by Rob Farber
• CUDA Programming: A Developer's Guide to Parallel
Computing with GPUs by Shane Cook
Mario Martínez-Zarzuela
Febrero 2015
29