Práctica 04
Práctica 04
Facultad de Informática
Departamento de Ingenierı́a y Tecnologı́a de Computadores
Área de Arquitectura y Tecnologı́a de Computadores
Prácticas de
Curso 2024/2025
Índice
1. Introducción 2
1.1. Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2. Herramientas utilizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 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
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:
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.
Docker
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
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.
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.
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.
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).
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):
Para salir del contenedor basta con escribir exit. Posteriormente, para volver a conectarte, puedes
utilizar:
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 (/).
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
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.
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.
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.
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:
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.
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:
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).
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.
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
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
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
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().
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
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.
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#
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.
/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#
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.
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 bancos (MemoryBanks); estos incluyen tanto los de la L2, como la DRAM.
19
Frecuencia de reloj de la GPU.
• 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.
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 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).
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.
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
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
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