Sistemas Complejos en Máquinas Paralelas GPGPU Avanzado Esteban E. Mocskos (emocskos@dc.uba.ar) Facultad de Ciencias Exactas y Naturales, UBA CONICET 5/6/2012 E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 1 / 19
Objetivos: Mini-repaso de temas dados la semana pasado. Profundizar en la idea de paralelismo en base a la organización y arquitectura de las GPU s. Manejar los detalles acerca de las transferencias de memoria y el uso de la memoria de la placa. Ver las herramientas existentes para soporte de desarrollo. Encarar la resolución de un problema más complejo y real. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 2 / 19
Repaso de temas API s de CUDA 1 The data-parallel C++ Thrust. 2 The runtime API: se puede usar tanto desde C o C++. 3 The driver API: se puede usar tanto desde C o C++. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 3 / 19
Repaso de temas Tipos de funciones en CUDA host HostFunc() se ejecuta en el host y se llama desde el host: son las funciones normales y conocidas de C que hicimos hasta ahora en nuestra vida. global Kernelfunc() se ejecuta en el device y se llama desde el host: esta corresponde a la función que lanza la ejecución en la placa de video. device func() se ejecuta en el device y se llama desde el device: solo se puede llamar desde un kernel o desde otra función de este tipo. Deben ser funciones sencillas(no pueden ser recursivas). Se puede usar host y device con el mismo nombre, al compilar se crean dos distintas. Es útil para tener versión CPU y GPU de lo mismo para comparar o para elegir cuál usar en base a disponibilidad de la plataforma particular. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 4 / 19
Repaso de temas Arquitectura de una GPU E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 5 / 19
Repaso de temas Jerarquía de memoria de una GPU El device puede: R/W per-thread registers R/W per-thread local memory R/W per-block shared memory R/W per-grid global memory Read only per-grid constant memory Mientras que el host puede: Transfer data to/from per-grid global and constant memories E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 6 / 19
Repaso de temas Funciones útiles cudamalloc(): Reserva espacio en la memoria global del device. No se pueden usar los punteros de esta función directamente como punteros en C, hay que traer y llevar datos. cudafree() libera los recursos, recibe como parámetro el puntero que entregó cudamalloc. cudamemcpy(): transferencia de datos. Desde el host se puede acceder a memoria global y constante del device. La transferencia es asincrónica. 4 ciclos para pedir un dato de memoria, entre 400 y 600 para traerlo. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 7 / 19
Repaso de temas Paralelismo La llamada a un kernel va acompañada de execution configuration parameters que definen la organización de los hilos involucrados en la corrida. Cuidado con la cantidad máxima de threads por bloque, verificar con las propiedades de la placa. Se usan blockidx.x y blockidx.y para identificar en qué bloque se está. Cada thread tiene acceso a las variables threadidx.x, threadidx.y y threadidx.z, que representan unívocamente al hilo de sus hermanos. Cada thread en un bloque puede cooperar con los otros dentro del mismo bloque (sincronizar la ejecución, compartir datos a través de la shared memory). Dos threads en bloques distinos NO pueden cooperar. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 8 / 19
Detalles de ejecución Cuando un kernel se invoca, se ejecuta como una grid de threads. Una vez que un kernel se lanza, sus dimensiones NO pueden cambiar. La cantidad total de threads por bloque no puede ser mayor a 512, verificar mirando las propiedades de la placa. Dimensiones correctas para un bloque: (512, 1, 1) (8, 16, 2) (16, 16, 2) pero un bloque definido como (32,32,1) supera el límite. Normalmente, una grilla esta compuesta por cientos de miles de GPU threads en cada invocación a un kernel. La tarea principal es crear sufiente cantidad de threads para utilizar toda la maquinaria disponible en cada placa. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 9 / 19
Separando el trabajo La idea es pensar en una separación de trabajo en forma de azulejo(tile). A cada bloque de threads se le asigna un tile. De esta manera, se tiene el programa preparado para distintos tamaños de problema. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 10 / 19
Thread scheduling El scheduling de los threads es un concepto que está a nivel de implementación y depende de los detalles específicos del hardware, pero saber cómo lo hace ayuda al momento diseñar la aplicación. Los bloques se asignan a los SMs siempre y cuando haya recursos disponibles, con un máximo que es dependiente de la placa. Cuando un bloque de threads se asigna a un SM, se vuelve a partir de bloques de 32 threads denominado warp. Si la cantidad de threads no es múltiplo de 32 se agregan para completar. El tamaño de los warps es totalmente dependendiente de la implementación y no forma parte de la especificación de CUDA. El warp es la unidad de scheduling en los SMs. La idea es tener muchos warps disponibles para poder ponerlos a correr a medida que se pueda (hay un mecanismo de prioridades para elegir si hay varios listos para correr). Latency hiding: cuando un warp queda bloqueado ante una instrucción (por ejemplo un acceso a memoria), se cambia por otro que este listo. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 11 / 19
Sincronizando threads Está disponible la función syncthreads(): Sincroniza la ejecución de los threads dentro de un mismo bloque (i.e. actúa como un barrier). Todos los hilos del bloque quedan en espera hasta que todo el resto llega a ejecutarlo. Hay que ser cuidadoso con el if-then-else para evitar que se cuelgue todo (una rama tiene la llamada a la función y la otra no). No hay una llamada similar para coordinar entre bloques y es una decisión de diseño para simplificar la arquitectura. Permite tener transparent scalability: la capacidad de poder ejecutar (y hacer uso) la misma aplicación en hardware con diferentes capacidades y recursos. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 12 / 19
Manejo de memoria de la placa Jerarquía de memoria de una GPU El device puede: R/W per-thread registers R/W per-thread local memory R/W per-block shared memory R/W per-grid global memory Read only per-grid constant memory Mientras que el host puede: Transfer data to/from per-grid global and constant memories E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 13 / 19
Manejo de memoria de la placa Declaración de variables Variable Declaration Memory Scope Lifetime Variables (no arreglos) Register Thread Kernel Automatic array variables Local Thread Kernel device, shared, int SharedVar; Shared Block Kernel device, int GlobalVar; Global Grid Application device, constant, int ConstVar; Constant Grid Application La memoria constante soporta acceso a baja latencia y alto ancho de banda cuando todos los threads acceden simultáneamente a la misma posición. Los registros y la memoria compartida están dentro del chip. Se pueden acceder a muy alta velocidad y en forma altamente paralela. Los registros se reservan a cada thread; cada thread solo puede acceder a sus propios registros. El uso ideal es para variables privadas que se acceden frecuentemente. La memoria compartida se reserva a los bloques. Todos los threads en un bloque puede acceder a las variables declaradas en esta memoria. La memoria compartida es una manera eficiente para que los threads cooperen compartiendo datos de entradas y resultados intermedios. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 14 / 19
Manejo de memoria de la placa Memory coalescing La memoria global en una placa normalmente se implementa utilizando DRAMs (Dynamic Random Access Memories). La lectura de un 1 implica la descarga de un capacitor, es muy lento. Localidad espacial: las aplicaciones suelen acceder a los datos cercanos a uno pedido. En CUDA, se trata de aprovechar la localidad espacial para mejorar el acceso a la memoria global. Dado que todos los threads de un warp ejecutan la misma instrucción, se pueden unir las lecturas de memoria si son a posiciones consecutivas. Si se dan todas las condiciones necesarias, el hardware de la placa detecta la situación y combina todos los accesos (memory coalescence). Los coalesced access permiten operar a la memoria global cerca de su pico de funcionamiento. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 15 / 19
Algunos detalles adicionales Midiendo tiempos cudaevent t s t a r t, stop ; cudaeventcreate (& s t a r t ) ; cudaeventcreate (& stop ) ; / * S t a r t de t i m e r * / cudaeventrecord ( start, 0 ) ; / * Codigo que se quiere medir * / cudaeventrecord ( stop, 0 ) ; cudaeventsynchronize ( stop ) ; f l o a t elapsedtime ; cudaeventelapsedtime (&elapsedtime, s t a r t, stop ) ; p r i n t f ( elapsed time = %g msec\n, elapsedtime ) ; / * Despu es de usar l o s timers, hay que d e s t r u i r l o s. * / cudaeventdestroy ( s t a r t ) ; cudaeventdestroy ( stop ) ; E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 16 / 19
Algunos detalles adicionales Importante: 1 Get the data on the GPGPU and keep it there. 2 Give the GPGPU enough work to do. 3 Focus on data reuse within the GPGPU to avoid memory bandwidth limitations. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 17 / 19
Algunos detalles adicionales Ejercicio Dadas dos matrices A y B, hacer una función para calcular la suma y mostrar el resultado tanto en CPU como en GPU. Variar los valores de bloques y cantidad de hilos para mejorar la performance. Comparar el desempeño de las implementaciones en CPU y GPU. E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 18 / 19
Algunos detalles adicionales Bibliografía: CUDA by Example: An introduction to General-Purpose GPU Programming, Jason Sanders, Edward Kandrot, Nvidia, 2010. Programming Massivelly Parallel Processors: A Hands-on approach, David Kirk, Wen-mei W. Hwu, Morgan Kaufmann, 2010. CUDA Application Design And Development, Ron Farber, Morgan Kaufmann, 2011. Online tutorial by Sean Baxter http://www.moderngpu.com/intro/intro.html (año 2011). E. Mocskos (UBA CONICET) GPGPU Avanzado 5/6/2012 19 / 19