0% encontró este documento útil (0 votos)
35 vistas26 páginas

Práctica 04

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)
35 vistas26 páginas

Práctica 04

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

Universidad de Murcia

Facultad de Informática
Departamento de Ingenierı́a y Tecnologı́a de Computadores
Área de Arquitectura y Tecnologı́a de Computadores

Prácticas de

Arquitecturas Multimedia y de Propósito


Especı́fico
4º de Grado en Ingenierı́a Informática
Boletı́n de prácticas 4 – Optimización de una arquitectura GPU para DNNs

Curso 2024/2025
Índice
1. Introducción 2
1.1. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2. Herramientas utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2

2. Limitación de las GPUs 2

3. MGPUSim 3
3.1. Introducción a MGPUSim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
3.2. Modelo de ejecución en GPUs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
3.3. Modelado de la GPU . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
3.4. Instalación de MGPUSim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
3.5. Jerarquı́a de directorios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3.6. Cómo hacer una simulación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3.7. Fichero de estadı́sticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.8. Streaming benchmark . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.9. Cómo configurar MGPUSim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11

4. conv2d 17

5. Arquitectura simulada de partida 19

6. Ejercicios propuestos 21

1
1. Introducción
Las Unidades de Procesamiento Gráfico, o GPUs por sus siglas en inglés, han adquirido una
enorme importancia en el mundo de la computación en las últimas décadas. Lo que comenzó como un
simple chip especializado para acelerar la renderización de gráficos en videojuegos, ha evolucionado
hasta convertirse en un componente esencial para el desarrollo de modelos de aprendizaje profundo,
también conocidos como redes neuronales.
Las GPUs se han convertido en el caballo de batalla de la inteligencia artificial y el machine
learning, permitiendo el entrenamiento y la inferencia de modelos de DNN de forma eficiente y a gran
escala. Además, se han convertido en un componente clave para el desarrollo de hardware especı́fico y
aceleradores de cómputo especializados, debido a su alta capacidad de procesamiento y su capacidad
de paralelización.
En esta práctica, utlizando MGPUSim (Multi-GPU Simulator), un simulador de GPUs basado en
eventos, exploraremos las diferentes partes de una GPU, su arquitectura y los conceptos clave que se
deben conocer para su programación y uso efectivo (a pesar de que la mayorı́a de las bibliotecas de
deep learning más populares, TensorFlow y PyTorch, admiten el uso de GPUs, requieren cierto nivel
de conocimiento del hardware para aprovechar al máximo su capacidad).
Durante la elaboración de esta práctica, hemos considerado conveniente recordaros la diferencia
entre FLOPS y FLOPs. El primero se refiere a la velocidad de procesamiento en operaciones de
coma flotante por segundo (FLOPs per Second ), mientras que el segundo indica la cantidad total de
operaciones que se pueden realizar (FLOPs).

1.1. Objetivos
Al terminar el boletı́n el alumno debe ser capaz de:

Conocer el diseño hardware de una arquitectura GPU.

Entender la influencia que los valores de diversos parámetros de una arquitectura GPU tienen
sobre las prestaciones y el consumo energético.

Utilizar una herramienta de simulación de arquitecturas GPU para lograr un prototipado rápido
de un diseño hardware.

Escribir adecuadamente los resultados del trabajo realizado en forma de artı́culo cientı́fico.

1.2. Herramientas utilizadas


Las herramientas que usaremos en este boletı́n son:

Docker

Debian 11 + golang1.20.2 (1)

MGPUSim v3.0 [1] (ampe-edition) (2)

Además, para la elaboración de la memoria de la práctica, que se realizará en formato de artı́culo


cientı́fico, se empleará LATEX (en la zona de ≪Recursos≫ del Aula Virtual puede encontrarse la plantilla
a usar) y, de manera recomendada, también BibTEX.

2. Limitación de las GPUs


A pesar de los enormes beneficios que ofrecen las GPUs, también tienen algunas limitaciones
importantes que deben tenerse en cuenta. Uno de los principales problemas es el consumo energético
en comparación con otros componentes (p.e. una CPU, un componente HW especializado, etc), lo
que puede ser un problema en aplicaciones que requieren una gran cantidad de procesamiento a largo
plazo.

2
Además, las GPUs suelen ser bastante grandes y voluminosas, lo que puede limitar su uso en
ciertos entornos y configuraciones. Esto es especialmente crı́tico para los dispositivos móviles, donde
el espacio y la eficiencia energética son factores crı́ticos.
Otra limitación importante es que la programación de las GPUs puede resultar bastante compleja
para los programadores que no estén familiarizados con la arquitectura. Por ejemplo, la llegada de la
nueva arquitectura de NVIDIA Hopper (H100), hace que los desarrolladores deban tener en cuenta
un nuevo nivel de abstracción, que no aparecı́a hasta ahora, y que les permite sincronizar bloques de
hilos agrupándolos en una nueva unidad llamada Thread Block Cluster o TBC.
Es importante tener en cuenta que las GPUs no son una solución universal para todos los pro-
blemas de computación intensiva. Aunque son muy efectivas para ciertos tipos de trabajo, como el
procesamiento de imágenes y deep learning (en general, aplicaciones que requieran un procesamiento
regular de una gran cantidad de datos), pueden no ser la mejor opción para otras aplicaciones, como
el procesamiento de texto o la simulación fı́sica. Por lo tanto, es importante evaluar cuidadosamente
las necesidades de cada proyecto antes de decidir utilizar una GPU.
Una de las principales ventajas de utilizar GPUs para el entrenamiento de modelos de deep learning
es que permiten realizar cálculos en paralelo, lo que significa que múltiples operaciones matemáti-
cas se pueden realizar simultáneamente, acelerando significativamente el proceso de entrenamiento
en comparación con las CPUs convencionales. Además, las GPUs tienen una arquitectura diseñada
especı́ficamente para acelerar los cálculos matemáticos (hardware especializado) necesarios para el
entrenamiento de estos modelos.
A pesar de las limitaciones mencionadas anteriormente, las GPUs son la opción preferida para el
entrenamiento de modelos de deep learning, debido a su capacidad para procesar grandes cantidades
de datos y realizar operaciones matemáticas complejas de manera extremadamente rápida.

3. MGPUSim
La creciente popularidad y escala de las cargas de trabajo de paralelismo de datos demandan
un aumento correspondiente en la potencia computacional de las GPUs. Dado que las plataformas
que cuentan con una sola GPU (Single-GPU) tienen dificultades para cumplir con las demandas
de rendimiento actuales, las plataformas con múltiples GPUs (Multi-GPU) han comenzado a ser la
elección dominante en el ámbito de la computación de alto rendimiento. La llegada de estos sistemas
trae consigo una serie de desafı́os en cuanto al diseño, como por ejemplo la estructura interna de la
GPU, la conexión entre varias GPUs, las librerı́as que manejan la ejecución en tiempo real y los modelos
de programación que se utilizan. Hasta la llegada de MGPUSim, la comunidad de investigación carecı́a
de un marco de simulación multi-GPU público, disponible y completo para evaluar diseños de sistemas
multi-GPU de próxima generación.

3.1. Introducción a MGPUSim


MGPUSim es un simulador de múltiples GPUs, extremadamente preciso y validado; este se en-
cuentra basado el repertorio de instrucciones (ISA) de AMD’s Graphics Core Next 3 (GCN3) y modela
la tarjeta gráfica AMD R9 Nano de la serie Fiji. MGPUSim cuenta con soporte integrado para la eje-
cución multihilo para permitir una simulación rápida, paralela y precisa. En términos de precisión de
rendimiento, MGPUSim difiere solo en un promedio del 5.5 % del hardware real de la GPU. También
logra obtener una aceleración promedio de 3.5x y 2.5x al ejecutar la emulación funcional (emu) y la
simulación de temporización detallada (timing), respectivamente, en una CPU de 4 núcleos, mientras
consigue la misma precisión que la simulación en serie (no paralela).
MGPUSim es ampliamente configurable y está publicado bajo la licencia MIT. El simulador se
encuentra desarrollado bajo el lenguaje de programación Go (de ahı́ el motivo de usar Go como herra-
mienta para el desarrollo de esta práctica). Además, proporciona soporte nativo para la programación
multihilo, lo que nos va a permitir generar un gran número de de Goroutines (es decir, hilos) para
procesar eventos con muy poca sobrecarga (no confundir estos hilos con los de la GPU; hablamos de
hilos en Go a la hora de lanzar la simulación).

3
MGPUSim está basado en el simulador de eventos llamado Akita y está formado por la unión de
varios módulos; por ejemplo, el módulo mem modela los componentes del sistema de memoria (L1s,
L2s, DRAM, etc.) o noc, que modela la red de interconexión (network on chip) p.e. de la L1 – L2.
A un nivel de abstracción alto, el simulador puede verse como un framework de trabajo compuesto
por cuatro partes muy bien diferenciadas:

Simulación por eventos, definiendo un evento como la actualización del estado de un compo-
nente: el motor de simulación de MGPUSim mantiene una cola de eventos para toda la simulación
y activa los eventos en orden cronológico.

Components: cada entidad que MGPUSim simula es un componente. En nuestro caso, una
GPU, una Compute Unit (CU) y un módulo de caché son ejemplos de componentes.

Conexiones: dos componentes sólo pueden comunicarse entre sı́ a través de conexiones mediante
peticiones. Las conexiones también se utilizan para modelar la red de interconexión intrachip y
la red de interconexión interchip.

Hooks: son pequeñas piezas de software que se pueden conectar al simulador para leer el esta-
do o actualizarlo. El motor de simulación, los componentes y todas las conexiones pueden ser
énganchadas’. Los ganchos pueden realizar tareas no crı́ticas como recoger trazas de ejecución,
volcar información de depuración, calcular métricas de rendimiento, registrar motivos de dead-
locks; de esta manera es como obtendremos información a cerca de la arquitectura que estamos
simulando.

3.2. Modelo de ejecución en GPUs


Un sistema GPU, como el que todos conocemos, está formado por una o varias CPUs y una
o varias GPUs (las plataformas multi-GPU más modernas pueden tener hasta 8 GPUs por nodo).
Tradicionalmente, la GPU es gestionada por la CPU. Más concretamente, el programa host que se
ejecuta en la CPU copia los datos a la memoria de las GPUs (memory copy host to device) y lanza los
programas (kernels) en las GPUs. Una vez finalizados, la CPU vuelve a copiar los resultados calculados
de la memoria de las GPUs a la memoria del sistema (memory copy device to host). Un driver de GPU
especı́fico (p.e. el que implementa CUDA), que se ejecuta a nivel de sistema operativo en la CPU,
recibe las llamadas a la API (p.e. cuda malloc o cuda copy) y transfiere los datos de/a las GPUs e
inicia los kernels en las GPUs.
Las nuevas caracterı́sticas de las GPUs les otorgan una mayor autonomı́a. Por ejemplo, Kernel
Side Enqueueing, le permite a la GPU lanzar kernels a sı́ misma sin la ayuda de una CPU. Otro
ejemplo es la memoria unificada y la paginación por demanda, las cuales permiten a la GPU leer y
escribir directamente en la memoria del sistema sin necesidad de movimientos explı́citos de datos entre
la CPU y la GPU. Cada nueva caracterı́stica que no estaba presente en generaciones anteriores de
GPU requiere nuevo soporte tanto en el ISA como en la microarquitectura, lo que ha llevado a una
evolución constante del ISA.
Un kernel puede lanzar una malla de 1-,2- o 3-dimensiones de hilos en una GPU. En terminologı́a
AMD, llamamos a este hilo un work-item; si lo comparamos con la definición de hilo tradicional en
una CPU, cada work-item tiene su propio registro de estado. En el ISA de GCN3, un wave-front
consiste en 64 work-items que ejecutan la misma instrucción en lockstep. Un work-group contiene
de 1-8 wavefronts que pueden ser sincronizados usando barreras.
Si hacemos una comparación con NVIDIA, la definición de work-item serı́a la de un Thread ; un
wave-front la de un Warp; y la de un work-group, la de un Thread Block. Con la única diferencia
de que GCN3 establece un tamaño de wave-front de 64 work-items, mientras que NVIDIA establece
un tamaño de warp de 32 threads.
Las arquitecturas GPU actuales proporcionan unas tasas de rendimiento muy elevadas, medidas
estas en FLOPS (operaciones de punto flotante por segundo). Por ejemplo, la GPU AMD Radeon
Instinct MI60 utiliza 64 unidades de cómputo (CU) para ejecutar instrucciones en paralelo. Cada CU
incorpora 4 unidades Single Instruction Multiple-Data (SIMD). Cada unidad SIMD tiene 16 carriles

4
(lanes), y cada carril soporta una unidad de punto flotante de precisión simple. Por lo tanto, una sola
unidad SIMD puede ejecutar 16 instrucciones en paralelo en un solo ciclo de reloj. Equipado con 64
CUs, la GPU R9 Nano puede ejecutar hasta 8.192 operaciones (las operaciones de multiplicación y
suma fusionadas se consideran dos operaciones) por ciclo.

3.3. Modelado de la GPU


A continuación, se describe la arquitectura original de GPU modelada por MGPUSim, la cual
representa a la GPU R9 Nano. Es importante destacar que esta no es la arquitectura que se abordará
en este boletı́n, sino que su propósito es brindar una introducción y un contexto para comprender
cómo se modela una arquitectura de GPU en MGPUSim. El objetivo de esta sección es proporcionar
los conocimientos necesarios sobre cómo se modelan los distintos componentes y cómo estos pueden
ser modificados posteriormente para llevar a cabo tareas especı́ficas.
Para conocer la arquitectura que estamos simulando en este boletı́n, es decir, la versión de partida
(no optimizada), te recomendamos que te dirijas a la Sección 5. Allı́ encontrarás una descripción deta-
llada de la arquitectura y sus componentes. Es importante que estés familiarizado con esta información
para que puedas comprender mejor el resto del boletı́n.
El modelo de GPU que implementa MGPUSim, y que se muestra en la Figura 1, admite fielmente
el ISA Graphics Core Next 3 (GCN3). El modelo de la GPU se configura según la documentación
pública disponible de AMD y a través de microbenchmarking. Si bien el último ISA en las GPUs
AMD Vega funciona con GCN5, GCN5 solo extiende las instrucciones de acceso a memoria. De esta
forma, la simulación de GCN5 se puede lograr en MGPUSim simplemente agregando soporte para las
nuevas instrucciones de acceso a memoria. No es necesario cambiar los componentes principales.
La arquitectura de la GPU se compone de un Procesador de Comandos (CP, Command Processor),
Motores de Cálculo Ası́ncrono (ACEs, Asynchronous Compute Engines), Unidades de Cálculo (CUs,
Compute Units), cachés y controladores de memoria. El CP es responsable de la comunicación con el
driver de la GPU y de iniciar los kernels con la ayuda de los ACEs. Los ACEs envı́an las wavefronts
de los kernels para ser ejecutados en las CUs. Además, existe un nivel de abstracción que agrupa 4
CUs en lo que se denomina un Shader Array (SA).
Si nos detenemos a pensar lo que ocurre a más bajo nivel, un kernel se descompone en distintos
workgroups, y estos a su vez, están conformados por varios wavefronts (64 work-items). Por la natura-
leza de la GPU, este trabajo de descomponer el kernel en workgroups lo hace el CP, y posteriormente
las ACEs se encargan de enviar los wavefronts a las distintas CU, pero garantizando que los wavefronts
de cada workgroup se ejecutan de manera aislada en la misma CU. Aunque podemos ver como que el
workgroup se envı́a a una CU, en realidad son los wavefronts lo que van siendo enviados a la CU (de
ahı́ que podamos hacer sincronizaciones de los work-items dentro del mismo workgroup).

Figura 1: La arquitectura GPU original

Una CU (como se muestra en la Figura 2) incorpora un planificador (scheduler), un conjunto de de-


codificadores (decoders), un conjunto de unidades de ejecución (SIMDs) y un conjunto de unidades de
almacenamiento. La CU incluye un conjunto de registros escalares (SGPRs), un conjunto de registros

5
vectoriales (VGPRs) y un almacenamiento compartido de datos local (LDS o memoria Scratchpad en
NVIDIA). Un fetch arbiter y un issue arbiter deciden qué wavefronts puedes recuperar instrucciones
y emitir instrucciones, respectivamente. Los decodificadores necesitan 1 ciclo para decodificar cada
instrucción antes de enviarla a la unidad de ejecución (por ejemplo, Scalar Decode y Scalar Unit). En
la mayorı́a de los casos, cada unidad de ejecución tiene un diseño de pipeline que incluye etapas de
lectura, ejecución y escritura (LDS Unit, SIMDs, VMem Unit, etc). En total, cada CU puede procesar
a la vez 4 wavefronts (ya que tiene 4 unidades SIMD), a un ritmo de 16 elementos de cada uno por
ciclo. De esta forma, cada CU realizará un total de 64 FLOPs por ciclo (16x4), ten en cuenta que las
operaciones de multiplicación y acumulación (MAC) se cuentan como dos operaciones para el cálculo
correcto de los FLOPs. Este dato lo necesitarás de cara a poder derivar los modelos roofline que se
piden en los ejercicios de la práctica.

Figura 2: La Unidad de Cómputo (CU) original

MGPUSim proporciona un conjunto de controladores de caché, que incluyen una caché de escri-
tura directa, una caché de write-around, una caché de write-back, y un controlador de memoria. Por
defecto, las cachés L1 y L2 utilizan polı́ticas de escritura directa y write-back, respectivamente. Los
controladores de caché no imponen la coherencia y la GPU implementa un modelo de memoria total-
mente relajado. Las unidades de cómputo envı́an direcciones virtuales para las peticiones de lectura
y escritura a la caché L1. Estas direcciones virtuales se traducen en direcciones fı́sicas en la caché
L1 con la ayuda de dos niveles de TLBs. Se muestra la configuración predeterminada en la Figura 1.
Finalmente, cada GPU está equipada con un motor de acceso directo a memoria remota (RDMA)
para gestionar la comunicación entre GPUs. En cualquier caso, en esta práctica vamos a centrarnos
en simular una única GPU, por lo que este último componente no va a ser tenido en consideración.
Puesto que en el apartado anterior hemos hecho una comparación entre las terminologı́as usadas
por AMD y NVIDIA, vamos a realizar lo mismo en este en cuanto a los componentes que acabamos de
ver que definen la arquitectura de una GPU. En NVIDIA, la Unidad de Cómputo es lo que denominan
el Streaming Multiprocessor o SM, que a su vez puede tener varios bloques de procesamiento. A grandes
rasgos, este SM podrı́a verse como el SA que contiene 4 CUs en nuestra arquitectura. Además, al igual
que cada CU tiene varios lanes (16*4), NVIDIA define lo que denominan CUDA Cores, que son las
unidades encargadas de hacer las operaciones individuales (enteras y de punto flotante). Ası́, cada
bloque de procesamiento dentro de esta SM puede tener varios CUDA cores dedicados (por ejemplo,
16 para operaciones enteras, 16 para FP32, 8 para FP64).

3.4. Instalación de MGPUSim


La instalación de MGPUSim es muy sencilla. Puesto que el simulador ha sido construido usando
el lenguaje Go, es suficiente con instalar la última versión de Go, y a continuación, hacer un git clone
del repositorio MGPUSim.
Sin embargo, puesto que MGPUSim es un framework de trabajo compuesto por varios módulos de
Akita, hay muchos de estos que no tienen reflejados los cambios que hemos realizado de cara a esta
práctica. Es por ello que os sugerimos e invitamos a que utilicéis el Docker que aquı́ os
presentamos.

6
Esta imagen de Docker cuenta con todo ya instalado y listo para hacer la práctica. Se incluyen
instaladas las herramientas que mostramos al principio de este boletı́n (Debian 11 + Go (v1.20) +
MGPUSim (ampe)).
Si utilizas Docker Desktop, puedes descargar la imagen desde: aquı́. O si lo prefieres puedes hacer
directamente (utiliza siempre la última versión):

$ docker pull nicolasmeseguer/ampe:vX.Y


$ docker run -it --name ampe-container nicolasmeseguer/ampe:vX.Y

Para salir del contenedor basta con escribir exit. Posteriormente, para volver a conectarte, puedes
utilizar:

$ docker start ampe-container


$ docker attach ampe-container

Si todo ha salido bien, una vez inicies el contenedor Docker deberás de poder hacer:

root@20018f8015f7:/go# go version
go version go1.20.2 linux/amd64

Durante el resto de secciones vamos a asumir que estamos usando el Docker y que MGPUSim se
encuentra instalado en el directorio raı́z (/).

3.5. Jerarquı́a de directorios


El directorio principal del simulador se encuentra en /mgpusim. Desde este directorio podemos
visualizar el fichero README.md del simulador, que contiene una explicación más detallada de la
instalación, funcionamiento y ejecución de MGPUSim. Aún ası́, en esta sección vamos a ver la jerarquı́a
de directorios principales y en las secciones próximas aprenderemos a cómo utilizar y modificar aquellos
aspectos del simulador necesarios para realizar nuestras simulaciones de arquitecturas GPU especı́ficas.

root@20018f8015f7:/mgpusim# ls -l
total 96
-rw-r--r-- 1 root root 1078 Mar 30 15:54 LICENSE
-rw-r--r-- 1 root root 7178 Mar 30 15:54 README.md
drwxr-xr-x 12 root root 4096 Mar 30 15:54 benchmarks
drwxr-xr-x 2 root root 4096 Mar 30 15:54 bitops
drwxr-xr-x 4 root root 4096 Mar 30 15:54 doc
drwxr-xr-x 3 root root 4096 Mar 30 15:54 driver
drwxr-xr-x 3 root root 4096 Mar 30 15:54 emu
-rw-r--r-- 1 root root 2140 Mar 30 15:54 go.mod
-rw-r--r-- 1 root root 28718 Mar 30 15:54 go.sum
drwxr-xr-x 3 root root 4096 Mar 30 15:54 insts
drwxr-xr-x 2 root root 4096 Mar 30 15:54 kernels
drwxr-xr-x 2 root root 4096 Mar 30 15:54 protocol
drwxr-xr-x 46 root root 4096 Mar 30 15:54 samples
drwxr-xr-x 2 root root 4096 Mar 30 15:54 server
drwxr-xr-x 4 root root 4096 Mar 30 15:54 tests
drwxr-xr-x 8 root root 4096 Mar 30 15:54 timing

De entre todos los directorios y ficheros, vamos a destacar los siguientes:

benchmarks: en este directorio encontrarás ficheros que funcionan como la interfaz de progra-
mación de aplicaciones (API) de OpenCL, es decir, el driver encargado de cargar el kernel en la
GPU, ası́ como, el propio kernel que se ejecutará. Esta parte podrı́a verse como el back-end de
los benchmarks.

7
samples: este directorio es la contraparte del directorio anterior, es decir, aquı́ se encuentra el
front-end de los benchmarks que se ejecutarán. Por lo tanto, desde estos ficheros se llamará a
los del directorio anterior.

doc: incluye documentación acerca de cómo se implementa la R9 Nano en el simulador (aparecen


componentes, conexiones, hooks entre los componentes, etc.)

emu: en este directorio se encuentran los ficheros de configuración para la emulación funcional.
En esta práctica nos centramos en la simulación de temporización (timing) detallada.

insts: contiene el repertorio de instrucciones de GCN3 y las tablas de decodificación.

timing: contiene los ficheros de configuración para la simulación detallada. Aquı́ podemos
encontrar las distintas unidades y componentes con las cuales formaremos la CU, el CP, etc.

Para la realización de esta práctica no vais a tener que modificar ningún benchmark. El objetivo
va a consistir en modificar parámetros de simulación (p.e. número de CUs, número de $L2, etc.). En
la Sección 3.9 detallaremos aquellos directorios y ficheros que tendréis que modificar para lograr este
propósito.

3.6. Cómo hacer una simulación


Para este ejemplo vamos a ejecutar un benchmark muy sencillo, fir (Finite Impulse Response) y
se refiere a un tipo de filtro digital que se utiliza comúnmente en el procesamiento de señales.
Vamos a dirigirnos al directorio samples y allı́, vamos a compilar el simulador con el benchmark.
El compilador generará un ejecutable llamado fir (para Linux y Mac OS) o fir.exe (en Windows),
a partir del fichero de front-end del benchmark, fir.go.
Vamos a ejecutar la simulación en el modo simulación detallada con:

root@20018f8015f7:/mgpusim# cd samples/fir
root@20018f8015f7:/mgpusim/samples/fir# go build
root@20018f8015f7:/mgpusim/samples/fir# ls -l
total 26548
-rwxr-xr-x 1 root root 27173302 Apr 3 10:38 fir
-rw-r--r-- 1 root root 617 Mar 30 15:54 fir.go
-rw-r--r-- 1 root root 440 Mar 30 15:54 fir_test.go
root@20018f8015f7:/mgpusim/samples/fir# ./fir -timing --report-all
length = 64
Monitoring simulation with http://localhost:44309
root@20018f8015f7:/mgpusim/samples/fir#

Es importante destacar que el argumento ‘-timing’ hay que utilizarlo en todas las ejecuciones
que hagamos, ya que si no estarı́amos lanzando la ejecución en modo emulación funcional (validación)
y esto generará errores y no podremos compilar/ejecutar nuestro benchmark.
Si quisiésemos modificar un benchmark (p.e. tamaño de vector) podemos hacerlo de dos maneras:

1. Como argumento del benchmark: por defecto todos los benchmarks de MGPUSim permiten
introducir por lı́nea de comandos el tamaño de las variables. Por ejemplo:

root@20018f8015f7:/mgpusim/samples/fir# ./fir -timing --report-all


-length=100000
length = 100000
Monitoring simulation with http://localhost:40201
root@20018f8015f7:/mgpusim/samples/fir#

8
2. Modificando el benchmark: utlizando un editor de texto (p.e. Vim) podemos cambiar el
tamaño por defecto de la variable length en el fichero del kernel; fir.go, que en este caso es
64 (recordaros que, tras hacer este cambio tenemos que volver a compilar con go build).

Para ver el resto de argumentos que este kernel soporta (dependiendo del kernel y de las entradas
que este admita, los argumentos de entrada variarán) puedes escribir -help.
Aunque no es un requisito para la práctica, es posible que alguno de vosotros sienta curiosidad o
desee experimentar con algún kernel para obtener una comprensión más profunda de las GPUs o de
la herramienta de simulación. En el fichero README.md de MGPUSim se detalla cómo preparar tu
propio experimento, y en el fichero index.md del directorio doc aparece una explicación más detallada,
a la cual os referimos si tenéis curiosidad.

3.7. Fichero de estadı́sticas


Si uno se fija bien en el comando anterior, una vez hemos compilado el benchmark con Go, a la hora
de lanzarlo, hemos especificado los argumentos -timing (simulación detallada), y --report-all.
Con este último argumento le decimos al simulador que habilite las métricas de rendimiento y nos las
devuelva en el directorio desde el que hemos lanzado el benchmark (fichero metrics.csv).

root@20018f8015f7:/mgpusim/samples/fir# ./fir -timing --report-all


length = 64
Monitoring simulation with http://localhost:37607
root@20018f8015f7:/mgpusim/samples/fir# ls -l
total 26592
-rwxr-xr-x 1 root root 27173302 Apr 3 10:38 fir
-rw-r--r-- 1 root root 617 Mar 30 15:54 fir.go
-rw-r--r-- 1 root root 440 Mar 30 15:54 fir_test.go
-rw-r--r-- 1 root root 44949 Apr 3 10:52 metrics.csv
root@20018f8015f7:/mgpusim/samples/fir#

Este fichero nos devuelve información acerca de todos los componentes de simulación que nuestra
R9 Nano modela, desde grano fino a grano grueso (SIMDs por CU, CUs por SAs, CP, etc.) y para
cada componente, accesos, tiempos medios de escritura, lectura, tamaño en bytes de escritura/lectura,
etc.
Entre las principales estadı́sticas se encuentran:

Tiempo total de ejecución.

Tiempo total del kernel.

Tiempo del kernel por GPU.

Conteo de instrucciones en cada Unidad de Cómputo

Latencia promedio de solicitud en todos los componentes de caché.

Número de fallos de lectura, aciertos en el MSHR de lectura, aciertos de lectura, fallos de


escritura, aciertos en el MSHR de escritura y aciertos de escritura en todos los componentes de
caché.

Número de transacciones entrantes y salientes en todos los componentes de RDMA.

Número de transacciones en cada controlador DRAM.

9
3.8. Streaming benchmark
En el Ejercicio 1 se os pide que confeccionéis el modelo roofline de la arquitectura de partida que
os presentaremos en la Sección 5 y posteriormente situéis en este el benchmark conv2d. Para ello,
previamente, necesitaréis calcular, por un lado, la capacidad de operaciones en punto flotante de la
arquitectura GPU de partida (FLOPS), y por otro lado, su ancho de banda de memoria (GB/s).
Para medir este último valor utilizaremos un benchmark dentro de la suite de benchmarks
denominada Streaming Benchmarks. Más concretamente, el benchmark que vamos a utilizar es
elementwise copy stride. Vamos a emplear este ya que reporta el mayor ancho de banda al
realizar peticiones a la memoria global en bloques contiguos (los accesos a memoria global por parte
de los hilos pueden ser ‘unidos’ o coalesced, lo que permite aprovechar de la mejor manera posible el
ancho de banda de memoria).

Figura 3: Accesos a memoriga global coalesced

Los accesos coalesced en una GPU se refieren a la forma en que los hilos (work-items) en un bloque
(work-group) de una GPU acceden a la memoria global. Este tipo de accesos se producen cuando los
hilos en un bloque acceden a posiciones de memoria contiguas en la memoria global. En otras palabras,
en los accesos coalesced, los hilos solicitan datos de la memoria global en bloques contiguos y no en
ubicaciones de memoria aleatorias (ver Figura 3). Este tipo de accesos son importantes para mejorar
el rendimiento en la GPU, ya que permiten una recuperación más eficiente de los datos de la memoria
global (ver Figura 4).
Este benchmark distribuye el tamaño del vector entre todos los work-items, de todas las CUs (en
nuestro caso -cus=64), de tal forma que, las wavefronts al ejecutar dicha instrucción, realizan accesos
coalesced a memoria global.

Figura 4: Accesos a memoriga global coalesced vs uncoalesced [3]

Es importante destacar que para conseguir una utilización completa (o lo mayor que podamos), es
necesario saturar la GPU lanzando varias iteraciones del mismo kernel y con un tamaño considerable.
Has de tenerlo en cuenta ya que la ejecución de este benchmark puede durar alrededor de 10 minutos
(dependiendo de tu máquina).
Finalmente, vamos a proceder a ejecutarlo y a ver cómo podemos calcular el ancho de banda. Para
este ejemplo vamos a usar la arquitectura por defecto de MGPUSim. Cuando lo calculéis vosotros más

10
adelante para la configuración de partida que usaremos en la práctica es muy probable que este valor
sea muy diferente.

root@20018f8015f7:/mgpusim# cd samples/stream/elementwise_copy_stride/
root@20018f8015f7:/mgpusim/samples/stream/elementwise_copy_stride# go build
root@20018f8015f7:/mgpusim/samples/stream/elementwise_copy_stride# ls -l
total 26532
-rwxr-xr-x 1 root root 27163510 Apr 3 17:27 elementwise_copy_stride
-rw-r--r-- 1 root root 855 Apr 3 17:27 main.go
root@20018f8015f7:/mgpusim/samples/stream/elementwise_copy_stride#
./elementwise_copy_stride -timing -parallel -cus=64
Monitoring simulation with http://localhost:33573
elementwiseCopyStride 0.0937500000 256
root@20018f8015f7:/mgpusim/samples/stream/elementwise_copy_stride#

El uso del argumento -parallel va a permitir que Go utilice unos hilos-ligeros denominados
Goroutines para ejecutar procesos de la aplicación de forma paralela.
La salida del programa por la terminal nos muestra en GB el tamaño de datos con el que se ha
operado (este kernel mueve datos de un vector A a un vector C y lo devuelve a la CPU). Para calcular
el ancho de banda de nuestra arquitectura, basta con dividir este valor entre el tiempo de ejecución
del Kernel, CommandProcessor, kernel time (tiempo virtual). Este último valor lo obtenemos
del fichero de estadı́sticas (metrics.csv), lı́nea 2.

root@20018f8015f7:/mgpusim/samples/stream/elementwise_copy_stride# ls -l
total 26544
-rwxr-xr-x 1 root root 27168458 Apr 3 17:27 elementwise_copy_stride
-rw-r--r-- 1 root root 855 Apr 3 17:27 main.go
-rw-rw-r-- 1 root root 154 Apr 3 17:41 metrics.csv
root@20018f8015f7:/mgpusim/samples/stream/elementwise_copy_stride# cat metrics.csv
, where, what, value
0, Driver, kernel_time, 0.000247938000
1, Driver, total_time, 0.000789882000
2, GPU[1].CommandProcessor, kernel_time, 0.000246171000

BW (GB/s) = Size(GB)/T (s)

BW (GB/s) = 0,0937500000/0,000246171000 = 380,83 GB/s


Este será el procedimiento habitual para calcular el ancho de banda de nuestra arquitectura simu-
lada. Es muy importante recordaros que dependiendo de vuestra configuración, ası́ tendréis
que modificar el argumento de CUs a utilizar, con el objetivo de saturar la GPU. Existe un se-
gundo argumento denominado -wfpoolsize=40, que puesto que no vais a modificar la arquitectura
interna de la CU, se puede obviar (por defecto, cada SIMD tiene una cola de wavefronts de tamaño
10, y dado que tenemos 4 SIMDs, hay un total de 4 wavefront pools).

3.9. Cómo configurar MGPUSim


Una vez que hemos revisado los distintos directorios que componen el simulador (Sección 3.5), es
importante identificar los directorios y ficheros que necesitamos modificar para realizar la práctica.
En particular, se requerirá modificar los ficheros ubicados en los directorios de samples/runner y
timing. Es importante mencionar que cualquier modificación que se realice en estos archivos tendrá
un impacto directo en el rendimiento y en los resultados obtenidos en la evaluación de la GPU simulada.

samples/runner/: contiene el driver de simulación (este objeto se encarga de la conexión


kernel–GPU) y la propia GPU a simular.

11
root@20018f8015f7:/mgpusim# cd samples/runner/
root@20018f8015f7:/mgpusim/samples/runner# ls -l
total 100
-rw-r--r-- 1 root root 1580 Mar 30 15:54 dramtracer.go
-rw-r--r-- 1 root root 5578 Mar 30 15:54 emugpubuilder.go
-rw-r--r-- 1 root root 3736 Mar 30 15:54 emuplatform.go
-rw-r--r-- 1 root root 1268 Mar 30 15:54 insttracer.go
-rw-r--r-- 1 root root 945 Mar 30 15:54 metrics.go
-rw-r--r-- 1 root root 1236 Mar 30 15:54 platform.go
-rw-r--r-- 1 root root 23466 Mar 30 15:54 r9nanobuilder.go
-rw-r--r-- 1 root root 23726 Mar 30 15:54 runner.go
-rw-r--r-- 1 root root 12153 Mar 30 15:54 shaderarray.go
-rw-r--r-- 1 root root 11526 Mar 30 15:54 timingplatform.go
root@20018f8015f7:/mgpusim/samples/runner#

Para comprender el flujo de funcionamiento y la interacción entre los diferentes ficheros, podemos
tomar como ejemplo cualquier benchmark. El primer paso que se realiza es:
1 runner := new(runner.Runner).ParseFlag().Init()
Listing 1: Creación del runner de simulación en samples/fir/fir.go

Esta función Init() llama al fichero samples/runner/runner.go, donde dependiendo del


tipo de emulación (timing o emu) va a crear una plataforma distinta. Como ya hemos comen-
tado hasta el momento, todas las simulaciones que hagamos serán con el argumento -timing,
por ende, estaremos creando la buildTimingPlatform().
Si navegamos a esta función buildTimingPlatform(), veremos que tiene el siguiente aspecto:
1 b := MakeR9NanoBuilder().
2 WithNumGPU(r.GPUIDs[len(r.GPUIDs)-1]).
3 WithLog2PageSize(24)
4
5 ...
6
7 r.platform = b.Build()
8

Listing 2: Función buildTimingPlatform() en samples/runner/runner.go

Vamos a centrarnos en el primer conjunto de lı́neas (1–3); MGPUSim está programado de forma
que MakeR9NanoBuilder() creará una GPU con unos valores por defecto (ver Listing 3).
Sin embargo, al utilizar la palabra clave WithParametro(), podremos modificar los parámetros
base por defecto.
1 func MakeR9NanoBuilder() R9NanoPlatformBuilder {
2 b := R9NanoPlatformBuilder{
3 numGPU: 4,
4 numSAPerGPU: 16,
5 numCUPerSA: 4,
6 log2PageSize: 12,
7 traceVisStartTime: -1,
8 traceVisEndTime: -1,
9 }
10 return b
11 }
Listing 3: Creación de una R9 Nano en samples/runner/timingplatform.go

WithNumGPU() y WithLog2PageSize() sobrescriben el valor por defecto de 4 y 12, respec-


tivamente.

12
Desde la función MakeR9NanoBuilder(), ver Listing 3, podemos modificar el número de
Shader Arrays, SA, y número de Compute Units, CUs, por SA.
Una vez alcanzado este punto y antes de continuar con la segunda parte, es importante destacar
que hemos creado una plantilla de GPU con unos valores base (por ejemplo, 64 CUs, tamaño de
página de 212 bytes, etc.). Luego, con la función b.Build(), dependiendo de los componentes
que hayamos decidido utilizar, crearemos nuestra GPU personalizada.
1 func (b R9NanoPlatformBuilder) Build() *Platform {
2 ...
3
4 b.globalStorage = mem.NewStorage(uint64(1+b.numGPU) * 4 * mem.GB)
5
6 mmuComponent, pageTable := b.createMMU(b.engine)
7
8 gpuDriver := b.buildGPUDriver(pageTable)
9
10 gpuBuilder := b.createGPUBuilder(b.engine, gpuDriver, mmuComponent)
11
12 ...
13
14 b.createGPUs(
15 rootComplexID, pcieConnector,
16 gpuBuilder, gpuDriver,
17 rdmaAddressTable, pmcAddressTable)
18
19 ...
20 }
Listing 4: Función Build() en samples/runner/timingplatform.go

Durante la ejecución de este código, se llevan a cabo diversas acciones. En primer lugar, se crea
una memoria global de tamaño 4 GB y se genera la unidad de gestión de memoria (MMU). Luego,
se construye el controlador de la GPU (b.buildGPUDriver()). A continuación, se llevan a ca-
bo los dos pasos más importantes de la creación de la GPU simulada: b.createGPUBuilder()
y b.createGPUs().

1. b.createGPUBuilder(): utilizando el constructor de la R9 Nano, establecemos los


parámetros de nuestra GPU.
1 func (b *R9NanoPlatformBuilder) createGPUBuilder(...) R9NanoGPUBuilder {
2 gpuBuilder := MakeR9NanoGPUBuilder().
3 WithEngine(engine).
4 WithMMU(mmuComponent).
5 WithNumCUPerShaderArray(b.numCUPerSA).
6 WithNumShaderArray(b.numSAPerGPU).
7 WithNumMemoryBank(16).
8 WithLog2MemoryBankInterleavingSize(6).
9 WithLog2PageSize(b.log2PageSize).
10 WithGlobalStorage(b.globalStorage).
11 WithFreq(1 * sim.GHz)
12
13 ...
14 }
Listing 5: Función createGPUBuilder() en samples/runner/timingplatform.go

Esta función sigue el mismo estilo que la del Listing 3. En concreto, la función
MakeR9NanoGPUBuilder() confecciona una R9 Nano con unos parámetros por defec-
to. A través de las funciones WithParametro(), podemos modificar los valores por defecto
de dichos parámetros, usando los que previamente hemos configurado (estos provienen del
objeto b, como por ejemplo, b.numCUPerSA, b.numSAPerGPU ) y con nuevos parámetros
(por ejemplo, un InterleavingSize, cuántos bancos de memoria DRAM queremos utilizar,
etc.)

13
Para finalizar este primer punto, podemos ver los valores por defecto de nuestra
R9NanoBuilder si nos dirigimos a la función MakeR9NanoGPUBuilder():
1 func MakeR9NanoGPUBuilder() R9NanoGPUBuilder {
2 b := R9NanoGPUBuilder{
3 freq: 1 * sim.GHz,
4 numShaderArray: 16,
5 numCUPerShaderArray: 4,
6 numMemoryBank: 16,
7 log2CacheLineSize: 6,
8 log2PageSize: 12,
9 log2MemoryBankInterleavingSize: 12,
10 l2CacheSize: 2 * mem.MB,
11 dramSize: 4 * mem.GB,
12 }
13 return b
14 }
Listing 6: Función MakeR9NanoGPUBuilder() en samples/runner/r9nanobuilder.go

Como se observa; aquellos valores por defecto (como el número de SAs, CU por SA, ta-
maño de página, etc) los modificamos haciendo uso de las funciones With previamente
mencionadas.
Para finalizar con lo anterior, a la hora de confeccionar vuestras GPUs tendréis que modi-
ficar aquellos valores que aparecen en el Listing 2, Listing 3 y Listing 5, evitando
modificar los parámetros que correspondan a nombres de variables, ya que estos podrı́an
ser utilizados en funciones posteriores y os podrı́a generar errores.
2. b.createGPUs(): volviendo al Listing 4, en esta función procedemos a construir nuestra
GPU una vez que ya está completamente configurada (en el caso de tener varias GPUs,
aquı́ las crearı́amos y las conectarı́amos entre sı́).
1 func (b *R9NanoPlatformBuilder) createGPUs(...) {
2 lastSwitchID := rootComplexID
3 for i := 1; i < b.numGPU+1; i++ {
4 ...
5
6 b.createGPU(i, gpuBuilder, gpuDriver,
7 rdmaAddressTable, pmcAddressTable,
8 pcieConnector, lastSwitchID)
9 }
10 }
Listing 7: Función createGPUs() en samples/runner/timingplatform.go

Si observamos esta función (b.createGPU()), podemos ver el último nivel antes de la


creación de la GPU:
1 func (b *R9NanoPlatformBuilder) createGPU(...) *GPU {
2 name := fmt.Sprintf("GPU[ %d]", index)
3 memAddrOffset := uint64(index) * 4 * mem.GB
4
5 gpu := gpuBuilder.
6 WithMemAddrOffset(memAddrOffset).
7 Build(name, uint64(index))
8
9 gpuDriver.RegisterGPU(
10 gpu.Domain.GetPortByName("CommandProcessor"),
11 driver.DeviceProperties{
12 CUCount: b.numCUPerSA * b.numSAPerGPU,
13 DRAMSize: 4 * mem.GB,
14 },
15 )
16 ...
17 }
Listing 8: Función createGPU() samples/runner/timingplatform.go

14
Importante destacar que si hemos modificado el tamaño de la memoria global
(b.globalStorage, ver Listing 4), de la misma manera tendremos que modificar esta
función (memAddrOffset y DRAMSize).
Para finalizar, al revisar la función Build(), podemos observar el proceso real de cons-
trucción de nuestra GPU, donde ya todos los parámetros y conexiones han sido establecidos
previamente.
1 func (b R9NanoGPUBuilder) Build(name string, id uint64) *GPU {
2 b.createGPU(name, id)
3 b.buildSAs()
4 b.buildL2Caches()
5 b.buildDRAMControllers()
6 b.buildCP()
7 b.buildL2TLB()
8
9 b.connectCP()
10 b.connectL2AndDRAM()
11 b.connectL1ToL2()
12 b.connectL1TLBToL2TLB()
13
14 b.populateExternalPorts()
15
16 return b.gpu
17 }
Listing 9: Función Build en samples/runner/r9nanobuilder.go

Como se observa, se crea la GPU, los SAs (incluyendo todas las CUs), las caches (L1,
L2), controladores de memoria (DRAM), etc. Se puede ver una imagen a alto nivel (en
terminologı́a MGPUSim) en la Figura 5, de cual serı́a el resultado final de nuestra GPU.

Figura 5: R9 Nano en MGPUSim

Para mantener el boletı́n lo más conciso posible, la programación en MGPUSim es simple y


clara. Por lo tanto, se sugiere que se revisen las funciones para comprender su funcionalidad
a alto nivel (b.buildSAs(), b.buildDRAMControllers(), etc.). Es posible que se
requiera navegar por el simulador para modificar ciertos valores y completar los ejercicios.
Ahora que conoces los directorios y los archivos que se deben modificar, la tarea es investigar
un poco y modificar los valores pertinentes.

15
timing/: es uno de los directorios más importantes del simulador, ya que contiene todos los
componentes de la GPU que estamos simulando (p.e. si decimos que estamos usando 64 CUs,
estamos usando todos los componentes dentro de timing/cu, para simular esas 64 unidades de
CU).
Como ya hemos mencionado, MGPUSim utiliza por debajo el simulador de eventos denominado
Akita. Cada componente que simulamos en la Figura 5 tiene varias unidades, cada una imple-
menta un pipeline, de tal forma que Akita sabe qué ocurre en cada Tick. Un tick puede verse
como una actualización del estado de cualquiera de los componentes/unidades del simulador. Si
nos fijamos en la CU, esta implementa varias unidades:

root@20018f8015f7:/mgpusim# cd timing/cu/
root@20018f8015f7:/mgpusim/timing/cu# ls -l
total 328
-rw-r--r-- 1 root root 2499 Mar 30 15:54 branchunit.go
-rw-r--r-- 1 root root 2444 Mar 30 15:54 branchunit_test.go
-rw-r--r-- 1 root root 259 Mar 30 15:54 coalescer.go
-rw-r--r-- 1 root root 19666 Mar 30 15:54 computeunit.go
...
-rw-r--r-- 1 root root 6350 Mar 30 15:54 vectormemoryunit.go
-rw-r--r-- 1 root root 6193 Mar 30 15:54 vectormemoryunit_test.go
-rw-r--r-- 1 root root 1077 Mar 30 15:54 wavefrontpool.go
-rw-r--r-- 1 root root 248 Mar 30 15:54 wfarbiter.go
-rw-r--r-- 1 root root 5669 Mar 30 15:54 wfdispatcher.go
-rw-r--r-- 1 root root 1669 Mar 30 15:54 wfdispatcher_test.go
-rw-r--r-- 1 root root 635 Mar 30 15:54 wfdispatchevent.go
root@20018f8015f7:/mgpusim/timing/cu#

Y si nos fijamos en la vectormemoryunit.go, su función Run(), que es la


que se llamará cada vez que ocurra un Tick en la simulación, implementa 4
etapas de pipeline (u.sendRequest(now), u.transactionPipeline.Tick(now),
u.instToTransaction(now) y u.instructionPipeline.Tick(now)):
1 func (u *VectorMemoryUnit) Run(now sim.VTimeInSec) bool {
2 madeProgress := false
3 madeProgress = u.sendRequest(now) || madeProgress
4 madeProgress = u.transactionPipeline.Tick(now) || madeProgress
5 madeProgress = u.instToTransaction(now) || madeProgress
6 madeProgress = u.instructionPipeline.Tick(now) || madeProgress
7 return madeProgress
8 }
Listing 10: Run() de la VectorMemory Unit

A priori, no es necesario que modifiques nada en este directorio timing/. Sin embargo,
si encuentras algún comportamiento o ejecución inesperada de alguna unidad, puede ser bueno
que revises cómo se ha implementado.

idealMemController.go : al comenzar, mencionamos que MGPUSim está compuesto por


varios módulos de Akita (mem, noc, vis, etc.) MGPUSim no modela fielmente la tecnologı́a
HBM (controlador de memoria principal), por ello, hemos decidido utilizar un controlador de
memoria ideal, el cual asume una serie de ciclos de latencia y tiene un tamaño por defecto.
Si queremos modificar este fichero, lo podemos hacer en el siguiente directorio:

/go/pkg/mod/gitlab.com/akita/mem/[email protected]/idealmemcontroller

16
Las modificaciones pertinentes que queramos añadir se harı́an al final del mis-
mo idealMemController.go: (c.Latency, c.MaxNumTransaction y la frecuencia,
500*sim.MHz). Como consejo final, destacar que el número de transacciones siempre deberı́a
de ser mayor que la latencia.
1 func New(...) *Comp {
2 c := new(Comp)
3
4 c.Latency = 120
5 c.MaxNumTransaction = 256
6 c.TickingComponent = sim.NewTickingComponent(name, engine, 500*sim.MHz, c)
7
8 c.Storage = capacity
9 c.topPort = sim.NewLimitNumMsgPort(c, 16, name+".TopPort")
10 c.AddPort("Top", c.topPort)
11
12 return c
13 }
Listing 11: Controlador de memoria ideal (idealmemcontroller.go)

4. conv2d
En esta sección presentaremos el benchmark que se utilizará para la realización de la práctica,
conv2d. La idea es que este benchmark no se modifique, a menos que sea absolutamente necesario y
de manera justificada. Este benchmark se utilizará principalmente para construir el modelo roofline
que se pedirá en los posteriores enunciados. Es importante que se entienda el funcionamiento del
benchmark para poder entender los resultados que se obtendrán en la práctica y poder analizarlos
adecuadamente.
En deep learning, una capa convolucional es una capa de una red neuronal que procesa datos
de entrada (a los que se le suele llamar input feature-map, o, ifmap) con un filtro para extraer ca-
racterı́sticas. La capa devuelve un resultado de salida, al que se suele llamar output feature-map, o,
ofmap. En lugar de utilizar una matriz de pesos densos como en una capa totalmente conectada, una
capa convolucional utiliza un conjunto de filtros que se aplican a partes de los datos de entrada. Estos
filtros pueden detectar patrones especı́ficos en los datos de entrada, como bordes, esquinas o texturas.
Las capas convolucionales son ampliamente utilizadas en aplicaciones de visión por computador, como
la detección de objetos, el reconocimiento facial y la clasificación de imágenes. Si tenéis más interés,
os referimos a algunos enlaces como: 1, 2 o 3 (este último, muy recomendado).
El simulador cuenta con benchmarks de redes neuronales complejas como VGG-16 (arquitectura de
red neuronal convolucional profunda utilizada en el procesamiento de imágenes que consiste en 16 capas
convolucionales y de agrupación, seguidas de tres capas totalmente conectadas para la clasificación
final) o LeNet (una de las primeras redes convolucionales desarrolladas en el aprendizaje profundo, es
una red neuronal de 7 capas que se utiliza principalmente para la clasificación de dı́gitos escritos a
mano, seguramente os suene de algo la Práctica 1). Sin embargo, dado que la simulación de modelos
completos requerirı́a bastante de tiempo (varias horas), vamos a concentrarnos en la optimización de
la arquitectura para una única capa convolucional, con un kernel de 2 dimensiones. Es decir, vamos a
lanzar un work-group con work-items en las 2 dimensionesk, tanto en el eje X como Y.
La convolución, en realidad, se realiza como una multiplicación de matrices, para lo cual se trans-
forman la entrada y los filtros utilizando la técnica de im2col. Es decir, en lugar de realizar la operación
de convolución propiamente dicha, se transforman los datos de entrada y los filtros en forma de ma-
trices y se realiza una multiplicación de matrices. Esto permite aprovechar las propiedades matriciales
de la operación y realizarla de manera más eficiente en términos de tiempo y recursos. Podéis ver el
tipo de transformación que se realiza en la diapositiva 57 (aproximadamente) del Tema 1 de teorı́a y,
a partir de ahı́, calcular el número de operaciones de coma flotante por segundo (FLOPs).
Para ejecutar el benchmark, bastará con dirigirnos a su carpeta correspondiente y compilarlo. El
tiempo necesario para completar cada simulación dependerá de vuestra máquina, pero para tratar de
acelerarlo os recomendamos ejecutarlo con el argumento -parallel:

17
root@20018f8015f7:/mgpusim# cd samples/conv2d/
root@20018f8015f7:/mgpusim/samples/conv2d# go build
root@20018f8015f7:/mgpusim/samples/conv2d# ls -l
total 29832
-rwxr-xr-x 1 root root 30543521 Apr 4 15:17 conv2d
-rw-r--r-- 1 root root 1292 Mar 30 15:54 main.go
root@20018f8015f7:/mgpusim/samples/conv2d# ./conv2d -timing -parallel
Monitoring simulation with http://localhost:36957
Im2Col input [1 1 28 28], kernel [3 3], stride [1 1], padding [0 0],
dilation [1 1], output [9 676]
Kernel Im2Col, Start 0.0000105140, Virtual Time: 1.0612e-05,
Real Time: 352.47ms
Kernel Transpose, Start 0.0000391610, Virtual Time: 1.3574e-05,
Real Time: 343.73ms
Kernel Gemm, Start 0.0000527350, Virtual Time: 1.0211e-05,
Real Time: 378.83ms
Kernel Transpose, Start 0.0000654650, Virtual Time: 1.5629e-05,
Real Time: 676.86ms
root@20018f8015f7:/mgpusim/samples/conv2d#

El back-end del benchmark se encuentra en benchmarks/dnn/conv2d, aunque como se ha


comentado al principio, no será necesario modificarlo.
A la hora de calcular la intensidad aritmética hemos de saber el cómputo que se hace en esta capa
en FLOPs (1), ası́ como, el total de bytes que se leen y escriben de memoria (2).
Para (1), se observa que tenemos dos canales de entrada con un ifmap de 64x64. Sobre estos canales
de entrada va a operar un filtro de 32x32. Este filtro va a hacer una operación MAC sobre ambos
canales y va a almacenar el resultado en el lugar correspondiente del ofmap. La aplicación de im2col
transformará el filtro en una matriz de 2048 × 11 , y el ifmap en una matriz 1089 × 20482 . En total,
(2048 × 1 + 1089 × 2048) se requieren 2.232.320 elementos, que si son de simple precisión (4 bytes
por elemento), estarı́amos hablando de un total de 8.929.280 bytes leidos.
Para (2), para calcular los bytes que se leen de memoria, basta con saber el tipo de dato (en
este caso floats) y las operaciones. En total se calculan 33x33 elementos, y cada uno de ellos requiere
32x32x2 multiplicaciones (y otras tantas sumas): FLOPs = 33x33x(32x32x2x2) = 4460544.
Con lo que la intensidad aritmética vendrı́a a ser 0.5. A pesar de que este dato se os proporcione,
se valorará positivamente un razonamiento justificado de este número; podéis apoyaros en los ejemplos
de conv2d que se os proporciona en el Tema 1.

1
2048 se obtiene del producto de 32 × 32 del filtro multiplicado por el número de canales, es decir, dos
2
1089 se obtiene mediante el número de desplazamientos permitidos del filtro de 32x32 sobre el ifmap de 64x64

18
5. Arquitectura simulada de partida
Todo lo que se ha explicado y mostrado hasta el momento sobre MGPUSim se ha basado en
el código original sin ninguna modificación. Ahora, nos centraremos en describir la Figura 6, que
corresponde con la arquitectura de la que partiremos (y que no está optimizada) para esta práctica.

Figura 6: AMD R9 Nano no optimizada

A simple vista, los cambios realizados en la arquitectura son bastante notables, incluyendo una
reducción en el número de CUs por SA, ası́ como de SAs y caches L1 y L2, entre otros. Además, se han
ajustado parámetros internos de MGPUSim, tales como: el tamaño de página (216 ), la frecuencia de
reloj de la GPU a 500 MHz, y se han realizado modificaciones al controlador de memoria, especı́fica-
mente en la tecnologı́a HBM, con una latencia de acceso a memoria de 250 ciclos, un número máximo
de transacciones de 128 y una frecuencia reducida a 200 MHz.
Llegados a este punto ya deberı́ais de ser capaces de saber cómo modificar estos parámetros (ver
Sección 3.9). Para ayudaros en la realización de la práctica, a continuación, os resumimos los paráme-
tros que deberéis considerar, aunque no estáis limitados a estos y aquellos que deseéis explorar y
mejorar en el simulador por vuestra cuenta, podrán ser considerados con puntuación adicional (siem-
pre y cuando la modificación esté justificada y el resultado ası́ lo demuestre). Dado que esta práctica
es bastante compleja, esperamos que esta información os resulte útil y os permita enfocaros en los
aspectos más importantes de la simulación y explorar otros parámetros.

Número de CUs (Compute Units) por SA (Shader Array).

Número de SAs (Shader Arrays).

Tamaño de página (PageSize).

Tamaño del Interleaving.

Número de bancos (MemoryBanks); estos incluyen tanto los de la L2, como la DRAM.

19
Frecuencia de reloj de la GPU.

Controlador de memoria ideal:

• Latencia.
• Número máximo de transacciones.
• Frecuencia de la memoria.

20
6. Ejercicios propuestos
Imagina que eres un arquitecto hardware y que acabas de montar tu propia empresa de diseño de
GPUs. Tu empresa ha ganado reputación en el mercado gracias a su capacidad para innovar y crear
arquitecturas GPU de alto rendimiento que se adaptan a las necesidades de los clientes más exigentes.
Un dı́a, recibes una propuesta de trabajo muy interesante: una gran empresa de tecnologı́a te ha
contratado para desarrollar tres GPUs con caracterı́sticas distintas, cada una con un objetivo diferente.
La primera GPU debe estar orientada al rendimiento, sin tener en cuenta el consumo energético (vamos
a llamar a este modelo High-Performance o HP). La segunda GPU debe estar orientada al consumo
energético, para aquellos casos en los que las prestaciones sean lo menos importantes y lo se busque es
consumir la menor cantidad posible de energı́a (llamemos a este otro Low-Power o LP). Finalmente,
la tercera GPU debe ser una hı́brida de las dos anteriores, que busque el mejor rendimiento y el menor
consumo energético (llamémosla Well-Balanced o WB).
Tú y tu equipo de ingenieros tendréis que trabajar juntos para diseñar y construir estas tres GPUs
que satisfagan las necesidades del cliente y que sean rentables para tu empresa. Para diseñar y probar
las tres tarjetas gráficas, utilizaréis una herramienta de simulación para GPUs llamada MGPUSim.
A pesar de que esta herramienta es muy novedosa en el mercado y ha despertado mucho interés
entre los arquitectos de hardware y desarrolladores de software, debido a la complejidad del diseño del
hardware en este simulador, muchas personas tienen miedo de usarlo y prefieren otras herramientas
más sencillas. Sin embargo, tú y tu equipo habéis decidido aceptar este desafı́o y utilizar MGPUSim
para diseñar y probar vuestras GPUs. Con esta herramienta, podréis simular el comportamiento de
vuestras tarjetas gráficas en diferentes escenarios con distintos benchmarks (es suficiente con conv2d)
y analizar su rendimiento y consumo de energı́a en tiempo real, ya que la herramienta ofrece una salida
en formato CSV de las estadı́sticas de la arquitectura que habéis simulado.
Es importante destacar que la configuración inicial del simulador MGPUSim no está
optimizada para los diseños que debéis probar. Por lo tanto, vuestro primer paso debe ser
revisar detalladamente los valores de salida del simulador y confeccionar un diagrama tipo modelo
roofline (Ejercicio 1). Solo ası́ podréis estar seguros de que los resultados obtenidos en el simulador
son confiables y representan con precisión el rendimiento y la eficiencia de vuestros diseños de GPU.
¡Pero tenéis que estar preparados para enfrentar la complejidad del diseño en MGPUSim y para
resolver los problemas que puedan surgir en el camino! Solo ası́ podréis construir GPUs innovadoras
que satisfagan las necesidades del cliente y que sean rentables para vuestra empresa.
Sin embargo, es importante que tengáis en cuenta que vuestra empresa no dispone de recursos
ilimitados (cada componente tiene un coste, ver Tabla 1). Por lo tanto, es esencial que diseñéis vuestras
GPUs dentro de un presupuesto máximo establecido. Tras varias reuniones llegamos a un acuerdo sobre
los requerimientos de cada uno de los aceleradores y resumimos toda la información en la Tabla 2.
Para cumplir los 3 contratos de forma correcta deberemos de crear 3 arquitecturas GPU (tres diseños
hardware), y, ser capaz de poder ejecutar los benchmarks conv2d y elementwise copy stride.
Tened en cuenta estos lı́mites al seleccionar los componentes y diseñar las tarjetas gráficas, ya que
vuestro objetivo es crear productos que sean rentables y asequibles para vuestros clientes potenciales.

Componente Coste (§)


ComputeUnit (CU) 5
PageSize 3 por unidad
Interleaving 3 por unidad
Bancos de memoria 5
Frecuencia de la GPU 30 cada 100 MHz
Ideal Memory Controller
Latencia e−latencia/100 ∗ 2E 3
Transacciones 1.5
Frecuencia de la memoria 60 cada 100 MHz

Tabla 1: Tabla de costes de nuestros componentes.

En la tabla anterior no se especifica el costo por SA, en su lugar, determı́nalo en base al número
de CUs totales que tengas. Por ejemplo, si tuvieses 2 CUs por SA y en total has especirficado 16 SAs,

21
equivaldrı́a a un coste de 10 (§) por SA, haciendo un total de 160 (§).
Para el cálculo de la latencia, obvia los decimales y utiliza únicamente la parte entera, puedes
calcular este coste de manera muy rápida en cualquier intérprete de Python online, utilizando el
siguiente script:
1 import math
2
3 latencia = 250
4 resultado = math.exp(-latencia/100)*2000
5 print(int(resultado))

Además de esto, es importante que tengáis en cuenta el consumo energético de vuestros modelos.
Para ello, vamos a presentar un modelo energético que tendréis que tener en consideración a la hora
de hacer vuestras evaluaciones.
Vamos a definir la energı́a total consumida por una GPU como la suma de tres componentes
principales: energı́a dinámica, energı́a estática y energı́a de transferencia.

Energı́a dinámica – Se refiere a la energı́a consumida por el procesamiento de datos y se puede


calcular como el producto de la frecuencia de la GPU (en GHz), el número de unidades de
cómputo totales y la energı́a por operación, que lo establecemos a 3 vatios (W), teniendo en
cuenta operaciones MAC.

Energı́a estática – Se refiere a la energı́a consumida por los componentes que están activos pero
no procesan datos, como los bancos de memoria y la lógica de control. Esta energı́a estática
puede calcularse como el producto del tamaño de página (en potencia de 2, p.e. 16 o 24), el
número de bancos de memoria y la energı́a por bit (1,5 W).

Energı́a de transferencia – se refiere a la energı́a consumida por la transferencia de datos entre la


GPU y la DRAM, y puede calcularse como el producto de la frecuencia de la DRAM (en MHz)
y la energı́a por transferencia (2 W), entre la latencia de acceso.

Teniendo en cuenta estos tres componentes, el modelo de energı́a total de la GPU se puede expresar
como: Etotal = Edinamica + Eestatica + Etransf erencia . Este modelo puede ser utilizado para estimar la
energı́a total consumida por una GPU dadas sus caracterı́sticas técnicas y los parámetros mencionados.
A continuación os presentamos la tabla con los lı́mites en coste, consumo y rendimiento que cada
modelo debe cumplir:
Modelo Coste (§) Consumo (W) TFLOPS
HP < 4000 ∞ > 35
LP < 1800 < 350 >3
WB < 2600 < 750 > 20

Tabla 2: Presupuesto de Coste (§), energı́a (W) y FLOPS mı́nimos que nuestros modelos deben cumplir.

Además, para presentar vuestros diseños a los clientes, se requiere que entreguéis un informe
cientı́fico (Ejercicio 3) en el que se expliquen en detalle las razones por las cuales habéis seleccio-
nado los valores y componentes utilizados en cada uno de los diseños de GPUs. Es importante que el
informe sea accesible a cualquier persona interesada en el diseño de hardware, sin importar su nivel de
experiencia en la materia. De esta manera, podréis demostrar a vuestros clientes que habéis realizado
un trabajo riguroso y bien pensado al diseñar estas tarjetas gráficas.
¿Estáis listos para este emocionante desafı́o? ¡Adelante!

1. (2.5p) Representa el modelo roofline para la arquitectura de partida y ubica en él la aplicación
conv2d. Para ello, debes tener en cuenta los valores de configuración de la arquitectura pre-
sentada en la Figura 6. Más concretamente, para calcular los FLOPS pico de la arquitectura,
recuerda lo explicado en la Sección 3.3 (FLOPs por ciclo para cada CU), y ten en cuenta las
caracterı́sticas de la arquitectura base mostradas en la Figura 6 (número de CUs y frecuencia
de la GPU).

22
Para obtener el ancho de banda de memoria, se utilizará el kernel de copia de memoria con stride
(elementwise copy stride) (ver Sección 3.8) con los siguientes parámetros (no te olvides
de ajustar el número de CUs a las mismas que usa tu modelo):

length – 4194304
Número de iteraciones – 2
Número de CUs – 16

Finalmente, para el cálculo de la intensidad aritmética (IA) de conv2d, ten en cuenta lo expli-
cado en la Sección 4. Los FLOPS obtenidos para conv2d podrás calcularlos a partir del valor
de kernel time que reporta MPGPUSim como resultado de la simulación.
No se permite modificar la configuración del simulador MGPUSim para este ejercicio. Deberás
utilizar los valores proporcionados y cualquier otra información relevante que puedas obtener a
partir de ellos para plantear el modelo roofline y analizar el rendimiento del benchmark conv2d.

2. (4.5p) Plantea los tres modelos de arquitectura GPU que se buscan y para cada uno, genera su
modelo roofline, ubicando en el mismo, el benchmark conv2d del ejercicio anterior.

High-Performance, HP – La configuración de alto rendimiento es una de las más de-


mandadas en el mercado, ya que es utilizada en aplicaciones que requieren un alto poder de
procesamiento, como la inteligencia artificial, el análisis de datos en tiempo real, la simula-
ción de sistemas complejos y muchas otras áreas. En este tipo de aplicaciones, la velocidad
de procesamiento es un factor clave y la GPU debe ser capaz de procesar grandes cantidades
de datos en muy poco tiempo. Por lo tanto, es importante diseñar una GPU que ofrezca
el máximo rendimiento posible, sin importar el consumo de energı́a o el tamaño de la
tarjeta gráfica.
Enumera que parámetros has modificado, y para cada uno de ellos, el valor que tenı́a
posteriormente (partiendo de la configuración por defecto no-optimizada) y el nuevo valor.
Además, acompaña dichas modificaciones con un pequeño texto de motivación a cerca
de porqué has decidido modificara dicho valor. Finalmente, cumplimenta la siguiente tabla
(recuerda tener en cuenta los lı́mites establecidos en la Tabla 2) y justifica cómo has obtenido
cada valor:
Coste (§) < 4000
Consumo (W) ∞
TFLOPS > 35

Tabla 3: Coste (§), Energı́a (W) y FLOPS del modelo High-Performance

Low-Power, LP – La configuración de bajo consumo es ideal para aplicaciones en las que


el consumo de energı́a es un factor crı́tico, como en dispositivos móviles, sistemas embebidos,
y dispositivos que funcionan con baterı́as. En estos casos, es importante diseñar una GPU
que pueda ofrecer un rendimiento aceptable, pero sin consumir demasiada energı́a, lo que
permitirá una mayor duración de la baterı́a y un funcionamiento más eficiente en general.
Al igual que en el ejercicio anterior, justifica tus respuestas y rellena la siguiente tabla:

Coste (§) < 1800


Consumo (W) < 350
TFLOPS >3

Tabla 4: Coste (§), Energı́a (W) y FLOPS del modelo Low-Power

Well-Balanced, WB – La configuración hı́brida es una solución ideal para aquellos casos


en los que se requiere tanto un alto rendimiento como una eficiencia energética óptima.

23
Por ejemplo, en el desarrollo de centros de datos o clusters de computación de alto ren-
dimiento, donde se necesita una gran cantidad de recursos computacionales para realizar
tareas complejas, pero al mismo tiempo se busca minimizar el consumo de energı́a y el
costo operativo. De esta manera, una GPU hı́brida puede ofrecer un alto rendimiento en
tareas que lo requieren, pero también puede reducir su consumo de energı́a en momentos
de baja carga de trabajo. Esto se traduce en un ahorro de costos significativo a largo plazo,
ası́ como en un menor impacto ambiental.
Finalmente, al igual que en el ejercicio anterior, justifica tus respuestas y rellena la siguiente
tabla:
Coste (§) < 2600
Consumo (W) < 750
TFLOPS > 20

Tabla 5: Coste (§), Energı́a (W) y FLOPS del modelo Well-Balanced

3. (2p) Elabora un informe cientı́fico en el que se justifiquen las tres configuraciones planteadas.
Más concretamente, en el informe debes explicar con detalle las razones por las cuales se han
seleccionado los valores incluidos en las Tablas 3, 4 y 5 para los parámetros hardware. Este
informe deberá organizarse en forma de artı́culo cientı́fico (puedes echar un vistazo a la referencia
[2], que puedes encontrar en la sección de recursos del Aula Virtual, en la que se explica cómo
organizar un artı́culo cientı́fico y qué incluir en cada uno de sus apartados) y deberá de ser
autocontenido (es decir, se deberá incluir un breve resumen acerca del problema a resolver,
arquitectura utilizada, requisitos pedidos en cada caso, cómo se han alcanzado dichos requisitos y
rendimiento/energı́a logrados en cada configuración). El informe se realizará en LATEX utilizando
la plantilla llncs.cls proporcionada dentro del fichero llncs2e.zip que encontrareis en la
zona de recursos del Aula Virtual. El informe tendrá una extensión aproximada de 6 páginas.

(1p) Además, se llevará a cabo una sesión de presentaciones (10 a 12 minutos más 5 de preguntas),
en la que cada uno de vosotros deberá presentar vuestros hallazgos, modelos y otros aspectos rele-
vantes de interés. Para dicha presentación se recomienda usar un formato PDF/PPT u otro similar y
adjuntarla a la práctica cuando la subáis al AV.
(Hasta 0.5p extra) Como extra, y solo extra, se os anima a realizar investigaciones más alla de los
lı́mites de la práctica, por ejemplo, estudios sobre el sistema de memoria 3D utilizado (HBM), procesos
litográficos de vuestras GPUs, tamaños reales (aproximados) de las arquitecturas propuestas, estudios
energéticos, etc.
A la hora de confeccionar vuestros modelos de GPU, no queremos que tratéis de obtener los
mejores modelos dentro de los lı́mites que os establecemos (p.e. mediante un script para explorar
todas las combinaciones posibles); en su lugar, premiaremos significativamente a los modelos realistas
y correctamente justificados con respecto a modelos GPU actuales. Si necesitáis/teneis que, superar
los lı́mites establecidos en algún caso, no habrı́a problema, siempre y cuando estuviese correctamente
justificado.

24
Bibliografı́a
1. Sun, Yifan and Baruah, Trinayan and Mojumder, Saiful A. and Dong, Shi and Gong, Xiang and
Treadway, Shane and Bao, Yuhui and Hance, Spencer and McCardwell, Carter and Zhao, Vincent
and Barclay, Harrison and Ziabari, Amir Kavyan and Chen, Zhongliang and Ubal, Rafael and
Abellán, José L. and Kim, John and Joshi, Ajay and Kaeli, David. ≪MGPUSim: Enabling Multi-
GPU Performance Modeling and Optimization≫. 10.1145/3307650.3322230. ISCA’19, Phoenix, AZ.
https://gitlab.com/akita/mgpusim

2. José M. Garcı́a. ≪Guı́a para la preparación de Informes Técnicos y Artı́culos de Investigación≫.


Informe técnico, Departamento de Ingenierı́a y Tecnologı́a de Computadores, Febrero 2009.

3. Liang, Yun and Cui, Zheng and Zhao, Shengkui and Rupnow, Kyle and Zhang, Yihao and Jones,
Douglas and Chen, Deming. (2012). Real-time implementation and performance optimization of
3D sound localization on GPUs. 10.1109/DATE.2012.6176610.

25

También podría gustarte