19
Recursos avanzados de CUDA Manuel Ujaldón Catedrático de Arquitectura de Computadores @ Universidad de Málaga CUDA Fellow @ Nvidia Corporation Curso de Extensión Universitaria Titulaciones Propias. Universidad de Málaga. Curso 2016/17 Agradecimientos 2 Al personal de Nvidia, por compartir conmigo ideas, material, diagramas, presentaciones, ... Stephen Jones [Kepler]. Mark Ebersole [Kepler, Maxwell]. Mark Harris [Maxwell, Volta]. Contenidos [60 diapositivas] 1. La caché de sólo lectura (Kepler+) [4 diapositivas] 2. Paralelismo dinámico (Kepler+). [17] 1. Ejecución dependiente de los datos. [2] 2. Algoritmos paralelos recursivos. [4] 3. Llamadas a librerías desde los kernels. [3] 4. Simplificar la división CPU/GPU. [2] 3. Hyper-Q (Kepler+). [6] 4. GPU Boost (Kepler+). [6] 5. Memoria unificada (Maxwell+). [19] 1. Ejemplos de programación. [9] 2. Resumen. [1] 3. La hoja de ruta. [1] 6. Planificación independiente de hilos (Volta+). [7] 3 1. La caché de sólo lectura (Kepler+)

Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

  • Upload
    others

  • View
    4

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Recursos avanzados de CUDA

Manuel UjaldónCatedrático de Arquitectura de Computadores @ Universidad de MálagaCUDA Fellow @ Nvidia Corporation

Curso de Extensión UniversitariaTitulaciones Propias. Universidad de Málaga. Curso 2016/17

Agradecimientos

2

Al personal de Nvidia, por compartir conmigo ideas, material, diagramas, presentaciones, ...

Stephen Jones [Kepler].Mark Ebersole [Kepler, Maxwell]. Mark Harris [Maxwell, Volta].

Contenidos [60 diapositivas]

1. La caché de sólo lectura (Kepler+) [4 diapositivas]2. Paralelismo dinámico (Kepler+). [17]

1. Ejecución dependiente de los datos. [2]2. Algoritmos paralelos recursivos. [4]3. Llamadas a librerías desde los kernels. [3] 4. Simplificar la división CPU/GPU. [2]

3. Hyper-Q (Kepler+). [6]4. GPU Boost (Kepler+). [6]5. Memoria unificada (Maxwell+). [19]

1. Ejemplos de programación. [9]2. Resumen. [1]3. La hoja de ruta. [1]

6. Planificación independiente de hilos (Volta+). [7]

3

1. La caché de sólo lectura (Kepler+)

Page 2: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Diferencias en la jerarquía de memoria:Fermi vs. Kepler

5

Motivación para usar la nueva caché de datos

48 Kbytes extra para expandir el tamaño de la caché L1. Posee el mayor ancho de banda en caso de fallo a caché.Usa la caché de texturas, pero de forma transparente al

programador, y elimina el tiempo de configuración de ésta.Permite que una dirección global pueda buscarse y

ubicarse en esta caché, utilizando para ello un camino separado del que accede a caché L1 y memoria compartida.

Es flexible, no requiere que los accesos estén alineados.Gestionada automáticamente por el compilador.

6

Declarar los punteros con el prefijo "const __restrict__".El compilador automáticamente mapeará la carga de esos

valores en la caché para usar el nuevo camino de datos a través de la memoria de texturas.

__global__ void saxpy(float x, float y, const float * __restrict__ input, float * output){ size_t offset = threadIdx.x + (blockIdx.x * blockDim.x);

// El compilador utilizará la nueva caché para "input" output[offset] = (input[offset] * x) + y;}

Cómo utilizar la nueva caché de datos

7

Comparativa con la memoria de constantes

8

A comparar Memoria de constantes Caché de datos de sólo lectura

Disponibilidad

Tamaño

Implementación hardware

Acceso

Mejor rasgo

Peor rasgo

Mejor escenario de uso

Desde CUDA Compute Capability 1.0A partir de CCC 3.5

(aunque desde CCC 1.0 se podía usar la memoria de texturas manualmente)

64 Kbytes 48 Kbytes

Una partición de la memoria global (DRAM)

Caché de texturas que expande la L1 (SRAM)

A través de una caché de 8 Kbytesque posee cada multiprocesador

Mediante un camino aparte en el cauce de segmentación gráfico

Latencia muy baja Gran ancho de banda

Menor ancho de banda Mayor latencia

Acceso con el mismo coeficiente (sin usar threadIdx) a un pequeño

conjunto de datos de sólo lectura

Cuando el kernel es memory-bound, aún después de haber saturado el ancho

de banda con memoria compartida

Page 3: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

2. Paralelismo dinámico (Kepler+)

La habilidad para lanzar nuevos procesos (mallas de bloques de hilos) desde la GPU de forma:

Dinámica: Basándonos en datos obtenidos en tiempo de ejecución.Simultánea: Desde múltiples hilos a la vez.Independiente: Cada hilo puede lanzar una malla diferente.

¿Qué es el paralelismo dinámico?

10

Fermi: Sólo la CPU puede generar trabajo en GPU.

Kepler: La GPU puede generar trabajo por sí sola.

CPU GPU CPU GPU

Así se hacían las cosas en la era pre-Kepler:La GPU era un mero esclavo del host o CPU

Gran ancho de banda en las comunicaciones:Externas: Superior a 10 GB/s (PCI-express 3).Internas: Superior a 100 GB/s (memoria de vídeo GDDR5 y anchura

de bus en torno a 384 bits, que es como un séxtuple canal en CPU).

11

Operación 1 Operación 2 Operación 3

Init

Alloc

Función Lib Lib Función Función

CPU

GPU

12

CPU GPU CPU GPU

Antes la GPU era un co-procesador

Ahora los programas van más rápido y

Y así se pueden hacer con Kepler:Las GPUs lanzan sus propios kernels

Con Kepler, la GPU es más autónoma: Entra en escena el paralelismo dinámico

se expresan de una forma más natural.

Page 4: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Ejemplo 1: Generación dinámica de la carga

Asigna los recursos dinámicamente según se vaya requiriendo precisión en tiempo real, lo que facilita la computación de aplicaciones irregulares en GPU.

Amplía el ámbito de aplicación donde puede ser útil.

13

Malla gruesa Malla fina Malla dinámica

Rendimiento elevado,precisión baja

Sacrifica rendimiento sólodonde se requiere precisión

Rendimiento bajo,precisión elevada

Ejemplo 2: Desplegando paralelismo según el nivel de detalle

14

CUDA hasta 2012:• La CPU lanza kernels de forma regular.• Todos los píxeles se procesan igual.

CUDA sobre Kepler:• La GPU lanza un número diferente de kernels/bloques para cada región computacional.

La potencia computacional se asocia a las regiones

según su interés

A vigilar cuando se use el paralelismo dinámico

Es un mecanismo mucho más potente de lo que aparenta su simplicidad en el código. Sin embargo...

Lo que escribimos dentro de un kernel CUDA se fotocopia para todos los hilos. Por lo tanto, la llamada a un kernel provocará millones de lanzamientos si no va precedida de algún IF que la delimite (por ejemplo, sólo el hilo 0 lanza).

Si un bloque CUDA lanza kernels hijos, ¿pueden éstos utilizar la memoria compartida del padre?

No. Sería fácil de implementar en hardware, pero muy complejo para el programador garantizar que no se producen riesgos por dependencias (condiciones de carrera).

15

2. 1. Ejecución dependiente de los datos

Page 5: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

El programa paralelo más elemental:Los bucles son paralelizables.Conocemos a priori la carga de trabajo.

Paralelismo dependiente del volumen de datos

17

for (i=0; i<N; i++) for (j=0; j<ElementsOnRow[i]; j++) convolution (i, j);

for (i=0; i<N; i++) for (j=0; j<M; j++) convolution (i, j);

Una solución mala: Supercómputo.Una solución peor: Serialización.

max(ElementsOnRow[i])

N

El programa imposible más elemental:Desconocemos la carga de trabajo.El reto es su distribución (partición de datos).

M

N

Lo que hace posible el paralelismo dinámico:Los dos lazos se ejecutan en paralelo

El programa CUDA para Kepler:

18

__global__ void convolution(int ElementosPorFila[]){ for (j=0; j<ElementosPorFila[blockIdx]; j++) // cada bloque lanza kernel <<< ... >>> (blockIdx, j) // ElementosPorFila[.] kernels}

convolution <<< N, 1 >>> (ElementosPorFila); // Lanza N bloques // de un solo hilo (las filas comienzan en paralelo)

N b

loqu

es

ElementosPorFila[blockIdx] llamadas a kernelsIntercambiando estos dos parámetros(y cambiando en el bucle blockIdx por threadIdx), el programa es más rápido, pero no vale para más de 1024 filas (el máximo tamaño del bloque).

2. 2. Algoritmos paralelos recursivos

Algoritmos paralelos recursivos antes de Kepler

Los primeros modelos de programación CUDA no soportaban recursividad de ningún tipo.

CUDA comenzó a soportar funciones recursivas en la versión 3.1, pero podían fallar perfectamente si el tamaño de los argumentos era considerable.

En su lugar, puede utilizarse una pila definida por el usuario en memoria global, pero a costa de una considerable merma en rendimiento.

Gracias al paralelismo dinámico, podemos aspirar a una solución eficiente para GPU.

20

Page 6: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Un ejemplo sencillo de recursividad paralela:Quicksort

Es el típico algoritmo divide y vencerás que cuesta a Fermi La ejecución depende de los datos.Los datos se particionan y ordenan recursivamente.

21

El código CUDA para quicksort

22

Versión ineficiente Versión más eficiente en Kepler_global_ void qsort(int *data, int l, int r){ int pivot = data[0]; int *lptr = data+l, *rptr = data+r; // Particiona datos en torno al pivote partition(data, l, r, lptr, rptr, pivot);

// Lanza la siguiente etapa recursivamente int rx = rptr-data; lx = lptr-data; if (l < rx) qsort<<<...>>>(data,l,rx); if (r > lx) qsort<<<...>>>(data,lx,r);}

_global_ void qsort(int *data, int l, int r){ int pivot = data[0]; int *lptr = data+l, *rptr = data+r; // Particiona datos en torno al pivote partition(data, l, r, lptr, rptr, pivot);

// Utiliza streams para la recursividad cudaStream_t s1, s2; cudaStreamCreateWithFlags(&s1, ...); cudaStreamCreateWithFlags(&s2, ...); int rx = rptr-data; lx = lptr-data; if (l < rx) qsort<<<...,0,s1>>>(data,l,rx); if (r > lx) qsort<<<...,0,s2>>>(data,lx,r);}

Las ordenaciones de la parte derecha e izquierda se serializan

Utiliza "streams" separados para lograr concurrencia

Resultados experimentales para Quicksort

El número de líneas de código se reduce a la mitad.El rendimiento se mejora en un factor 2x.

23

2. 3. Llamadas a librerías desde los kernels

Page 7: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Conceptos básicos del modelo CUDA:Sintaxis y semántica en tiempo de ejecución

25

__device__ float buf[1024];__global__ void dynamic(float *data){ int tid = threadIdx.x; if (tid % 2) buf[tid/2] = data[tid]+data[tid+1]; __syncthreads();

if (tid == 0) { launchkernel<<<128,256>>>(buf); cudaDeviceSynchronize(); } __syncthreads();

if (tid == 0) { cudaMemCpyAsync(data, buf, 1024); cudaDeviceSynchronize(); }}

Este lanzamiento se produce para cada hiloCUDA 5.0+: Espera a que concluyan todos los lanzamientos y llamadas que el bloque hayaefectuado anteriormente.Los hilos sin trabajo esperan al resto aquíCUDA 5.0+: Sólo se permiten lanzamientos asíncronos para la recogida de datos

Un ejemplo de llamada sencilla a una librería utilizando cuBLAS (disponible a partir de CUDA 5.0)

26

__global__ void libraryCall(float *a, float *b, float *c){ // Todos los hilos generan datos createData(a, b); __syncthreads();

// El primer hilo llama a librería if (threadIdx.x == 0) { cublasDgemm(a, b, c); cudaDeviceSynchronize(); }

// Todos los hilos esperan los resultados __syncthreads();

consumeData(c);}

La CPU lanza el kernel

Generación de datos por cada bloque

Llamadasa la librería

externa

Se ejecutala función externa

Uso del resultado

en paralelo

__global__ void libraryCall(float *a, float *b, float *c){ // Todos los hilos generan datos createData(a, b); __syncthreads();

// El primer hilo llama a la librería if (threadIdx.x == 0) { cublasDgemm(a, b, c); cudaDeviceSynchronize(); }

// Todos los hilos esperan los resultados __syncthreads();

consumeData(c);}

La relación padre-hijo en bloques CUDA

27

Ejecución por cada hilo

Una solo llamada a la función de librería externa:- La librería generará el bloque hijo...- ... pero sincronizamos en el bloque padre.

Sincroniza los hilos que han lanzado kernels:- Si no, pueden producirse condiciones de carrera entre el padre y el hijo.

Todos los hilos deben esperar antes de poder usar los datos en paralelo

Los bloques padre e hijo son distintos, así que:- La memoria local y compartida del padre no puede ser utilizada por el hijo.- Hay que copiar valores en memoria global para pasarlos al hijo como argumentos del kernel.

2. 4. Simplificar la división CPU/GPU

Page 8: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Versión para Fermi Versión para Kepler CPU GPUdgetrf(N, N)} { for j=1 to N { for i=1 to 64 { idamax<<<...>>> idamax(); memcpy dswap<<<...>>> dswap(); memcpy dscal<<<...>>> dscal(); dger<<<...>>> dger(); } memcpy dlaswap<<<...>>> dlaswap(); dtrsm<<<...>>> dtrsm(); dgemm<<<...>>> dgemm(); }}

CPU GPUdgetrf(N, N) { dgetrf<<<...>>> dgetrf(N, N) { for j=1 to N { for i=1 to 64 { idamax<<<...>>> dswap<<<...>>> dscal<<<...>>> dger<<<...>>> } dlaswap<<<...>>> dtrsm<<<...>>> dgemm<<<...>>> } } synchronize();}

La CPU está completamente ocupada controlando los lanzamientos a GPU

Una LU por lotes permite liberar a la CPU, que ahora puede acometer otras tareas

Un método directo para la resolución de ecuaciones lineales: La descomposición LU

29

Los beneficios son muy superiores cuando hay que realizar la LU sobre muchas matrices

Trabajo por lotes controlado por la CPU: Serializar las llamadas. Padecer las limitaciones de P-threads (10s).

30

dgetf2 dgetf2 dgetf2

Control de hilos en CPU

Control de hilos en CPU

Control de hilos en CPU

dswap dswap dswap

Control de hilos en CPU

dtrsm dtrsm dtrsm

Control de hilos en CPU

dgemm dgemm dgemm

Trabajo por lotes a través del paralelismo dinámico: Migrar los lazos superiores a la GPU para computar miles de LUs en paralelo.

Control de hilos en CPU

Control de hilos en CPU

dgetf2

dswap

dtrsm

dgemm

Control de hilos en GPU

dgetf2

dswap

dtrsm

dgemm

Control de hilos en GPU

dgetf2

dswap

dtrsm

dgemm

Control de hilos en GPU

3. Hyper-Q (Kepler+)

Hyper-Q

En Fermi, diversos procesos de CPU ya podían enviar sus mallas de bloques de hilos sobre una misma GPU, pero su ejecución concurrente se encontraba severamente limitada por hardware.

En Kepler, pueden ejecutarse simultáneamente hasta 32 kernels procedentes de:

Varios procesos de MPI, hilos de CPU o streams de CUDA.

Esto incrementa el porcentaje de ocupación temporal de la GPU.

32

FERMI1 sola tarea MPI activa

KEPLER32 tareas MPI simultáneas

Page 9: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Un ejemplo: 3 streams, cada uno compuesto de 3 kernels

33

__global__ kernel_A(pars) {body} // Same for B...ZcudaStream_t stream_1, stream_2, stream_3;...cudaStreamCreatewithFlags(&stream_1, ...);cudaStreamCreatewithFlags(&stream_2, ...);cudaStreamCreatewithFlags(&stream_3, ...);...kernel_A <<< dimgridA, dimblockA, 0, stream_1 >>> (pars);kernel_B <<< dimgridB, dimblockB, 0, stream_1 >>> (pars);kernel_C <<< dimgridC, dimblockC, 0, stream_1 >>> (pars);...kernel_P <<< dimgridP, dimblockP, 0, stream_2 >>> (pars);kernel_Q <<< dimgridQ, dimblockQ, 0, stream_2 >>> (pars);kernel_R <<< dimgridR, dimblockR, 0, stream_2 >>> (pars);...kernel_X <<< dimgridX, dimblockX, 0, stream_3 >>> (pars);kernel_Y <<< dimgridY, dimblockY, 0, stream_3 >>> (pars);kernel_Z <<< dimgridZ, dimblockZ, 0, stream_3 >>> (pars);

stre

am

1

stream_1

kernel_A

kernel_B

kernel_C

stream_2

kernel_P

kernel_Q

kernel_R

stream_3

kernel_X

kernel_Y

kernel_Z

stre

am

2st

ream

3

Distribuidor de cargapara los bloques lanzados desde las mallas

16 mallas activas

Colas de streams(colas ordenadas de mallas)

Kernel C

Kernel B

Kernel A

Kernel Z

Kernel Y

Kernel X

Kernel R

Kernel Q

Kernel P

Stream 1 Stream 2 Stream 3

El gestor de kernels/mallas: Fermi vs. Kepler

34

Distribuidor de cargaSe encarga de las mallas activas

32 mallas activas

Cola de streamsC

B

A

R

Q

P

Z

Y

X

Gestor de kernels/mallasMallas pendientes y suspendidas

Miles de mallas pendientes

SMX SMX SMX SMXSM SM SM SM

Fermi Kepler GK110

Carg

a de

tra

bajo

ge

nera

da p

or C

UD

A

Una sola cola hardwaremultiplexa los streams

Hardware paralelo de streams

Permite suspender mallas

Relación entre las colas software y hardware

35

P -- Q -- R

A -- B -- C

X -- Y -- Z

Stream 1

Stream 2

Stream 3

Oportunidad para solapar: Sólo en las fronteras entre streams

A--B--C P--Q--R X--Y--ZEl hardware de la GPU puede albergar hasta 16 mallas en ejecución...

...pero los streams se multiplexan en una cola únicaFermi:

Relación entre las colas software y hardware

36

P -- Q -- R

A -- B -- C

X -- Y -- Z

Stream 1

Stream 2

Stream 3

A--B--C P--Q--R X--Y--Z

Fermi:

P -- Q -- R

A -- B -- C

X -- Y -- Z

Stream 1

Stream 2

Stream 3Concurrencia plena entre streams

P--Q--REl número de mallas

en ejecución crece hasta 32

Desaparecen las dependencias entre streamsKepler:

A--B--C

X--Y--Z

Oportunidad para solapar: Sólo en las fronteras entre streams

El hardware de la GPU puede albergar hasta 16 mallas en ejecución...

...pero los streams se multiplexan en una cola única

Page 10: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

...mapeados sobre GPU 37

E

F

D

C

B

A

Procesos en CPU...

Sin Hyper-Q: Multiproceso por división temporal

A B C D E F

100

50

% u

tiliz

ació

n de

la G

PU

0Tiempo

Tiempo ganado0

A

AA

B

B B

C

CC

D

D

D

E

E

E

F

F

F

Con Hyper-Q: Multiproceso simultáneo 100

50

% u

tiliz

ació

n de

la G

PU

0

4. GPU Boost (Kepler+)

¿En qué consiste?

Permite acelerar hasta un 17% el reloj de la GPU si el consumo de una aplicación es bajo.

Se retornará al reloj base si se sobrepasan los 235 W.Se puede configurar un modo “persistente” de vigencia

permanente de un reloj, u otro para ejecuciones puntuales.

39

Consumo sin apurar

Rendimiento

Reloj a máxima frecuenciaReloj base

Maximiza los relojes gráficos sin salirsede los márgenes de consumo nominales

745 MHz 810 MHz 875 MHz

Cada aplicación tiene un comportamiento distinto en relación al consumo

Aquí vemos el consumo medio (vatios) en la Tesla K20X de aplicaciones muy populares en el ámbito HPC:

40

0

40

80

120

160

AMBER ANSYS Black ScholesChroma GROMACS GTC LAMMPS LSMS NAMD Nbody QMCPACK RTM SPECFEM3D

Boa

rd P

ower

(Wat

ts)

Page 11: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

En el caso de la K40, se definen tres saltos de frecuencia con incrementos del 8.7%.

Aquellas aplicaciones que menos consumen pueden beneficiarse de una frecuencia mayor

41

Reloj base

Consumo máximo. Referencia (peor caso).

235W

Reloj acelerado

#1

Consumomoderado. Ej: AMBER

235W

Reloj acelerado

#2

Consumo bajo. Ej: ANSYS Fluent

235W

875 MHz

810 MHz

745 MHz

A 875 MHz, la K40 mejora el rendimiento hasta en un 40% respecto a la K20X.

Y no sólo mejoran los GFLOPS, también lo hace el ancho de banda efectivo con memoria.

GPU Boost frente a otras implementaciones

Resulta mejor un régimen estacionario para la frecuencia desde el punto de vista del estrés térmico y la fiabilidad.

42

Reloj de la GPU

Conmutación automática de reloj

Boost Clock # 1

Boost Clock # 2

Tesla K40

Relojes deterministas

Base Clock # 1

Otros fabricantes

Otros fabricantes Tesla K40

Valor por defecto

Opciones predefinidas

Interfaz con el usuario

Duración del reloj acelerado

Reloj acelerado Reloj base

Bloquear a la frecuencia base 3 niveles: Base, Boost1 o Boost2

Panel de control Comandos en el shell: nv-smi

Aprox. 50% del tiempo de ejec. 100% del tiempo de ejecución

Lista de comandos GPU Boost

43

Comando Efecto

nvidia-smi -q -d SUPPORTED_CLOCKS

nvidia-smi -ac <MEM clock, Graphics clock>

nvidia-smi -pm 1

nvidia-smi -pm 0

nvidia-smi -q -d CLOCK

nvidia-smi -rac

nvidia-smi -acp 0

Muestra los relojes que soporta nuestra GPU

Activa uno de los relojes soportados

Habilita el modo persistente (el reloj sigue vigente tras el apagado)

Modo no persistente: El reloj vuelve a su configuración base tras apagar la máquina

Consulta el reloj en uso

Inicializa los relojes en su configuración base

Permite cambiar los relojes a los usuarios que no son root

Ejemplo: Consultando el reloj en uso

nvidia-smi -q -d CLOCK —id=0000:86:00.0

44

Page 12: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

5. Memoria unificada (Maxwell+)

Ahora

46

GPU CPU

DDR4Memoria 2.5D

NVLINK80 GB/s

DDR4

100 GB/s Memoria apilada en

4 capas: 1 TB/s

En pocos años: Todas las comunicaciones internas al chip 3D

47

GPUCPU

Límitesdel áreade silicio

SRAM

3D-DRAM

La idea: Tenemos que acostumbrar al programador a ver así a la memoria

48

GPUCPU

DDR3 GDDR5

Memoria principal Memoria de video

PCI-express

Maxwell GPUCPU

DDR3 GDDR5Memoriaunificada

El viejo modelo software y hardware:Differentes memorias, prestaciones y espacio de direcciones.

El nuevo API:Misma memoria, un solo espacio de direcciones.

Rendimiento sensible a la proximidad de los datos.

CUDA 2007-2014 CUDA en lo sucesivo

Page 13: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Requerimientos del sistema

49

Requerido Limitaciones

Versión de CUDA

GPU

Sistema Operativo

Windows

Linux

Linux on ARM

Mac OSX

A partir de la 6.0

Kepler (GK10x+) oMaxwell (GM10x+)

Rendimiento limitado en CCC 3.0 y CCC 3.5

64 bits

7 u 8 WDDM & TCC no XP/Vista

Kernel 2.6.18+Todos los distros soportados por CUDA,

sin ARM en las primeras versiones

ARM64

Soportado a partir de CUDA 7 Soportado a partir de CUDA 7

Aportaciones de la memoria unificada

Un modelo de programación y de memoria más simple:Un puntero único a los datos, para acceder conjuntamente desde

CPU y GPU.Ya no hace falta utilizar cudaMemcpy().Simplifica enormemente la portabilidad del código.

Mayor rendimiento a través de la localidad de los datos:Migra los datos a la memoria del procesador que accede a ellos.Garantiza la coherencia global.Aún permite la optimización manual con cudaMemcpyAsync().

50

Los tipos de memoria en CUDA

51

Zero-Copy(pinned memory)

Unified Virtual Addressing Unified Memory

Llamada CUDA

Alojada en

Acceso local

Acceso por PCI-e

Otros rasgos

Coherencia

Disponibilidad

cudaMallocHost(&A, 4); cudaMalloc(&A, 4); cudaMallocManaged(&A, 4);

Mem. principal (DDR3) Mem. video (GDDR5) Ambas

CPU La GPU de su tarjeta La CPU y la GPU de su tarjeta

Todas las GPUs El resto de GPUs El resto de GPUs

Evita paginación a disco Prohibida desde la CPU Migra al acceder desde CPU o GPU

En todo momento Entre GPUs Sólo con lanzar + sincronizar

CUDA 2.2 CUDA 1.0 CUDA 6.0

Novedades en el API de CUDA

cudaMallocManaged(puntero,tamaño,flag)Sustituto de cudaMalloc(puntero,tamaño) para alojar memoria.El flag indica quién comparte el puntero con la GPU.cudaMemAttachHost: Sólo la CPU.

cudaMemAttachGlobal: Adicionalmente, cualquier otra GPU.

Todas las operaciones válidas sobre la memoria de la GPU también son válidas sobre la memoria unificada.

Nueva palabra clave: __managed__Anotación de variable global que se combina con __device__.Declara una variable de GPU migrable y de ámbito global.Símbolo accesible tanto desde la CPU como desde la GPU.

Nueva llamada: cudaStreamAttachMemAsync()Gestiona concurrentemente las aplicaciones multi-hilo de la CPU. 52

Page 14: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Detalles técnicos

La máxima cantidad de memoria unificada que puede alojarse es la menor de las memorias que tienen las GPUs.

Aquella memoria unificada que sea tocada por la CPU debe migrar de regreso a la GPU antes de lanzar el kernel.

La CPU no puede acceder a la memoria unificada mientras la GPU esté ejecutando, esto es, debemos llamar a cudaDeviceSynchronize() antes de permitir a la CPU que pueda acceder a la memoria unificada.

La GPU tiene acceso exclusivo a la memoria unificada mientras se esté ejecutando un kernel, aunque éste no toque la memoria unificada (ver el primer ejemplo de la serie).

53

5.1. Ejemplos de programación

Ejemplo 1:Restricciones de acceso (2)

55

__device__ __managed__ int x, y = 2; // Memoria unificada

__global__ void mykernel() // Territorio GPU{ x = 10;}

int main() // Territorio CPU{ mykernel <<<1,1>>> ();

y = 20; // ERROR: Acceso desde CPU concurrente con GPU return 0;}

Ejemplo 1:Restricciones de acceso (2)

56

__device__ __managed__ int x, y = 2; // Memoria unificada

__global__ void mykernel() // Territorio GPU{ x = 10;}

int main() // Territorio CPU{ mykernel <<<1,1>>> (); cudaDeviceSynchronize(); // Solución // Ahora, la GPU está parada, el acceso a “y” no tiene riesgo y = 20; return 0;}

Page 15: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

57

Código CUDA pre-versión 6.0SIN memoria unificada

Código CUDA post-versión 6.0CON memoria unificada

__global__ void incr (float *a, float b, int N){ int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx < N) a[idx] = a[idx] + b;}void main(){ unsigned int numBytes = N*sizeof(float); float* h_A = (float* ) malloc(numBytes); float* d_A; cudaMalloc(&d_A, numBytes); cudaMemcpy(d_A,h_A,numBytes,cudaMemcpyHostToDevice); incr<<<N/blocksize,blocksize>>>(d_A,b,N); cudaMemcpy(h_A,d_A,numBytes,cudaMemcpyDeviceToHost); cudaFree(d_A); free(h_A);}

__global__ void incr (float *a, float b, int N){ int idx = blockIdx.x * blockDim.x + threadIdx.x; if (idx < N) a[idx] = a[idx] + b;}void main(){

float* m_A; cudaMallocManaged(&m_A, numBytes);

incr<<<N/blocksize,blocksize>>>(m_A,b,N); cudaDeviceSynchronize(); cudaFree(m_A);}

Ejemplo 2: Incrementar un valor “b” a los N elementos de un vector “a”

Ejemplo 3: Ordenar un fichero de datos.Comparemos frente a las CPUs que usan C

58

Código para la CPU (en C) Código para la GPU (a partir de CUDA 6.0)

void sortfile (FILE *fp, int N) { char *data; data = (char *) malloc(N); fread(data, 1, N, fp);

qsort(data, N, 1, compare);

use_data(data);

free(data);}

void sortfile (FILE *fp, int N) { char *data; cudaMallocManaged(&data, N); fread(data, 1, N, fp);

qsort<<<...>>>(data, N, 1, compare); cudaDeviceSynchronize(); use_data(data);

cudaFree(data);}

Ejemplo 4: Clonando estructuras dinámicas SIN memoria unificada

Realizar copias sucesivas:Debemos copiar la estructura

y todos los contenidos a los que direcciona. Por eso C++ inventó el “copy constructor”.

CPU y GPU no pueden compartir una copia de sus datos (coherencia). Esto impide comparaciones tipo memcpy, checksums y demás.

59

dataElem

prop1

prop2

*text “Hola, mundo”

Memoria principal (CPU)

dataElem

prop1

prop2

*text “Hola, mundo”

Memoria de vídeo (GPU)

struct dataElem { int prop1; int prop2; char *text;}

Dos direccionesy dos copias diferentes delos datos

Clonando estructuras dinámicasSIN memoria unificada (2)

60

dataElem

prop1

prop2

*text “Hola, mundo”

Memoria principal (CPU)

dataElem

prop1

prop2

*text “Hola, mundo”

Memoria de vídeo (GPU)

void launch(dataElem *elem) { dataElem *g_elem; char *g_text;

int textlen = strlen(elem->text);

// Aloja almacenamiento para struct y text cudaMalloc(&g_elem, sizeof(dataElem)); cudaMalloc(&g_text, textlen);

// Copia cada pieza por separado, incluyendo un nuevo puntero en *text para la GPU cudaMemcpy(g_elem, elem, sizeof(dataElem)); cudaMemcpy(g_text, elem->text, textlen); cudaMemcpy(&(g_elem->text), &g_text, sizeof(g_text)); // Finalmente lanzamos el kernel, pero CPU y GPU usan diferentes copias de “elem” kernel<<< ... >>>(g_elem);}

Dos direccionesy dos copias diferentes delos datos

Page 16: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Clonando estructuras dinámicasCON memoria unificada

Lo que queda invariable:El movimiento de datos.GPU usa una copia local de text.

Lo que cambia:El programador ve un solo puntero.CPU y GPU acceden y referencian

al mismo objeto.Existe coherencia en memoria.

Para pasar por referencia y por valor se necesita usar C++.

61

void launch(dataElem *elem) { kernel<<< ... >>>(elem);}

dataElem

prop1

prop2

*text “Hola, mundo”

Memoria de vídeo (GPU)

Memoria unificada

Memoria principal (CPU)

Ejemplo 5: Listas enlazadas

Casi imposible de implementar con el API original de CUDA.La solución menos mala es utilizar memoria pinned:

Los punteros son globales, al igual que con memoria unificada.Pero el rendimiento es bajo: La GPU padece los accesos por PCI-e.La latencia de la GPU es muy alta, lo que resulta crítico para las listas

enlazadas debido al usual recorrido encadenado de punteros. 62

key

value

next

key

value

next

key

value

next

key

value

next

key

value

next

key

value

next

Todos los accesos por PCI-express

Memoria principal

Memoria de vídeo

Listas enlazadas con memoria unificada

Se pueden pasar elementos entre la CPU y la GPU.No hace falta mover datos entre la CPU y la GPU.

Se pueden insertar y borrar elementos desde CPU o GPU.Pero el programa debe prevenir condiciones de carrera (los datos

son coherentes entre CPU y GPU solo si se lanza y sincroniza). 63

key

value

next

key

value

next

key

value

next

Memoria principal (CPU)

Memoria de vídeo (GPU)

Memoria unificada: Resumen

cudaMallocManaged() reemplaza a cudaMalloc().cudaMemcpy() es ahora opcional.

Simplifica enormemente la portabilidad de código.Menos gestión de la memoria en el lado del host.

Permite compartir estructuras de datos entre CPU y GPUUn mismo puntero al dato. No hay que clonar su estructura.

Potente mecanismo en lenguajes de alto nivel como C++.

64

Page 17: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Memoria unificada: La hoja de ruta.Contribuciones según el nivel de abstracción

65

Nivel de abstracción

Consolidadoen 2014

Realizadodurante 2015-16

Lo más reciente (2017-2018)

Alto

Medio

Bajo

Un único puntero a los datos. cudaMemcpy()

ya no es necesaria

Mecanismos de prebúsqueda para anticipar la llegada de

datos en las copias

El alojamiento de la memoria

se unifica

Coherencia garantizada si se lanza y sincroniza

Directivas del programador para ayudar a una migración

de datos más eficiente

El uso de la memoria en la pila

también se unifica

Estructuras de datos compartidas en C y C++

Soporte adicional enlos sistemas operativos

Los mecanismos para aceleración de la coherencia se implementan en hardware

6. Planificación independiente de hilos

Nuevo modelo de sincronización y comunicación

En CUDA 9 se pueden definir sincronizaciones a 3 niveles:Intra-warp: Grupos de hilos dentro del warp.

Lanzar con el típico mikernel<<< , >>> o usando cudaLaunchKernel();

Inter-bloques: Múltiples bloques dentro de la malla.Lanzar usando cudaLaunchCooperativeKernel(mikernel);

Inter-GPUs: Múltiples GPUs dentro del sistema.Lanzar usando cudaLaunchCooperativeKernelMultiDevice(mikernel);

67

Intra-warp: Grupos cooperativos

Permite definir, sincronizar y particionar grupos de hilos cooperativos dentro del warp.

El programa puede ejecutarse en GPUs a partir de Kepler, aunque dispone de infraestructura hardware rápida en Volta:

Programación de algoritmos y estructuras de datos de forma natural.Agrupamiento y sincronización flexible de hilos.

La ejecución es escalable (desde unos pocos hilos a todos).Permite una sincronización explícita (a partir de CUDA 9).

Debemos adaptar el código antiguo al nuevo modelo de ejecución, eliminando la programación síncrona implícita de warps. Por ejemplo:

CUDA 9 depreca __shfl(), __ballot(), __any(), __all() asíncronos como transición a __shfl_sync(), __ballot_sync(), __any_sync(), ...

68

Page 18: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Grupos cooperativos:Sincronización explícita y flexible

Los grupos de hilos son objetos explícitos en tu programa:thread_group block = this_thread_block();

Se pueden sincronizar hilos dentro del grupo:block.sync();

Se pueden crear grupos particionando los ya existentes:thread_group tile32 = tiled_partition(block, 32);thread_group tile4 = tiled_partition(tile32, 4);

Los grupos particionados pueden sincronizarse también:tile4.sync();

69Nota: En verde las llamadas del nuevo API (espacio de nombres de grupos cooperativos). 70

Para cada bloque Para cada warp

g = my_thread_block();reduce(g, ptr, myVal);

g = tiled_partition<32>(my_thread_block());reduce(g, ptr, myVal);

Ejemplo 1: Suma por reducción en paralelo.Compuesta, robusta y eficiente

__device__ int reduce(thread_group g, int *x, int val) { int lane = g.thread_rank(); for (int i = g.size()/2; i > 0; i /= 2) { x[lane] = val; g.sync(); val += x[lane + i]; g.sync(); } return val;}

Ejemplo: Simulación de partículas

Sin emplear grupos cooperativos:

71

// Los hilos actualizan las partículas en paralelo// (posición, velocidad) integrate<<<blocks, threads, 0, s>>>(particles);

// Nota: sincron. implícita entre lanzam. de kernels

// Construye una malla para acelerar el proceso // de encontrar las colisiones entre partículascollide<<<blocks, threads, 0, s>>>(particles);

Notar cómo tras la creación de la malla, el orden de las partículas en memoria y su mapeo sobre los hilos cambia, requiriendo una sincronización entre fases y el lanzamiento de múltiples kernels. Utilizando grupos cooperativos, todas las sincronizaciones pueden hacerse dentro de un único kernel.

Cooperación para toda la malla (grid)

La actualización se hace en un solo kernel:

Lanzar usando cudaLaunchCooperativeKernel();72

__global__ void particleSim(Particle *p, int N) {

grid_group g = this_grid(); for (i = g.thread_rank(); i < N; i += g.size()) integrate(p[i]);

g.sync(); // Sync whole grid!

for (i = g.thread_rank(); i < N; i += g.size()) collide(p[i], p, N);}

Page 19: Recursos avanzados de CUDA Agradecimientos Curso de …ujaldon/descargas/curso-gpu2/diapos/CUDA... · 2017-07-13 · Diferencias en la jerarquía de memoria: Fermi vs. Kepler 5 Motivación

Cooperación multi-GPU

Large-scale multi-GPU simulation in a single kernel:

Lanzar usando cudaLaunchCooperativeKernelMultiDevice();73

__global__ void particleSim(Particle *p, int N) {

multi_grid_group g = this_multi_grid(); for (i = g.thread_rank(); i < N; i += g.size()) integrate(p[i]);

g.sync(); // Sync all GPUs!

for (i = g.thread_rank(); i < N; i += g.size()) collide(p[i], p, N);}