Universidad Complutense de Madrid Facultad de Informática EVALUACIÓN DE RENDIMIENTO Y EFICIENCIA ENERGÉTICA DE PROCESOS DE INFERENCIA EN GPUS PERFORMANCE AND ENERGY EFFICIENCY EVALUATION OF INFERENCE PROCESSES ON GPUS Trabajo Fin de Grado Grado en Ingenieŕıa Informática Autor Gonzalo Isla Llave Tutores Francisco D. Igual Peña Jorge Villarrubia Elvira Mayo 2024 Agradecimientos A mi tutor, Fran, por su infinita paciencia y ayuda en el trabajo, y a mi familia y amigos por todo su apoyo. Resumen La compañ́ıa NVIDIA se sitúa como un referente de innovación y desarrollo de GPUs, colocándo- se entre las compañ́ıas que más están creciendo en estos últimos meses. Con la nueva gama de GPUs Blackwell publicada este mismo año 2024, vemos la importancia que se le está dando a este sector, como un gran proyecto de futuro. El uso de gran cantidad de datos procedentes del auge de la inteligencia artificial hace que estas máquinas tengan tanta importancia. Parte de las investigaciones que se llevan a cabo son formas de sacar el máximo partido de estas máquinas debido a la cantidad inmensa de procesadores que contienen. Es esta misma premisa lo que se intenta conseguir con este trabajo, haciendo uso del bechmark MLPerf. A través del modo de trabajo Offline que proporciona el banco de trabajo, se estudian una serie de modelos de inferencia que van desde el reconocimiento automático de voz hasta la división de imágenes en 3D para ámbito biomédico. Estos modelos ya entrenados se analizan en base a su rendimiento en cuanto a latencia, throughput 1 y rendimiento energético. La tecnoloǵıa utilizada para contrastar resultados se basa en una NVIDIA A30 y una Jetson Orin AGX, haciendo uso de la herramienta nvpmodel que proporciona a esta última para limitar la potencia máxima consumida. Se ha conseguido ver cómo al aumentar el número de peticiones máximas que se le pasa al modelo, el número de muestras procesadas por segundo aumenta. Esto es debido a que las GPUs son capaces de paralelizar el gran número de peticiones entre sus los distintos cores. Por otro lado, se ha demostrado que la eficiencia energética mejora a medida que disminuye la potencia máxima de la máquina. Esto se debe a que en el proceso de inferencia no se está proporcionando una suficiente carga de trabajo, de forma que no se puede paralelizar en las máquinas de mayor potencia. Por último, cuando se estudia la latencia se ha contrastado que al aumentar el batch que se le pasa al modelo (i.e. el número de peticiones), cuando se fija el número de muestras esperadas al número de muestras resultantes, el tiempo de ejecución no vaŕıa. Palabras Clave: NVIDIA, MLPerf, Inferencia, A30, AGX, nvpmodel, rendimiento, GPU. El repositorio con las aportaciones, scripts y resultados obtenidos se encuentra en este enlace. 1Eficiencia computacional 3 https://github.com/GonzaloIslaLlave/TFG-Evaluacion-de-rendimiento-y-eficiencia-energetica-de-procesos-de-inferencia-en-GPUs.git Abstract NVIDIA has become a leading innovator in GPU development, positioning itself among the fastest-growing companies in recent months. With the release of the new Blackwell GPU series in 2024, we see the importance that is being placed on this sector, with a promising future ahead. The use of vast amount of data driven by the rise of artificial intelligence highlights the importance of these machines. Part of the ongoing research focuses on maximizing the performance due to the massive amount of cores they have. This same premise is carried through in this work, using MLPerf. With theOffline mode provided by the benchmark, a series of models are studied, ranging from automatic speech recognition to 3D image segmentation for biomedical applications. These pre-trained models are analyzed for their performance in terms of latency, throughput and energy efficiency. The technology used to compare the results is a NVIDIA A30 and a Jetson Orin AGX, using the nvpmodel tool provided by the latter to cap the maximum power consumption. It has been observed that as the maximum number of requests passed to the model increases, so does the rate of processed samples per second. This is due to the GPUs being able to parallelize the great number of requests among their different cores. On the other hand, it has also been observed that the energetic efficiency rises with decreasing maximum power of the machine. The reason for this is that during the inference process there would not be a sufficiently high workload, so parallelization in the highest power machines is not optimized. Lastly, in studying the latency it was apparent that increasing the batch size for the model (i.e., the number of requests), and fixing the number of expected samples to the number of resulting samples, the execution time does not vary. Key words: NVIDIA, MLPerf, Inference, A30, AGX, nvpmodel, benchamrk, GPU. The reposi- tory containing the contributions, scripts, and results obtained is located in this link. 4 https://github.com/GonzaloIslaLlave/TFG-Evaluacion-de-rendimiento-y-eficiencia-energetica-de-procesos-de-inferencia-en-GPUs.git Índice general 1 Introducción 9 1.1 Motivación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 1.2 Objetivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2 Introduction 12 2.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.2 Objectives . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3 Arquitecturas utilizadas 14 3.1 NVIDIA A30 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 3.2 Jetson AGX Orin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 4 MLPerf 16 4.1 Estructura general y escenarios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.2 Modelos disponibles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2.1 BERT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 4.2.2 RNN-T . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.2.3 3D-Unet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.2.4 ResNet50 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2.5 RetinaNet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4.2.6 GPT-J . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 4.2.7 DLRMV2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 5 Medición de potencia 22 5.1 Servidor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5.1.1 NVIDIA A30 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 5.1.2 Jetson AGX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 5.2 Cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 6 Diseño de los experimentos 28 6.1 Limitaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 7 Resultados Obtenidos 32 7.1 Bert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 7.1.1 Throughput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 7.1.2 Eficiencia Energética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 7.1.3 Latencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 7.2 Resnet50 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 7.2.1 Throughput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 7.2.2 Eficiencia energética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 7.2.3 Latencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 7.3 Rnnt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 5 ÍNDICE GENERAL 7.3.1 Througthput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 7.3.2 Eficiencia energética . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 7.3.3 Latencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 7.4 3D-Unet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 8 Conclusión final y trabajo futuro 44 9 Conclusion and future work 45 A Script para automatizar MLPerf 46 B Ejemplo de cliente PMLib utilizando ctypes 48 6 Índice de figuras 4.1 Representación del modelo LoadGen. [1] . . . . . . . . . . . . . . . . . . . . . . . . . 16 4.2 Mapa conceptual de las fases del modelo BERT. [2] . . . . . . . . . . . . . . . . . . . 19 4.3 Capas convolucionales del modelo 3D-Unet. [3] . . . . . . . . . . . . . . . . . . . . . 20 4.4 Representación de red piramidal con las dos subredes propuesta por Retinanet. [4] . 21 6.1 Estructura de directorios. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 7.1 Throughput hallado por cada valor de batch en el modelo Bert. . . . . . . . . . . . . 32 7.2 Potencia consumida en cada batch por el modelo Bert en NVIDIA A30. . . . . . . . 33 7.3 Potencia consumida en cada batch por el modelo Bert en Jetson Orin AGX. . . . . . 34 7.4 Potencia consumida en cada batch por el modelo Bert en Jetson Orin AGX con 15W potencia máxima. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 7.5 Latencia en cada batch para el modelo Bert. . . . . . . . . . . . . . . . . . . . . . . . 35 7.6 Throughput hallado por cada valor de batch en el modelo Resnet50. . . . . . . . . . 36 7.7 Potencia consumida en cada batch por el modelo Resnet50 en NVIDIA A30. . . . . . 37 7.8 Potencia consumida en cada batch por el modelo Resnet50 en Jetson Orin AGX. . . 37 7.9 Potencia consumida en cada batch por el modelo Resnet50 en Jetson Orin AGX con 15W potencia máxima. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 7.10 Eficiencia energética consumida en cada batch por el modelo Resnet50. . . . . . . . . 38 7.11 Latencia en cada batch para el modelo Resnet50. . . . . . . . . . . . . . . . . . . . . 39 7.12 Throughput hallado por cada valor de batch en el modelo Rnnt. . . . . . . . . . . . 40 7.13 Potencia consumida en cada batch por el modelo Rnnt en NVIDIA A30. . . . . . . . 41 7.14 Potencia consumida en cada batch por el modelo Rnnt en Jetson Orin AGX. . . . . 41 7.15 Potencia consumida en cada batch por el modelo Rnnt en Jetson Orin AGX con 15W potencia máxima. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 7.16 Latencia en cada batch para el modelo Rnnt. . . . . . . . . . . . . . . . . . . . . . . 42 7.17 Throughput hallado por cada valor de batch en el modelo 3D-Unet. . . . . . . . . . 43 7 Índice de tablas 4.1 Caracteŕısticas de los modos de MLPerf. [5] . . . . . . . . . . . . . . . . . . . . . . . 17 7.1 Comparación de latencia entre diferentes máquinas y tamaños de batch para Bert. . 34 7.2 Comparación de latencia entre diferentes máquinas y tamaños de batch para Resnet50. 38 7.3 Comparación de latencia entre diferentes máquinas y tamaños de batch para Rnnt. . 42 8 Caṕıtulo 1 Introducción 1.1. Motivación Desde la invención del primer ordenador se ha trabajado para mejorar el rendimiento del pro- cesamiento de datos, y los enfoques han sido muy variados. Al inicio se llevó a cabo aumentando la frecuencia de reloj y, de forma totalmente transparente al programador, se obtuvo una mejora sustancial. Sin embargo, debido a las corrientes de fuga y el aumento en la disipación de calor que produce aumentar la frecuencia, este sistema no puede llevarse a cabo de forma indefinida. Por otra parte, siguiendo la famosa Ley de Moore en 1965 [6], se ha conseguido durante años reducir el tamaño de los transistores a la mitad cada 18 meses, pero a su vez esta mejora también está limitada. A nivel de uniprocesador hemos tenido que tomar otras v́ıas para mejorar los computadores, optando en primer lugar por la paralelización a nivel de instrucción (ILP). Esto introdujo aśı térmi- nos como cambio de contexto o pipelines y por tanto, técnicas que hicieron mejorar el rendimiento como la ejecución fuera de orden (Out of Orden - OoO), las predicciones de salto, el cambio de la jerarqúıa de memoria para hacer accesos ecualescentes, etc. Más tarde llegó el paralelismo a nivel de thread (TLP), consiguiendo ejecutar varios hilos en un sólo núcleo. Al igual que con ILP, también trajo problemas para el programador a la hora de sincronizar los datos (carreras cŕıticas, deadlocks, coherencias de caché, etc.), aunque mejoró el rendimiento. No fue hasta inicios de los 2000 cuando se popularizó el uso de ordenadores multicore, consi- guiendo conectar varios cores f́ısicos entre ellos por buses de alta velocidad. Añadir una memoria caché compartida, diferente de la individual, hizo que la latencia de acceso a memoria bajase de forma drástica y facilitase la comunicación y por ende, el rendimiento. Como último paso, a nivel de CPU, existen los grids, que son la interconexión de procesadores multicore a través de red, lo que nos permite coordinar recursos de forma no centralizada, paralela y distribuida. Pese a todas estas mejoras cabe destacar que hay casos en los que por mucho más cores que se añadan, por naturaleza de la aplicación, no es posible paralelizar el programa. Con el auge de los videojuegos y con el aliciente por mejorar sus gráficos, nace en 1999 el término GPU (Graphics Processing Unit), que conlleva la explotación del paralelismo a nivel de datos. Debido a la cantidad de operaciones de vectores derivadas del renderizado de imágenes que producen los videojuegos, surgen las instrucciones vectoriales o SIMD (Single Instruction Multiple Data). A d́ıa de hoy, aunque las GPUs se siguen usando para el diseño de gráficos, tanto el auge de Hight Performance Computing (HPC) como de Machine Learning han ocultado su propósito inicial. Gracias a que contienen una cantidad inmensa de cores y la memoria está diseñada para poder acceder de forma rápida y eficaz, se obtienen mejoras como la paralelización masiva de instrucciones. Además, se consigue un gran ancho de banda para traer o llevar memoria desde la 9 Introducción CPU a la GPU y una baja latencia que facilita el movimiento de datos entre Stream Multiprocessors (SM), lo cual hace a las GPUs ideales para el cómputo masivo. Machine Learning se basa en enseñar a una máquina a realizar una serie de tareas a través de la interacción humana, usualmente con predicciones o clasificaciones de imágenes. Para ello se dispone de algoritmos, como pueden ser los de árbol de decisión que ilustra cada posible resultado en forma de árbol, o las redes neuronales, que son una serie de capas de nodos interconectados. A partir de una entrada de datos, de un umbral y un valor por nodo se consigue llegar al resultado final. El aprendizaje de la inteligencia artificial está dividida en dos pasos: entrenamiento e inferencia. El entrenamiento, como en cualquier disciplina, se basa en enseñar a tomar buenas decisiones avisando cuando el trabajo se ha hecho de forma correcta y cuando no. Un algoritmo no es ninguna excepción, ya sea dando datos de entrada del objetivo a cumplir o a partir de los resultados, pudiendo redireccionarlo para conseguir el resultado deseado. La fase de inferencia, en cambio, se basa en poner a prueba lo enseñado en el entrenamiento y, de forma análoga a la vida real, no siempre sale como resultado lo que uno desea. De ambos procesos el que más vale la pena optimizar tanto en latencia, rendimiento y potencia, ya que el que más recursos gasta, es la inferencia. La motivación de este trabajo proviene de mi interés por trabajar con nuevas herramientas no muy utilizadas a lo largo del grado, y qué mejor forma que destacando la relevancia de las GPUs. El gran progreso que está teniendo el benchmark de NVIDIA MLPerf, aśı como la cantidad de posibilidades que permite frente a otros bancos de trabajo, hace de él un activo muy interesante para probar y aprender a configurar software. Precisamente, el atractivo de las GPUs de alta gama ya ha llamado la atención de grandes empresas (Intel, AMD), que pretenden competir con los avances introducidos por NVIDIA. 1.2. Objetivos Los objetivos de este trabajo se basan en estudiar y evaluar ciertas redes neuronales con uno de los benchmarks más usados de NVIDIA, MLPerf. Con ello vamos a poder ver el comportamiento que ofrecen las GPUs NVIDIA A30 y Jetson Orin AGX a la hora de ponerlas en distintas cargas de trabajo y bajo distintas configuraciones, analizando tanto rendimiento como potencia consumida. Este objetivo se conseguirá mediante: • La modificación de aquellos aspectos de la configuración para hacer funcionar MLPerf en la A30, al no estar soportada por defecto, aśı como comprender qué parámetros y en qué situaciones de cada modelo hacen que cambie el rendimiento en la GPUs. • La ampliación del benchmark para añadir la medición de potencia con el software PMLib. • La comparación del comportamiento de los distintos modelos de procesamiento entre diferentes máquinas, modelos y modos de funcionamiento, para poder conocer los ĺımites y diferencias de cada una de ellas. El presente documento se estructura como se detalla a continuación: En el caṕıtulo 2 se detallan las máquinas con las que vamos a trabajar con sus caracteŕısticas. En el caṕıtulo 3 se describe qué es MLPerf, cómo está dividido y las distintas opciones que proporciona para la evaluación de los modelos que ofrece. Después se procede a dar una pequeña explicación de cada modelo. En el caṕıtulo 4 se explica cómo se ha añadido la medición de potencia al benchmark. El caṕıtulo 5 se incluye la metodoloǵıa seguida para lanzar un modelo, cómo está dividido en carpetas y cómo se configura cada modelo. En el caṕıtulo 6 se describen los resultados obtenidos, ejemplificando las conclusiones con gráficas y tablas de los datos obtenidos. 10 Introducción Por último, en el caṕıtulo 7 se explican las conclusiones generales, tanto en rendimiento como en potencia, las posibles mejoras del benchmark y las posibles v́ıas para trabajos futuros. 11 Caṕıtulo 2 Introduction 2.1. Motivation Since the invention of the first computer there has been a great effort to improve data processing efficiency, and there have been multiple aproaches. At the beginning it was carried out increasing the clock’s frenquency and it became evident that a substancial improvement was achieved. However, due to current leakage and a rise of heat dissipation, this cannot be appplied indefinetely. Following the famous Moore’s Law in 1965 [6], it was possible to reduce the size of the transistors to half every 18 months, but this improvement is also limited. To a one-processor level it is necessary to take other aproaches to improve computers perfor- mace, implementing for example Instruction-level parallelism (ILP). This introduced terms such as change of context or pipelines and therefore methods to improve efficiency like Out Of Order ejecution(OoO), jumping predictions, changes of memory hierarchy to do sequential access, etc. Later on, Thread level parallelism (TLP) appeared, which is able to perform multiple threads in a simple core. It happened the same as with ILP: even though there was an improvement in efficiency, this also brought problems to the programmer for data synchronization (race condition, deadlocks, cache coherences, etc.). It was not until the beginning of the 20th century when multicore computers became popular, being able to connect multiple physical cores between them through high speed buses. Adding a memory shared cache, different from the individual one, made access latency to memory drop drastically and in this way improved comunication, and overall performace was enhanced. The next level in CPU field grids is an interconnection of multicore processors through network which allows us to coordinate resources in a centralized, parallel and distribuited way. Despite all these improvements there are certain applications that, due to their nature, are not possible to parallelize. With the rising of videogames and the desire to have better graphics, in 1999 the term GPU (Graphics Processing Unit) was born, and with that, the exploitation of data level parallelism. Because of the amount of vector operations due to image rendering produced by videogames, Single Instruction Multiple Data (SIMD) operations were created. Although nowadays GPUs are still being used for graphic design, the boom of High Performance Computing (HPC) and Machine Learning has somewhat hidden their original purpose. Thanks to the considerable amount of cores that they have and a memory which is designed for fast and effec- tive access, it is possible to obtain improvements such as massive parallel instructions. Futhermore, it is possible to also get a considerable bandwidth to bring and to take memory to the GPU from the CPU with a low latency which facilitates movement of data among Stream Multiprocessors (SM), making GPUs perfect for massives compute. Machine Learning is based in teaching a machine how to do a series of tasks through human interaction, usually predictions or classification of images. For this we have algoritms such as decision 12 Introduction tree that illustrates each and every possible decission in a tree form, or neuronal networks, which are a series of layers forms with interconnected nodes. From an input of data, a threshold and a value per node, we can achieve a result. Learning artificial intelligence is divided into two steps: training and inference. Training, as in any other field, is based in tutoring to take good decisions informing when the work has been done well and when it has not. With an algorithm this is not an exception, either giving data of what the result should be as an input or analizing the output, being able to suggest which way to go to achieve our goal. However, inference is based on testing what has been learned in training and, similar to real life, not always gives us the result we expect. From both processes the one most worth optimizing either in latency, performance and power, because it is the one that uses more resources, is inference. The reason of this project comes from my interest to work with new tools that I haven not used a lot during the grade, and what a better way than to emphasise the importance of GPUs. The great improvement that Nvidia’s benchamrk MLPERF is having, just like the number of possilities that it offers in comparison with other benchmarks, makes it a really interesting tool to try and learn to configure software. And although there are other big companies such as Intel or AMD that are developing their own high range GPUs, I can also work with a series of powerful devices of the brand with more impact on the market, NVIDIA. 2.2. Objectives The objectives of this work are based on studying and evaluating certain neural networks with one of the most used benchmarks of NVIDIA, MLPerf. With this tool, we will be able to see the behaviour that offers GPUs NVIDIA A30 and Jetson Orin AGX, this last one with different ma- ximum power values, when I use them with different workloads and under different configurations, analizing either performace as power consumed. This objective is divided into: • Changing those aspects in which the configuration is set to be able to make MlCommons work in th A30 because it is not supported by default, and also to understand which parameters and in which situations of every model make an impact on each GPU. • Extending the benchmark to add the power measurement with PMLib software. • Comparing the behaviour of the different forms of processing in each machine, to be able to know advantages and disadvantages of every one of them. The document follows the structure outlined below: In chapter 2 there is a description of the machines which will be used as our testbed, together with their characteristics. In section 3 we describe what MLPerf is, how it is divided and the different options which are provided for the evalution of the models that it offers. After that we proceed to describe briefly every model. In chapter 4 we explain the necessary modifications added the power measurement into the software. Chapter 5 includes the methods carried out in order to execute it, how the work is divided inside the folders and how to configure each model. In chapter 6 there is a description of the results obtained and we give examples using graphics and tables. Finally, in chapter 7 general conclusions are explained, either in performance and power, the possible improvements in MLPerf and the different routes we can follow in future projects. 13 Caṕıtulo 3 Arquitecturas utilizadas Para la evaluación de MLPerf, tanto en términos de latencia, rendimiento y potencia, se han seleccionado dos arquitecturas sustancialmente diferentes tanto desde el punto de vista de su rendi- miento pico, como de su consumo energético y ámbito t́ıpico de aplicación. Espećıficamente, estas arquitecturas son: NVIDIA A30. Se trata de una GPU discreta de alto rendimiento [7], t́ıpicamente desplegada en centros de cálculo para acelerar procesos de entrenamiento, aunque también aplicable a procesos de inferencia. Jetson Orin AGX. Se trata de una GPU integrada en un System-on-chip [8], t́ıpicamente enfo- cada exclusivamente a procesos de inferencia o reentrenamiento de redes neuronales. 3.1. NVIDIA A30 NVDIA A30 tiene una arquitectura Ampere y consigue un consumo de potencia de ente 31 y 165W. Ofrece un redimiento pico de de 10.3 TFLOPS en operaciones de punto flotante de 32 bits. Con hasta 82 TFLOPS en operaciones BFLOAT16 y FP16, alcanzando hasta 330 TOPS en operaciones INT8, es ideal para realizar el proceso de inferencia. Incluye aceleradores para tareas multimedia, como un acelerador óptico de flujo (OFA), decodificador JPEG (NVJPEG) y varios decodificadores de video (NVDEC). Está equipada con 24GB de memoria HBM2, llegando a alcanzar un ancho de banda de memoria de 933GB/s. Soporta PCIe Gen4 con una velocidad de 64GB/s y NVLink de tercera generación con un ancho de banda de hasta 200GB/s, permitiendo una comunicación rápida entre GPU y otros dispositivos. Contiene la herramienta Multi-Instance GPU (MIG), con la que se es capaz de particionar en 4 instancias de GPU con 6GB cada una, en 2 con 12 GB y o en una sola instancia con 24GB. También ofrece soporte para la virtualización de GPU, lo que permite compartir eficientemente los recursos de la GPU entre múltiples máquinas virtuales. Además, es compatible con NVIDIA AI Enterpriise y NVIDIA Virtual Compute Server. La NVIDIA A30 ocupa dos ranuras de expansión y tiene tanto altura como longitud completa. La GPU está instalada bajo CPU Intel Xeon Silver 4314 [9], que trabaja a una frecuencia básica 2.40 GHz y una frecuencia turbo máxima de 3.40 GHz. Los 16 núcleos de la CPU están divididos en dos nodos Non-Uniform Memory Access (NUMA): del 0 al 7 pertenecen al nodo 0 y del 8 al 15 al nodo 1. Este sistema asigna de forma f́ısica la memoria a cada procesador, de forma que es más rápido acceder a memoria local. La Intel Xenon Silver 4314 contiene una potencia de diseño térmico de 135W y se encuentra bajo un sistema de memoria de 62 GB. El tamaño de la caché L1 de datos es de 768 KiB, la caché de L1 de instrucciones es de 512 KiB, la L2 es de 20 MiB y la L3 es de 24 MiB. 14 Arquitecturas utilizadas Los puertos ráız PCIe son puntos de entrada que conectan a la CPU o al chipset; en esta máquina se emplea el ’Intel Corporation C620 Series Chipset Family PCI Express Root’. Los puentes PCIe utilizados incluyen ’ASPEED Technology’, ’Inc. AST1150 PCI-to-PCI Bridge’, ’Intel Corporation Device 347c’ e ’Intel Corporation Device 347d’, los cuales proporcionan carriles para tarjetas de expansión. En cuanto a periféricos del sistema, se utilizan los PCIes ’Intel Corporation Ice Lake Memory Map/VT-d’ para mapeo a memoria, ’Intel Corporation Ice Lake Mesh 2 PCIe’, ’Intel Corporation Ice Lake RAS’ e ’Intel Corporation Ice Lake CBDMA’. Como puentes de host se emplea ’Intel Corpo- ration Ice Lake IEH’. También existen controladores espećıficos como ’Intel Corporation Ethernet Controller X550’ para Ethernet o ’Intel Corporation C620 Series Chipset Family USB 3.0 xHCI Controller’ para USB. 3.2. Jetson AGX Orin La NVIDIA Jetson AGX Orin es una GPU de la gama Ampere, con una arquitectura aarch64 que contiene 2048 cores de NVIDIA CUDA junto a 64 tensor cores. Con 64GB de RAM LPDDR5 de 256 bits, llega a consumir desde una potencia mı́nima de 15 a una máxima de 60 W. Alcanzado los 275 TOPS (Tera Operations Per Second), hace la función perfecta de una máquina de medio consumo para el análisis de MLPerf. Está soportada bajo una CPU de 1322 núcleos Arm Cortex-A78AE v8.2 de 64 bits. Incluye dos NVDLA v2.0 (Acelerador de Aprendizaje Profundo de NVIDIA) y una PVA v2.0 (Acelerador de Visión Programable) que hacen mejorar el procesamiento de inferencia. En esta GPU nos encontramos el término Texture Processor Cluster (TPC) o también llamado grupos de procesamiento de texturas. Es un conjunto de Stream Multiprocessors, una unidad de textura y lógica de control, el cuál toma una gran importancia a la hora de poder capar la potencia, ya que la Jetson AGX Orin contiene 8 de ellos y se puede limitar su uso. Para simular más casos de consumo energético, la Jetson Orin AGX permite capar el consumo máximo con la herramienta nvpmodel [10]. El modo de potencia, que por defecto es 0 significando que utiliza la máxima frecuencia, se puede saber con la instrucción sudo /usr/sbin/nvpmodel -q. La Jetson Orin AGX de 64GB permite hasta 4 modos de potencia pudiendo hacer el cambio ente ellos con la instrucción sudo /usr/sbin/nvpmodel -m [0,1,2,3]. El modo 1 se corresponde con 15W, pasa de usar las 8 GPUs de tipo TPC que se usan con frecuencia máxima a sólo usar 3, con una frecuencia de GPU que se reduce de las 1301 MHz a apenas 420. Además reduce las online CPU de 12, en modo 0, a 4 y disminuye la frecuencia de la CPU de 2201.6 a casi la mitad, 113.6. De forma similar pasa para el modo 2, que tiene 30W de potencia máxima, utiliza 4 GPU de tipo TPC y frecuencia máxima de las GPUs es de 420.75 MHz. La frecuencia máxima de la CPU se reduce a 1728 MHz y contiene 8 online CPU. Por último, el modo 3 corresponde con una potencia máxima de 50 vatios. Aunque contiene las mismas online CPU y GPU TPC que en modo 0, la frecuencia de la CPU es de 1497.6 MHz y la de la GPU es de 828.75MHz. 15 Caṕıtulo 4 MLPerf En este trabajo se ha elegido utilizar el estándar de inferencia con MLPerf publicado en 2020 por MLCommons, un benchmark que complementa el entrenamiento de MLPerf. Lanzada la parte de inferencia en 2019 [11], se han aportado más de 3500 resultados por parte de la comunidad, con más de 100 configuraciones distintas pasando por distintas CPUs, GPUs, FPGAs y otro tipo de aceleradores. Hecho para investigación, contiene una serie de modelos de código abierto de distintos tipos como segmentación de imágenes en el ámbito médico, reconocimiento automático de voz o autocompletado de frases, entre otros, que nos permite contrastar rendimiento. Estos son: Bert, Rnnt, 3d-Unet, GPTJ, DLRMV2, Resnet50 y Retinanet. Entre las versiones disponibles, se ha decidido utilizar la última en el momento de inciar este trabajo, la 3.1. 4.1. Estructura general y escenarios Si no se comenta nada, parece que MLPerf es el encargado de realizar todo el trabajo de inferen- cia, pero no es aśı. Para poder medir el rendimiento utiliza el modelo LoadGen [1] esquematizado en la figura 4.1, el cual, a partir del sistema sobre el que queremos ejecutar (System Under Test o SUT), escenario y configuración especificada, genera el tráfico necesario para la ejecución del mo- delo. Además es el encargado de medir ciertos parámetros como latencia o muestras por segundo. El orden en el que trabaja con el benchmark es que este le dice el conjunto de datos del modelo con el que trabajar y LoadGen genera las peticiones con las muestras. Una vez acabado LoadGen le responde con los resultados obtenidos. Figura 4.1: Representación del modelo LoadGen. [1] 16 MLPerf LoagGen ofrece una serie de escenarios que simulan distintos entornos de trabajo donde se puede aplicar inferencia en nuestro SUT: Offline, Server, SingleStream y MultiStream. La diferencia entre ellos es la forma en la que reciben los datos de entrada (batch) y reportan el resultado. Offline simula una aplicación de procesamiento de muestras, recibe una petición al sistema con todas las muestras y una vez realizado el proceso, devuelve el resultado en cualquier orden. Ejemplo de esto puede ser encontrar el número de personas que hay en una fotograf́ıa. Para la medición de rendimiento se usan peticiones procesadas por segundo (queries per second or qps). En el modo Server, en cambio, llegan los datos usando una distribución Poisson, la cual se asemeja a los datos recibidos comúnmente por un servidor, por lo que la unidad para medir tiempo vuelve a ser qps. Aqúı la latencia es importante, ya que al igual que hacemos consultas por internet conectándonos a un servidor, queremos que vaya rápido. Por esto el SUT responde a cada muestra en un rango de 15 a 250 milisegundos, y sólo un percentil de uno de las muestras procesadas pueden pasarse del tiempo máximo para que el resultado siga siendo válido. En SingleStream llega una muestra por petición, la cuál debe finalizar la ejecución para que se env́ıe la siguiente. Debido a la importancia de la latencia, para medir el rendimiento se mide con el percentil 90 del tiempo en el que se ha llevado a cabo. Por último, en MultipleStream llegan varias muestras por petición con una frecuencia fijada antes de empezar el proceso. La inferencia se debe completar entre 50 y 100 milisegundos depen- diendo del modelo, por lo que se reporta la latencia de tantas muestras como haya dado tiempo a realizarse en el tiempo acordado. Se trabaja por intervalos, de forma que si se está procesando la petición anterior y se acaba el ĺımite, las demás muestras saltan un intervalo. Para que la ejecución sea válida ocurre lo mismo que con Server, sólo un uno por ciento de las muestras pueden no cumplir el plazo. Scenario Query Generation Duration Samples / query Latency Cons- traint Tail La- tency Performance Metric Single stream LoadGen sends next query as soon as SUT completes the previous query 1024 queries and 60 se- conds 1 None 90% 90%-ile mea- sured latency Multiple stream (1.1 and earlier) LoadGen sends a new query every latency constraint if the SUT has completed the prior query, otherwise the new query is drop- ped and is counted as one overtime query 270,336 que- ries and 60 seconds Variable, see me- tric Benchmark specific 99% Maximum number of inferences per query supported Multiple stream (2.0 and later) Loadgen sends next query, as soon as SUT completes the previous query 270,336 que- ries and 600 seconds 8 None 99% 99%-ile mea- sured latency Server LoadGen sends new queries to the SUT ac- cording to a Poisson distribution 270,336 que- ries and 60 seconds 1 Benchmark specific 99% Maximum Poisson th- roughput parameter supported Offline LoadGen sends all que- ries to the SUT at start 1 query and 60 seconds At least 24,576 None N/A Measured th- roughput Tabla 4.1: Caracteŕısticas de los modos de MLPerf. [5] 17 MLPerf Los cuatro modos de ejecución cumplen una serie de restricciones que deben cumplir para que la ejecución sea válida, encontradas en el fichero text tuning guide.md en la carpeta de docu- mentación. En todos ellos la duración mı́nima (min duration) debe de ser de 600 segundos, 60 si se utiliza el flag ’--fast ’. Todos deben cumplir que, de forma respectiva, los offline expected qps, server target qps, single stream expected latency ns y multi stream expected latency ns sean ma- yores que los añadidos en el fichero de configuración. Además se deben de cumplir un número mı́nimo de consultas fijado por defecto en 24576 para Offline, 270336 para Server, 1024 para SingleStream y 270336 para MultiStream. A pesar de estas restricciones, aún existe un considerable margen de optimización en la confi- guración, permitiendo, por ejemplo, sacrificar algo de exactitud a favor de menor latencia. MLPerf nos ofrece dos v́ıas: closed, que es algo más restringida pero más fácil de configurar, y open, que permite mayor flexibilidad en los ĺımites de cada modelo. En este estudio se utilizará la opción closed. MLCommons intenta asemejarse en todo lo posible a casos reales, por lo que divide la calidad de servicio en varios sistemas: HPC, Edge, Datacenter, Tiny y Mobile. En nuestro caso, al usar la versión de la Jetson Orin-AGX entra en el ámbito de sistemas Edge, mientras que la A30 pertenece a Datacenter. Docker Tanto para la preparación del entorno como para ejecución de los modelos, MLPerf utiliza Docker. Docker es una plataforma software que, mediante la creación de contenedores, parecidos a máquinas virtuales, nos permite ejecutar y depurar los errores de forma sencilla. Los ’Makefile’ que se nos proporciona ya están creados para utilizar este software, aśı como el almacenamiento de los modelos al que apunten la variable de entorno MLPERF SCRATCH PATH necesarias para la ejecución, como se indica en la documentación. 4.2. Modelos disponibles 4.2.1. BERT El modelo de lenguaje BERT (Bidirectional Encoder Representations from Transformers) [2], representado en la figura 4.2, enmascara aleatoriamente ciertas palabras del mensaje de entrada que recibe y, a través del contexto, intenta predecir con la mayor exactitud posible la palabra enmascarada. Este modelo mejora a otros de su mismo propósito ya que fusiona el contexto izquierdo y el derecho, además de trabajar mediante pares de oraciones. Por el momento sólo trabaja con texto en inglés. Está compuesto por dos fases: pre-entreno y fine-tuning. El pre-entreno, a través de tareas diferentes genera una serie de datos; estos son pasados a la tarea fine-tuning para conseguir ajustar el modelo. 18 MLPerf Figura 4.2: Mapa conceptual de las fases del modelo BERT. [2] 4.2.2. RNN-T RNN-T (Recurrent Neural Network-Transducer) [12] es un modelo de reconocimiento automáti- co de voz. El modelo propuesto por Alex Graves funciona secuencia a secuencia y está formado de 3 componentes: un codificador acústico que recibe unos datos de entrada y genera una representa- ción de alto nivel, una red de predicción y un mezclador que une los resultados de los dos pasos anteriores. 4.2.3. 3D-Unet El modelo 3D-Unet [3] es una red neuronal convolucional diseñada en 2015, usada para la división de imágenes en 3D en el ámbito biomédico. Esto significa que la red recibe una serie de imágenes para ser entrenada y, mediante operaciones matemáticas como pueden ser operaciones matriciales, produce un resultado. Esto se asemeja mucho a la idea de aplicar un filtro a una imagen. Está formada por 2 partes, ambas con 4 capas de nodos: la codificadora, a través de la cual se eligen los datos que nos interesan, y la descodificadora, que hará las operaciones inversas de la convolución para la generación del resultado. La capas convolucionales de este modelo son esbozadas en la figura 4.3. 19 MLPerf Figura 4.3: Capas convolucionales del modelo 3D-Unet. [3] 4.2.4. ResNet50 ResNet-50 (Residual Network) [13] es una red convolucional con 50 capas, capaz de clasificar fotograf́ıas en alrededor de 1000 categoŕıas distintas como puede ser diferenciar animales. Está formada por 48 capas convolucionales, 1 capa MaxPool, basada en dividir la imagen y reducirla eligiendo el valor más alto de cada rectángulo, y una última capa pool que saca el promedio. 4.2.5. RetinaNet RetinaNet [4] es un modelo de una sola fase de detección de objetos mediante una operación de pérdida focal. Usado para imágenes aéreas y de satélite, se realiza una red piramidal (FPN) sobre el resultado de aplicar ResNet a la imagen de entrada. Esta pirámide, representada en la figura 4.4, está compuesta de anchor boxes [14]. Se trata de una técnica usada en la detección de objetos basada en una serie de cajas o anchor boxes predefinidas según los datos de entrada, a los que el modelo atribuye una serie de atributos como la probabilidad de encontrar el objeto. Después se ponen en marcha otras dos subredes: una clasificación de anchor boxes y una regresión sobre las capas de la pirámide. 20 MLPerf Figura 4.4: Representación de red piramidal con las dos subredes propuesta por Retinanet. [4] 4.2.6. GPT-J Generado por EleutherAI, GPT-J (Generative Pre-trained Transformer)[15] es un modelo con código abierto de generación de texto en inglés a partir de un texto de entrada. La complejidad del modelo al contener 28 capas, tener 6 mil millones de parámetros y haber sido entrenado por una cantidad masiva de datos (modelo de entrenamiento llamado The Pile [16] de 825GiB), permite generar texto de forma creativa. Cabe destacar que sólo funciona para texto en inglés y, aunque utiliza la misma tecnoloǵıa de transformación de lenguaje, no es lo mismo que el famoso modelo ChatGPT, ya que este último utiliza un número mucho menor de parámetros (alrededor de medio millón en el caso de ChatGPT-4). 4.2.7. DLRMV2 DLRMv2 (Deep Learning Recommendation Model) es un modelo de recomendación diseñado para hacer uso tanto de datos numéricos como categóricos. Está diseñado para poder entrenar modelos que sean demasiado grandes para una sola GPU y trabaja con cualquier tipo de conjunto de datos. Al ya conocer todos los modelos, podemos definir a qué corresponde una petición o querie en uno de ellos. En caso de Resnet, Retinanet, 3d-unet una consulta se refiere a una sola imágen, mientras que en Bert y GPT-J es una frase, en Rnnt un audio de hasta 15 segundos y en DLRMV2 hasta 700 pares de datos usuario-elemento. 21 Caṕıtulo 5 Medición de potencia Para la medición de potencia se ha usado el software PMLib (ejemplo en el apéndice B) proporcionado por el departamento DACYA de la UCM, integrándolo en la infraestructura de MLPerf. Diseñado como un sistema cliente-servidor, PMLib es capaz de hacer uso de diversos mecanismos de medición de consumo o medidores externos de forma transparente al usuario. Espećıficamente, PMLib despliega un servidor en la máquina destino que, con previa configu- ración para su adaptación al sistema de medición pertinente, ofrece mediante sockets (por defecto, escuchando en el puerto 6521) los servicios necesarios para establecer contadores de consumo. La parte cliente, que puede desplegarse de forma interactiva en cualquier equipo externo al de medi- ción mediante el comando pm info, o bien instrumentando código, realiza las pertinentes conexiones contra dicho servidor, recogiendo los datos y proporcionandolos en forma de ficheros de texto que pueden a posteriori ser analizados. 5.1. Servidor En nuestro caso, el servidor PMLib ha interactuado utilizando dos mecanismos distintos de recogida de muestras para medición de consumo, adaptándose de forma espećıfica a las herramientas disponibles en cada una de las dos máquinas disponibles. 5.1.1. NVIDIA A30 La infraestructura proporcionada por NVIDIA para la medición de consumo en GPUs discretas (como es la A30) es la biblioteca NVML (NVIDIA Management Library). NVML proporciona una API completa para la interacción desde programas (inicialmente en lenguaje C) para gestionar y consultar multitud de parámetros proporcionados por el panel de control nvidia-smi. Entre estas caracteristicas, se encuentra la medición instantánea de potencia. En nuestro caso, ya que la infraestructura PMLib está desarrollada en Python, se ha hecho uso de la biblioteca pyNVML proporcionada por la propia compañ́ıa. Espećıficamente, se ha desarrollado un plugin para PMLib que, de forma constante (en función de la frecuencia de muestreo solicitada por el usuario), realiza peticiones al panel de control uti- lizando la infraestructura NVML. Previamente al desarrollo del plugin, se ha añadido un fichero de configuración (settings.py) en el que se define tanto la máquina a utilizar, como el medidor de consumo (de tipo NVMLDevice) del que se desea hacer uso: from daemon . d e v i c e s import ∗ from daemon . modules import ∗ #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− 22 Medición de potencia # General s e c t i o n #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # IP and Port in which the daemon w i l l be l i s t e n i n g ( d e f a u l t : 6526) IP=” 0 . 0 . 0 . 0 ” PORT=6526 # Log f i l e name ( d e f a u l t : ”/ var / log /powermeter . l og ”) LOGFILENAME=” . / powermeter . l og ” path =” . ” #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # Computers s e c t i o n #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− OCEJON = Computer . Computer (name=”OCEJON” , ip=” 0 . 0 . 0 . 0 ” ) #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # Devices s e c t i o n #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− A30 dev = NVMLDevice( name = ”NVMLDevice” , computer = OCEJON, u r l=”” , max frequency =100) A30 dev . a d d l i n e ( number=0, name=”NVML OCEJON” , vo l tage =12, d e s c r i p t i o n=”NVML OCEJON” ) La clase NVMLDevice realiza la interaccion con la biblioteca NVML de forma constante en su método read, que es invocado periódicamente por la infraestructura. Espećıficamente, obsérvese en el siguiente código cómo se crea una única ĺınea de lectura de potencia, cuyo valor es de retorno de la invocación a la función nvmlDeviceGetPowerUsage proporcionado por pyNVML: # −∗− coding : utf −8 −∗− #====================================================================== # NVMLDevice c l a s s #====================================================================== import Device import pexpect import time from pynvml import ∗ ## An NVML dev i ce d e s c r i p t i o n c l a s s NVMLDevice( Device . AttachedDevice ) : ## Creates a NVML dev i ce d e s c r i p t i o n and adds i t to the d ev i c e s ## d i c t i o n a r y # # @param [ in ] name The dev i c e name ( used f o r i d e n t i f i c a t i o n , must be unique ) # @param [ in ] u r l The u r l o f t h i s dev i c e # @param [ in ] max frequency The maximum sample f requency o f the dev i ce # de f i n i t ( s e l f , name , computer , ur l , max frequency ) : s e l f . n l i n e s = 1 s e l f . handle= [ 0 ] ∗ s e l f . n l i n e s nvmlInit ( ) f o r i in range ( 0 , s e l f . n l i n e s ) : s e l f . handle [ i ] = nvmlDeviceGetHandleByIndex ( i ) super (NVMLDevice , s e l f ) . i n i t (name , computer , ur l , max frequency ) de f read ( s e l f ) : power= [ 0 ] ∗ s e l f . n l i n e s 23 Medición de potencia t muestra = s e l f . max frequency ∗∗ −1 whi l e s e l f . running : t1 = time . time ( ) power [ 0 ] = nvmlDeviceGetPowerUsage ( s e l f . handle [ 0 ] ) y i e l d power w = t muestra − ( time . time ( ) − t1 ) i f ( w > 0 ) : time . s l e e p (w) A partir de este punto, y tras desplegar el servidor en segundo plano, será posible realizar una interacción con el mismo, creando contadores de consumo sobre dicha ĺınea para consultar los valores de potencia instantánea asociados a la NVIDIA A30. 5.1.2. Jetson AGX En este caso, la infraestructura NVML no está disponible para su uso. Sin embargo, NVIDIA proporciona una serie de dispositivos de medición de consumo (sensores de efecto Hall INA 231 de Texas Instruments), que se exponen a través de ficheros en el arbol de directorios de Linux para su consulta. Espećıficamente, nuestra solución hace uso de los monitorización que ofrecen las máquinas NVIDIA [17], realizando una recopilación de medidas de forma periódica sobre la máquina que se le indique. La Jetson AGX contiene 2 monitorizaciones de 3 canaless I2C, trabajando a través de la lectura de una serie de sensores a través de unos ficheros de texto encontrados en los siguientes directorios: /sys/bus/i2c/drivers/ina3221/1-0040/hwmon/hwmon[x] /sys/bus/i2c/drivers/ina3221/1-0041/hwmon/hwmon[y] Los dispositivos 0x40 y 0x41 corresponden con las direcciones donde se encuentran los monitores de potencia. Cada uno de estos ficheros corresponden a una ’ĺınea’ o tipo de medición, siendo capaces de poder configurar qué ĺıneas leer desde el fichero settings.py : #====================================================================== # PowerMeter daemon s e t t i n g s #====================================================================== from daemon . d e v i c e s import ∗ from daemon . modules import ∗ #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # General s e c t i o n #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # IP and Port in which the daemon w i l l be l i s t e n i n g ( d e f a u l t : 6526) IP=” 0 . 0 . 0 . 0 ” PORT=6526 # Log f i l e name ( d e f a u l t : ”/ var / log /powermeter . l og ”) LOGFILENAME=” . / powermeter . l og ” path =” . ” #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # Computers s e c t i o n #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ORIN = Computer . Computer (name=”ORIN” , ip=” 0 . 0 . 0 . 0 ” ) 24 Medición de potencia #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− # Devices s e c t i o n #−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− ORINAGX dev = ORINAGXDevice( name = ”ORINAGXDevice” , computer = ORIN, u r l=”” , max frequency =100) ORINAGX dev . a d d l i n e ( number=0, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=1, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=2, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=3, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=4, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=5, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=6, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=7, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=8, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=9, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) ORINAGX dev . a d d l i n e ( number=10, name=”AGX” , vo l tage =12, d e s c r i p t i o n=”AGX” ) Obsérvese que se añaden 11 ĺıneas que serán expuestas a los usuarios. Espećıficamente, estas ĺıneas corresponden a mediciones de voltaje y amperaje para cada uno de los posibles dispositivos de interés. Véase la definición de la clase ORINAGXDevice para observar cómo se extraen y combinan dichas ĺıneas (obsérvese que, en este caso, la interacción con los sensores se realiza abriendo y leyendo constantemente ficheros de texto): # −∗− coding : utf −8 −∗− #====================================================================== # PDUDevice c l a s s #====================================================================== import Device import pexpect import time ## An ORINNANO dev i ce d e s c r i p t i o n c l a s s ORINAGXDevice( Device . AttachedDevice ) : ## Creates a ORINAGX dev i ce d e s c r i p t i o n and adds i t to the d e v i c e s ## d i c t i o n a r y # # @param [ in ] name The dev i c e name ( used f o r i d e n t i f i c a t i o n , must be unique ) # @param [ in ] u r l The u r l o f t h i s dev i c e # @param [ in ] max frequency The maximum sample f requency o f the dev i ce # de f i n i t ( s e l f , name , computer , ur l , max frequency ) : s e l f . n l i n e s = 11 super (ORINAGXDevice , s e l f ) . i n i t (name , computer , ur l , max frequency ) de f read ( s e l f ) : power= [ 0 ] ∗ s e l f . n l i n e s t muestra = s e l f . max frequency ∗∗ −1 whi l e s e l f . running : t1 = time . time ( ) power [ 0 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ cur r1 input ” , ” rb” ) . read ( ) ) power [ 1 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ in1 input ” , ” rb” ) . read ( ) ) power [ 2 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ cur r2 input ” , ” rb” ) . read ( ) ) power [ 3 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ in2 input ” , ” rb” ) . read ( ) ) power [ 4 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ 25 Medición de potencia cu r r3 input ” , ” rb” ) . read ( ) ) power [ 5 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ in3 input ” , ” rb” ) . read ( ) ) power [ 6 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ cur r4 input ” , ” rb” ) . read ( ) ) power [ 7 ] = f l o a t ( open ( ”/ sys /bus/ i 2 c / d r i v e r s / ina3221 /1−0040/hwmon/hwmon3/ in4 input ” , ” rb” ) . read ( ) ) power [ 8 ] = ( power [ 0 ] / 1000 . ) ∗ ( power [ 1 ] / 1000 . ) power [ 9 ] = ( power [ 2 ] / 1000 . ) ∗ ( power [ 3 ] / 1000 . ) power [ 1 0 ] = ( power [ 4 ] / 1000 . ) ∗ ( power [ 5 ] / 1000 . ) y i e l d power w = t muestra − ( time . time ( ) − t1 ) i f ( w > 0 ) : time . s l e e p (w) Se realizan 3 mediciones espećıficas: el consumo de GPU junto al sistema on chip (SoC), el consumo de CPU junto al SoC y la entrada al sistema y el subsistema de memoria, todas ellas en W. Pero para hallar esto, los sensores primero deben hacer la medición de corriente (Amperios) y voltaje (Voltios). Las ĺıneas 0 y 1 para la corriente y voltaje, de forma respectiva, de consumo GPU. De la misma forma ocurre las ĺıneas 2 y 3 para la CPU y la 4 y 5 para memoria. Para poder hacer la multipicación de ambos valores y sacar los vatios (W) se añaden las ĺıneas 8, 9 y 10 que corresponden a las distintas mediciones en orden. Las ĺıneas no nombradas son otro tipo de cálculos no relevantes en nuestro caso. 5.2. Cliente El procedimiento descrito anteriormente corresponde a la medición desde la parte del servi- dor, pero necesitamos un cliente que solicite realizar estos cálculos. En nuestro caso, la solicitud debe ser realizada mediante la invocación a run harness ya que es la encargada de ejecutar el modelo, apartando aśı toda la preparación de la ejecución. Esta se encuentra en el archivo code/actionhandler/run harness.py: result\_data = self.harness.run\_harness( flag\_dict=self.harness\_flag\_dict , skip\_generate\_measurements=True) En un primer lugar, la parte cliente de la infraestructura cliente de PMLib estaba diseñada para interactuar con el servidor en lenguaje C, utiliando una API documentada para gestionarla (por ejemplo, para desencadenar lecturas). A continuación, se muestra un ejemplo de lectura utilizando lenguaje C para la interacción con un servidor en Ocejon: #inc lude #inc lude ”pmlib . h” i n t main ( i n t argc , char ∗argv [ ] ) { s e r v e r t s e r v i d o r ; c ount e r t contador ; l i n e t l i n e a s ; d e v i c e t d i sp ; char ∗∗ l i s t a ; i n t i , num devices ; i n t f requency= 10 ; i n t aggregate= 0 ; LINE CLR ALL(& l i n e a s ) ; 26 Medición de potencia p m s e t l i n e s ( ”0−8” , &l i n e a s ) ; p r i n t f ( ”Empieza pm se t s e rve r \n” ) ; pm se t s e rve r ( ” 1 2 7 . 0 . 0 . 1 ” , 6526 , &s e r v i d o r ) ; pm get dev i ce s ( s e rv ido r , &l i s t a , &num devices ) ; pm create counter ( ”NVMLDevice” , l i n e a s , aggregate , f requency , s e rv ido r , & contador ) ; pm star t counter (&contador ) ; // INICIO codigo a muestrear . s l e e p (1 ) ; // FIN codigo a muestrear . pm stop counter(&contador ) ; pm get counter data (&contador ) ; pm pr int data csv ( ” out . csv ” , contador , l i n e a s , −1) ; p m f i n a l i z e c o u n t e r ( &contador ) ; r e turn 0 ; } Obsérvese como el código a muestrear en el anterior ejemplo es simplemente una invocación a sleep durante 1 segundo. Es en este punto en el que se englobaŕıa la medición de consumo en la infraestructura de MLPerf. Sin embargo, debido al uso de Python en el benchmark, el uso de la API nativa en C no es viable desde el cliente. Para resolver este punto, se ha hecho uso de la infraestructura ctypes de Python para crear un wrapper sobre las funciones principales de la API de PMlib, que permite su interacción directa desde cualquier código Python. El código detallado, inluyendo tanto las definiciones de tipos como el propio ejemplo de interacción, puede encontrarse en el apéndice B. 27 Caṕıtulo 6 Diseño de los experimentos Debido a que tenemos dos máquinas a probar, en el repositorio se trabajan con dos carpetas; NVIDIA: correspondiente a la NVIDIA A30 y AGX para la Jetson Orin AGX. En cada una de las carpetas se dispone de los ficheros necesarios para configurar cada modelo, de los binarios correspondientes para poder ejecutarlos, de los resultados obtenidos, aśı como de los Makefile para la contrucción de contenedores en Docker. inference results v3.1 closed NVIDIA a30 bert Offline custom.py bert.sh resultadosEnergiaA30 tabla.sh ... configs build Makefile ... AGX resultadosAGX bert Offline init .py ... resultadosAGX60W resultadosAGX15W bert energia bert.sh tabla.sh ... ... Figura 6.1: Estructura de directorios. 28 https://github.com/GonzaloIslaLlave/TFG-Evaluacion-de-rendimiento-y-eficiencia-energetica-de-procesos-de-inferencia-en-GPUs.git Diseño de los experimentos Para almacenar los resultados se dispone de varias carpetas como se dispone en la figura 6.1: en el caso de la A30 se dispone de la carpeta a30 para el rendimiento, en la que encontramos una carpeta para cada modelo con el propio nombre, y otra con los resultados de la potencia, resultadosEnergiaA30. Además, al haber introducido la herramienta nvpmodel que nos permite configurar la potencia máxima consumida por Jetson Orin AGX, hay que separar los resultados obtenidos de cada caso. En la carpeta resultadosAGX existe una subcarpeta por modelo en la cuál se guardan resultados con potencia máxima, haciendo uso de resultadosAGX60W para almacenar los fichero de las medidas energéticas. En cambio los resultados de la medición con 15W de potencia máxima están guardados en resultadosAGX15W, de nuevo con una carpeta por modelo y otra subcarpeta llamada energia para la propia medición energética. Debido a que cada escenario de cada modelo de cada máquina hace uso de unos ciertos paráme- tros que se definen en diferentes ĺıneas y siendo los ĺımites diferentes, se ha diseñado un script individual a cada modelo, como se ve en el apéndice A. Esto permite automatizar los cambios pertinentes en el propio fichero de configuración. Estos ficheros tienen el nombre del modelo que se ejecuta: bert.sh, rnnt.sh, resnet50.sh, 3d-unet.sh y retinanet.sh. Para facilitar el visionado de los resultados, se ha creado el script tabla.sh, que recibe el modelo y modo de ejecución y saca los resultados válidos de todas las ejecuciones tomadas. MLPerf soporta, entre otras, las GPUs de tipo Orin y contiene su fichero de configuración en configs/(modelo)/(modo)/ init .py , en cambio, no está ampliado para poder ejecutarlo por defecto en la A30. Es por esto por lo que se ha creado un archivo de configuración para cada caso llamado configs/(modelo)/modo/custom.py). Esto también pasa en los escenarios de tipo Server de la máquinas AGX, haciendo uso de la misma técnica para solucionar el problema. Listing 6.1: bert/Offline/custom.py from . import * @ConfigRegistry.register(HarnessType.Custom , AccuracyTarget.k_99 , PowerSetting.MaxP) class OCEJON(OfflineGPUBaseConfig): system = KnownSystem.ocejon gpu_batch_size = 10 offline_expected_qps: float = 1400.0 Como ya hemos indicado, para conseguir un resultado válido se deben seguir una serie de requisi- tos en cuanto a los parámetros que se le pasa al modelo, con el objetivo de superar los ĺımites impues- tos. El propio banco de trabajo recomienda variar ciertos parámetros, dependiendo del modo en el que te encuentres, con el objetivo de maximizar el rendimiento y obtener resultados válidos. Las si- guientes indicaciones vienen especificadas en el fichero: inference results v3.1/closed/NVIDIA /documentation/performance tuning guide.md. En cuanto a Offline se modifica el tamaño máximo de batch (gpu batch size) para hacer variar las peticiones por segundo obtenidas. Las target offline qps basta con que sean mayores a las esperadas. Como veremos en la parte de experimentos más tarde, en Server es algo más complicado, ya que hay que hacer variar el batch y las server target qps para obtener mejores resultados. A diferencia de Offline y Server que el resultado clave se da en qps, aunque también se den otros como latencia, el resultado importante en SingleStream y MultiStream es el percentil 90 de la latencia. Para que este valor sea válido se encuentra modificando single stream expected latency ns en caso de SingleStrem y gpu batch size y multi stream expected latency ns en caso de MultiStream. Para poder sacar datos válidos de la ejecución, primero se hizo un experimento dando datos muy variados en la A30 tanto en batch y qps esperados para hallar los ĺımites superiores. Esto facilitó la medición en la Jetson, ya que al ser bastante más lenta, no se teńıa que ejecutar los modelos sin saber qué valores aproximados medir. De esta forma, se pudieron eliminar casos como no tener que variar los qps esperados en modo Offline ya que el resultado no vaŕıa. 29 Diseño de los experimentos La ejecución de nuestro modelo se hace con la llamada a la instrucción ’make run harness’. De- bido a que tenemos varios modelos a ejecutar, en la carpeta ’code’ se dispone de varias subcarpetas cada una asociada a un modelo. Dentro de cada carpeta tenemos el fichero llamado main ’nombre modelo’.cc, desde donde se inicializa el profiler. Listing 6.2: main bert.cc cudaProfilerStart (); StartTest(bert\_server.get(), qsl.get(), testSettings , logSettings); cudaProfilerStop (); El profiling es una técnica de recopilar información sobre la ejecución de un programa, dando feedback al programador sobre las formas en las que mejorar la latencia y el rendimiento. Debido a que NVIDIA tiene su propio lenguaje de programación (CUDA) y crea sus propias máquinas, tiene su propio mecanismo de profiling llamado nvprof ([?]). De forma gráfica permite dar una conclusión al programador de cómo mejorar la ejecución. Nvprof también funciona para lenguaje OpenMP y OpenACC. En nuestro caso, el número de datos de muestras procesadas por segundo y saber si cumplimos los ĺımites de latencia y rendimiento nos los proporciona LoadGen. En caso de no cumplir alguna de las restricciones, nos devuelve una ejecución INVALID, dando una pequeña explicación del motivo de fallo y, en ciertos casos, una recomendación de qué cambiar.En el siguiente caso se nos indica que se deben aumentar las qps esperadas para que LoadGen pueda generar un petición mayor. Listing 6.3: Aviso error en la ejecución SUT name : LWIS_Server Scenario : Offline Mode : PerformanceOnly Samples per second: 1923.18 Result is : INVALID Min duration satisfied : NO Min queries satisfied : Yes Early stopping satisfied: Yes Recommendations: * Increase expected QPS so the loadgen pre -generates a larger (coalesced) query. Y en caso de que la ejecución sea válida nos da el resultado pertinente al modo de ejecución, indicando que las restricciones se han completado. Listing 6.4: Ejemplo resultado válido. ================================================ MLPerf Results Summary ================================================ SUT name : LWIS_Server Scenario : Offline Mode : PerformanceOnly Samples per second: 1922.92 Result is : VALID Min duration satisfied : Yes Min queries satisfied : Yes Early stopping satisfied: Yes ================================================ Additional Stats ================================================ 30 Diseño de los experimentos Min latency (ns) : 2697916 Max latency (ns) : 102968316008 Mean latency (ns) : 51474352239 50.00 percentile latency (ns) : 51468363515 90.00 percentile latency (ns) : 92666100817 95.00 percentile latency (ns) : 97817389938 97.00 percentile latency (ns) : 99877995893 99.00 percentile latency (ns) : 101938623253 99.90 percentile latency (ns) : 102865765020 Pero si la configuración se aleja mucho de unos los ĺımites razonables del modelo, la ejecución en vez de dar INVALID pasa a dar error de acceso a memoria al internar al crear el engine. Listing 6.5: Error en ejecución AttributeError: ’NoneType ’ object has no attribute ’ create_engine_inspector ’ PyCUDA WARNING: a clean -up operation failed (dead context maybe?) cuMemFree failed: an illegal memory access was encountered 6.1. Limitaciones Durante la construcción y ejecución de los modelos se han encontrado una serie de limitaciones que han impedido utilizar el benchmark en su totalidad. En un primer lugar se decidió realizar el experimento en 3 máquinas: A30, Orin Jetson AGX y Orin Jetson Nano. Como en ésta última no se pudo hacer funcionar MLPerf, se decidió simular con la herramienta nvpmodel, añadiendo la configuración de 15W como máximo, que es la que más se acerca a esta máquina. Como hemos visto hasta ahora, MLPerf contiene hasta 7 distintos modelos pero no todos han podido ser estudiados. En el caso del modelo GPTJ, en la configuración (code/gptj/tensorrt) nos indica que sólo está soportado para la arquitectura SM90, es decir, máquinas Hopper. Al querer hacer uso de máquinas Ampere se nos indica que se debe eliminar el flag ’-a=90’ de Makefile.build. Aún aśı, desafortunadamente siguió dando error al construir el modelo, por lo que se decidió apartar de la investigación. Otra limitación encontrada ha sido en el modelo Resnet50 en modo MultiStream. Debido al gran uso de memoria que hace este modo en este modelo espećıfico, sin importar qué parámetros se le pasase, haćıa incapaz de acceder al servidor, obligando a reiniciarlo, y sin apenas llegar a acabar la ejecución. De forma similar pasa con el modelo Retinanet. Al ser tan potente, ninguna de las configuraciones que se ha pasado al modelo ha permitido poder ejecutarlo. A causa de la complejidad de los modelos, el tiempo de ejecución en la AGX es muy alto, llegando a tardar más de una hora por ejecución. Es por esto por lo que se ha decidido utilizar la opción proporcionada por el benchmark --fast en todos los modelos ejecutados en la Jetson Orin AGX, salvo en Bert, que los tiempos son razonables. Esto reduce el tiempo mı́nimo de ejecución de 10 a 1 minuto, haciendo que la ejecución en su totalidad llegase a tardar entre 5 y 30 minutos. Para la medición de potencia se ha comentado que se utiliza el programa Cliente-Servidor PMLib. Debido al gran uso de recursos que hace uso la ejecución del modelo, la parte del servidor capaz de recopilar la información se detiene en la NVIDIA A30, lo que limita el uso de scripts para la ejecución, y por tanto, el número de mediciones válidas de potencia. 31 Caṕıtulo 7 Resultados Obtenidos Como se ha comentado, en el estudio se analiza el throughput, que en este caso equivale a los qps, la latencia y el rendimiento energético en cada uno de los modelos. 7.1. Bert 7.1.1. Throughput En el caso del throughput nos tenemos que hacer la pregunta de dónde están los ĺımites de las peticiones procesadas de cada máquina, si difieren entre ellas y en cuánto. Figura 7.1: Throughput hallado por cada valor de batch en el modelo Bert. 32 Resultados Obtenidos En este modelo nos encontramos con unos ĺımites de rendimiento muy claros como se ve en la figura 7.1, estabilizándose antes cuanto menor es la potencia máxima. Además vemos un claro aumento en los qps cuanto mayor es esta potencia, alcanzando 1531 qps en la A30 con un batch 70, 512 con batch 30 en la Jetson Orin AGX y 67 qps con batch 20 al limitar la AGX a 15W. 7.1.2. Eficiencia Energética Aunque las GPUs no estén en funcionamiento sabemos que sigue habiendo un consumo estático por estar conectada a la corriente. En el caso de la A30 está en 31.5 W y, por parte de la Orin, la suma de CPU, GPU y otras consumos ronda los 6.5W, sin importar que esté capada la potencia máxima. Para hallar la eficiencia energética primero debemos conocer el gasto de potencia en cada una de las máquinas con distintos valores de batch. Para estos valores se han tomado los siguientes valores de batch: 1, 20 y 70. Figura 7.2: Potencia consumida en cada batch por el modelo Bert en NVIDIA A30. 33 Resultados Obtenidos Figura 7.3: Potencia consumida en cada batch por el modelo Bert en Jetson Orin AGX. Figura 7.4: Potencia consumida en cada batch por el modelo Bert en Jetson Orin AGX con 15W potencia máxima. Aśı como en la A30 (ver gráfica 7.2) se ve como, por mucho que se vaŕıe el batch recibido, la potencia máxima consumida es la máxima con 165W, no pasa lo mismo en la AGX. Y es que la potencia consumida en la AGX con máxima potencia vaŕıa, y mucho, con respecto al batch indicado, como se aprecia en la figura 7.3. Llega a pasar de alrededor de 30W de media con batch 1, subiendo rápidamente a los 50W con batch 10. No se alcanza el máximo de 60W ya que hay que recordar que se tienen las otras dos ĺıneas de consumo: medición de CPU y de subsistema de memoria. En cambio, al capar la máquina a 15W no se dispone de mucho margen de mejora debido a que, de forma más drástica que el caso anterior, el consumo de las otras dos ĺıneas de consumo limitan la potencia consumida. Aún aśı se puede apreciar una pequeña mejora de alrededor de un watio entre las ejecuciones con batch 1 y 70. Esto se observa en la figura 7.4. Ya vistos los gastos de potencia, pasamos a lo que nos interesa: la eficiencia energética. Para ello se crea la tabla 7.1, en la que se disponen los distintos números de batch de referencia en cada una de las máquinas utilizadas. Batch Máquina NVIDIA A30 Jetson Orin AGX Jetson Orin AGX 15W 1 666.422 / 163.842 = 4.068 185.116 / 29.985 = 6.173 38.389 / 5.781 = 6.640 20 1469.62 / 164.123 = 8.957 502.88 / 51.551 = 9.755 65.5127 / 6.580 = 9.955 70 1531.75 / 164.097 = 9.9337 534.112 / 53.787 = 9.931 66.8021 / 6.589 = 10.138 Tabla 7.1: Comparación de latencia entre diferentes máquinas y tamaños de batch para Bert. De aqúı se sacan dos conclusiones: en primer lugar, para un tamaño de batch pequeño da lo esperado, la NVIDIA A30 es menos eficiente al no tener suficientes datos a analizar y consumir la máxima potencia. Pero, sorprendentemente, los datos que se muestran al utilizar un batch de gran tamaño indican que el aumento de los qps en la NVIDIA A30 no es suficiente para contrarrestar la potencia consumida. 34 Resultados Obtenidos 7.1.3. Latencia Siendo Bert el único modelo en el que no se ha utilizado la opción --fast para acelerar el cómputo en la AGX, la diferencia de latencia entre las máquina es mı́nima como se ve en la 7.5. Figura 7.5: Latencia en cada batch para el modelo Bert. 35 Resultados Obtenidos 7.2. Resnet50 7.2.1. Throughput Al igual que con el modelo Bert, Resnet50 no vaŕıa en la existencia clara de unos ĺımites marcados por el rendimiento de la máquina (ver figura 7.6). Estos están marcados alrededor del batch 200 para el caso de la A30, alcanzando más de 19000 qps; con un batch 70 en cuanto a AGX con 6100 peticiones por segundo y batch 7 con apenas 1340 al limitar a 15W. Figura 7.6: Throughput hallado por cada valor de batch en el modelo Resnet50. 7.2.2. Eficiencia energética En cambio, Resnet50 tiene una singularidad con los demás modelos estudiados como se aprecia en la imágen 7.7, y es que vaŕıa la potencia consumida en la A30. Esto quiere decir que debido a la implementación del modelo, con un número muy pequeño de peticiones por segundo no necesita utilizar la potencia máxima disponible. 36 Resultados Obtenidos Figura 7.7: Potencia consumida en cada batch por el modelo Resnet50 en NVIDIA A30. Figura 7.8: Potencia consumida en cada batch por el modelo Resnet50 en Jetson Orin AGX. Figura 7.9: Potencia consumida en cada batch por el modelo Resnet50 en Jetson Orin AGX con 15W potencia máxima. Con la AGX vemos la misma estructura que se véıa en el modelo Bert con las figuras 7.8 y 7.9. Se aprecia un mayor consumo de potencia al aumentar los qps hasta el ĺımite de 40W. De nuevo no hay un mayor consumo debido a que está siendo usado por las otras dos ĺıneas de consumo. Al calcular la eficiencia energética dividiendo los qps entre potencia consumida en los puntos de batch estudiandos, de nuevo se aprecia como una mejor eficiencia en la AGX capando a 15W. Esta diferencia es mucho más drástica que en Bert, siendo más del doble de eficiente para batches grandes, al realizarse un mayor número de qps. Los cálculos se disponen en la tabla 7.2. Para ver la diferencia de forma gráfica se dispone de la figura 7.10. 37 Resultados Obtenidos Batch Máquina NVIDIA A30 Jetson Orin AGX Jetson Orin AGX 15W 1 1927.86 / 95.742 = 20.139 2931.51 / 18.873 = 155.308 1344.51 / 5.573 = 241.183 7 9213.47 / 136.516 = 67.506 5018.74 / 26.769 = 187.420 1731.59 / 6.196 = 279.470 70 18009.9 / 160.638= 112.097 6181.56 / 34.069 = 181.397 1703.14 / 6.230 = 273.408 200 19208.9 / 160.948 = 119.354 6328.86 / 35.729 = 177.202 1456.22 / 5.943= 245.059 Tabla 7.2: Comparación de latencia entre diferentes máquinas y tamaños de batch para Resnet50. Figura 7.10: Eficiencia energética consumida en cada batch por el modelo Resnet50. 38 Resultados Obtenidos 7.2.3. Latencia En la latencia de Resnet50, aunque se puede distinguir un ligero aumento en AGX con 15W de potencia máxima que en la AGX, no es representativa. La comparativa de la AGX con la A30 no es representativa al haber utilizado el flag --fast, acortando aśı el tiempo consumido por la máquina al realizar la inferencia. Aún aśı se aprecia cómo al aumentar el batch hasta llegar a los ĺımites, la latencia no vaŕıa. Figura 7.11: Latencia en cada batch para el modelo Resnet50. 39 Resultados Obtenidos 7.3. Rnnt 7.3.1. Througthput En el modelo Rnnt ocurre un caso algo distinto a los otros dos modelos. Anteriormente hemos visto que cuanto más potente es la máquina se obtiene un mayor rendimineto con un número de batch mayor. Pero este no es el caso de Rnnt como se aprecia en la figura 7.12. Lo que ocurre es que debido al gran aumento exponencial de qps en los batches pequeños de la A30, se llega antes al ĺımite en esta máquina que en la AGX con máxima potencia. Esto no pasa al capar a 15W ya que el escalado es mı́nimo. Figura 7.12: Throughput hallado por cada valor de batch en el modelo Rnnt. 40 Resultados Obtenidos 7.3.2. Eficiencia energética Figura 7.13: Potencia consumida en cada batch por el modelo Rnnt en NVIDIA A30. Figura 7.14: Potencia consumida en cada batch por el modelo Rnnt en Jetson Orin AGX. Figura 7.15: Potencia consumida en cada batch por el modelo Rnnt en Jetson Orin AGX con 15W potencia máxima. Con Rnnt pasa algo parecido que con Bert, en la NVIDIA A30 (ver figura 7.13) para cualquier valor de batch se obtiene que se consume la máxima potencia, mientras que en la AGX para batches pequeños se gasta alrededor de la mitad que con un batch de gran tamaño, como se aprecia en la figura 7.14. En el caso del consumo al capar a 15W, como se ve en la figura 7.3.2, en este modelo apenas se nota diferencia energética al ejecutar con valores diferentes de batch. Al ver la comparativa de eficiencia energética (ver figura 7.3) se dedude que hay una pésima eficiencia para la A30 y Jetson Orin AGX con máxima potencia para un tamaño de batch pequeño. 41 Resultados Obtenidos En esta última configuración, al aumentar el batch, se iguala con la configuración de la AGX al capar a 15W; sin embargo, para la A30 no hay suficiente mejora. Batch Máquina NVIDIA A30 Jetson Orin AGX Jetson Orin AGX 15W 1 79.2639 / 161.821 = 0.4897 16.6376 / 18.009 = 0.9239 1103.78 / 38.008 =29.0360 100 2702.6 / 162.364 = 16.6458 755.523 / 32.825= 23.0151 157.042 / 6.203 = 25.3142 500 4087.84 / 163.330 = 25.0267 1103.78 / 38.008 =29.036 180.047 / 6.074 = 29.6353 Tabla 7.3: Comparación de latencia entre diferentes máquinas y tamaños de batch para Rnnt. 7.3.3. Latencia Figura 7.16: Latencia en cada batch para el modelo Rnnt. Al igual que con el modelo Resnet50, en la gráfica 7.16 no es comparable la latencia obtenida por NVIDIA A30 con la Jetson Orin AGX al haber utilizado la herramienta --fast para disminuir el tiempo de ejecución. Como conclusión de nuevo no hay diferencia en cuanto al número de batch que se le pasa al modelo ni en los tiempos entre las dos configuraciones de la AGX. 42 Resultados Obtenidos 7.4. 3D-Unet Debido a la complejidad del modelo 3D-Unet, sólo se ha podido analizar en la NVIDIA A30 y con la máxima potencia de la Orin con batches muy pequeños. Esto es debido a que apenas es capaz de llegar a 2 qps en la A30 y 0.6 en la Orin. Figura 7.17: Throughput hallado por cada valor de batch en el modelo 3D-Unet. 43 Caṕıtulo 8 Conclusión final y trabajo futuro Sabiendo que MPERF es un banco de trabajo en desarrollo, quiero destacar ciertas ventajas y mejoras posibles a añadir para futuras versiones que ayudaŕıa a la gente sin experiencia a usarlo. En primer lugar se puede mejorar la accesibilidad al campo de datos de Imagenet [18], ya que en la página oficial que indica MLPerf no está disponible. En caso de no tenerlo, da lugar a no poder usar casi la mitad de los benchmarks, ya que resnet50, gptj y retinanet hacen uso de él. Además seŕıa una buena mejora añadir nuevas arquitecturas de NVIDIA que sean soportadas por el benchmark, haciendo que no se tenga que añadir el fichero custom.py. También, en los casos observados donde es suficiente con cambiar los parámetros que se pasan al modelo para hacerlo funcionar, pero la ejecución falla debido a un error espećıfico de acceso a memoria, seŕıa útil mostrar un aviso indicando que éste puede ser el motivo. Debido a que al usar este banco de trabajo, como se hace en este trabajo, se van a querer probar a ejecutar varios modelos o varios escenarios con distintas configuraciones, un área de mejora seŕıa añadir una herramienta que pueda clasificar, ordenar y filtrar los resultados por campos como modelo, modo, fecha de ejecución o por parámetros usados. Como conclusión final, se ha visto una mejora gradual del rendimiento en cuanto a peticiones por segundo procesadas al aumentar el número de batch máximo. La potencia consumida por la A30 es tan alta que aún teniendo esta mejora de peticiones al aumentar el batch, la eficiencia energética no mejora en comparación a la de la AGX. De forma proporcional pasa con las dos configuraciones de la AGX; con la potencia máxima pasa a ser menos eficiente que con la limitación de 15 vatios. Por parte de la latencia se obtiene que, si se ajusta las peticiones esparadas por segundo al resultado obtenido, ésta no vaŕıa por mucho que se modifiquen el número de peticiones máximas pasadas al modelo. Como posible trabajo futuro se puede seguir profundizando en el benchmark, analizando en profundidad los modos Server, SingleStream y MultiStream, y modificando otros parámetros para conseguir un mayor rendimiento. 44 Caṕıtulo 9 Conclusion and future work Knowing that MLPerf is a benchmark in development, I want to highlight certain advantages and possible improvements to add for future versions that would help inexperienced users. First of all, improve accesibility to the Imagenet [18] dataset field, as it is not available on the official page indicated by MLPerf. Without it, almost half of the benchmarks cannot be used, as resnet50, gptj, and retinanet rely on it. Additionally, it would be a good improvement to add new NVIDIA architectures supported by the benchmark, eliminating the need to add the custom.py file. Also, in the observed cases where it is sufficient to change the parameters passed to the model to make it work, but the execution fails due to a specific memory access error, it would be useful to display a warning indicating that this might be the cause. Since using this workbench, as done in this work, involves testing various models or scenarios with different configurations, an area for improvement would be to add a tool that can classify, sort and filter the results by fields such as model, mode, execution date or parameters used. In conclusion, a gradual improvement in performance, measured by requests processed per se- cond, has been observed as the maximum batch size increases. However, the power consumption of the A30 is so high that, despite this improvement, its energy efficiency does not surpass that of the AGX. Similarly, for both configurations of the AGX, operating at maximum power becomes less efficient than the 15-watt limitation. Regarding latency, if the expected requests per second are aligned with the results obtained, it remains relatively constant, regardless of changes in the number of maximum requests submitted to the model. As potential future work, further exploration of the benchmark can be undertaken by analy- zing the Server, SingleStream, and MultiStream modes in greater depth, and by modifying other parameters to achieve higher performance. 45 Apéndice A Script para automatizar MLPerf #!/bin/bash #defienen valores de batch , qps y nanosegundos segun corresponda #AVISO! El periodo tanto de batch , qps y nanosegundos NO puede ser 0 ZERO=0 BATCH_INI =20 #batch inicial BATCH_FIN =100 #batch final BATCH_PER =10 #periodo de batach #qps inicial , final y el perido para modo OFFLINE QPS_OFF_INI =2000 QPS_OFF_FIN =2000 QPS_OFF_PER =500 #qps inicial , final y el perido para modo SERVER QPS_SER_INI =1210 QPS_SER_FIN =1250 QPS_SER_PER =10 #ns inicial , final y el perido para modo SINGLESTREAM NS_SIN_INI =1000000 NS_SIN_FIN =1000000 NS_SIN_PER =1000000 #make run se ejecuta en /work/ cd .. #para cada modelo indicado [Offline Server SingleStream] for j in Offline do #sustituye el valor 0 en el fichero de configuracion por el batch inicial en la linea pertinente sed -i "14s/$ZERO/$BATCHINI/" /work/configs/rnnt/$j/custom.py #RANGO BATCH SIZE #segun el modo indicado se cambia ya sea los qps o ns por el valor inicial if [ "$j" == "Offline" ]; then ini=$(( QPSOFFINI)) fin=$(( QPSOFFFIN)) per=$(( QPSOFFPER)) sed -i "43s/$ZERO .0/ $QPSOFFINI .0/" /work/configs/rnnt/Offline/custom.py elif [ "$j" == "Server" ]; then 46 ini=$(( QPSSERINI)) fin=$(( QPSSERFIN)) per=$(( QPSSERPER)) sed -i "50s/$ZERO/$QPSSERINI/" /work/configs/rnnt/Server/custom.py else ini=$(( NSSININI)) fin=$(( NSSINFIN)) per=$(( NSSINPER)) sed -i "46s/$ZERO/$NSSININI/" /work/configs/rnnt/SingleStream/custom.py fi #doble bucle para realizar todas las combinaciones de batch/qps o batch/ns indicadas for ((batch= BATCH_INI; batch <= BATCH_FIN; batch+= BATCH_PER)) #RANGO BATCH SIZE do for ((qps = $qpsIni; qps <= $qpsFin; qps += $qpsPer)) do #ejecucion make run RUN_ARGS="--benchmarks=rnnt␣--scenarios=$j" > a30/rnnt/"rnnt_${j}_${batch} _${valor}.txt" #cambio de qps/ns if [ "$j" == "Offline" ]; then sed -i "43s/$valor .0/$((valor␣+␣per)).0/" /work/configs/rnnt/$j/custom.py elif [ "$j" == "Server" ]; then sed -i "50s/$valor/$((valor␣+␣per))/" /work/configs/rnnt/$j/custom.py else sed -i "46s/$valor/$((valor␣+␣per))/" /work/configs/rnnt/$j/custom.py fi #fin bucle qps/ns done #cambio de batch sed -i "14s/$batch/$((batch␣+␣BATCHPER))/" /work/configs/rnnt/$j/custom.py if [ "$j" == "Offline" ]; then sed -i "43s/$valor .0/ $ini .0/" /work/configs/rnnt/$j/custom.py elif [ "$j" == "Server" ]; then sed -i "50s/$valor/$ini/" /work/configs/rnnt/$j/custom.py else sed -i "46s/$valor/$ini/" /work/configs/rnnt/$j/custom.py fi #fin segundo bucle done #cambio de valores a 0 para proxima ejecucion sed -i "14s/$batch/$ZERO/" /work/configs/rnnt/$j/custom.py if [ "$j" == "Offline" ]; then sed -i "43s/$ini .0/ $ZERO .0/" /work/configs/rnnt/$j/custom.py elif [ "$j" == "Server" ]; then sed -i "50s/$ini/$ZERO/" /work/configs/rnnt/$j/custom.py else sed -i "46s/$ini/$ZERO/" /work/configs/rnnt/$j/custom.py fi done 47 Apéndice B Ejemplo de cliente PMLib utilizando ctypes from ctypes import * import time so_file = "/work/pmlib_orin.so" pmlib = CDLL(so_file) print(type(pmlib)) print(pmlib.pm_create_counter) DOUBLE_10 = c_double * 10 CHAR_1 = c_char * 1 CHAR_4 = c_char * 4 CHAR_16 = c_char * 16 ## Structure definition. class PM_MeasuresStruct(Structure): _fields_ = ( ("watts_size", c_int), ("watts_sets_size", c_int), ("watts_sets", POINTER(c_int)), ("watts", POINTER(c_double)), ("lines_len", c_int) ) class PM_MeasuresWTStruct(Structure): _fields_ = ( ("next_timing", c_int), ("timing", POINTER(c_double)), ("energy", PM_MeasuresStruct) ) class LineStruct(Structure): 48 _fields_ = [ ("__bits", c_char * 16) ] class DeviceStruct(Structure): _fields_ = ( ("name", c_char_p), ("max_frequency", c_int), ("n_lines", c_int), ) class CounterStruct(Structure): _fields_ = ( ("sock", c_int), ("aggregate", c_int), ("lines", LineStruct), #(" lines", CHAR_16), ("num_lines", c_int), ("interval", c_int), ("pm_measures_wt", POINTER(PM_MeasuresWTStruct)) ) class ServerStruct(Structure): _fields_ = ( ("server_ip", c_char * 16), ("port", c_int), ) # Main program servidor = ServerStruct () contador = CounterStruct () lineas = LineStruct( b’1000000000000000 ’) disp = DeviceStruct () # Start pm_set_lines _pm_set_lines = pmlib.pm_set_lines _pm_set_lines.argtypes = [c_char_p ,POINTER(LineStruct)] _pm_set_lines.restype = c_int lines_string = c_char_p(b’8-10’) _pm_set_lines(lines_string , byref(lineas)) # End pm_set_lines # Start pm_set_server _pm_set_server = pmlib.pm_set_server _pm_set_server.argtypes = [c_char_p ,c_int ,POINTER(ServerStruct)] _pm_set_server.restype = c_int ip = c_char_p(b’127.0.0.1 ’) for field_name , field_type in servidor._fields_: 49 print(field_name , getattr(servidor , field_name)) _pm_set_server(ip , 6526, byref(servidor)) for field_name , field_type in servidor._fields_: print(field_name , getattr(servidor , field_name)) # End pm_set_server # Start pm_create_counter _pm_create_counter = pmlib.pm_create_counter _pm_create_counter.argtypes = [c_char_p ,LineStruct ,c_int ,c_int , ServerStruct ,POINTER(CounterStruct)] _pm_set_server.restype = c_int #device_name = c_char_p(b’ORINNANODevice ’) device_name = c_char_p(b’ORINAGXDevice ’) #device_name = c_char_p(b’NVMLDevice ’) _pm_create_counter(device_name , lineas , 0, 0, servidor , byref( contador)) # End pm_create_counter # Start pm_start_counter _pm_start_counter = pmlib.pm_start_counter _pm_start_counter.argtypes = [POINTER(CounterStruct)] _pm_start_counter.restype = c_int _pm_start_counter(byref(contador)) # End pm_start_counter time.sleep (1) # Start pm_stop_counter _pm_stop_counter = pmlib.pm_stop_counter _pm_stop_counter.argtypes = [POINTER(CounterStruct)] _pm_stop_counter.restype = c_int _pm_stop_counter(byref(contador)) # End pm_stop_counter # Start pm_get_counter_data _pm_get_counter_data = pmlib.pm_get_counter_data _pm_get_counter_data.argtypes = [POINTER(CounterStruct)] _pm_get_counter_data.restype = c_int _pm_get_counter_data(byref(contador)) # End pm_get_counter_data # Start pm_print_data_csv 50 _pm_print_data_csv = pmlib.pm_print_data_csv _pm_print_data_csv.argtypes = [c_char_p ,CounterStruct , LineStruct , c_int] _pm_print_data_csv.restype = c_int file_name = c_char_p(b’test.csv’) _pm_print_data_csv(file_name , contador , lineas , -1) # End print_data_csv # Start pm_stop_counter _pm_finalize_counter = pmlib.pm_finalize_counter _pm_finalize_counter.argtypes = [POINTER(CounterStruct)] _pm_finalize_counter.restype = c_int _pm_finalize_counter(byref(contador)) # End pm_stop_counter 51 Bibliograf́ıa [1] Arjun Suresh y Pablo Gonzalez. Loadgen. https://github.com/mlcommons/inference/ tree/master/loadgen. (visitado 14-03-2024). [2] Jacob Devlin, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. BERT: Pre- training of Deep Bidirectional Transformers for Language Understanding. 2:16, May 2019. https://paperswithcode.com/method/bert. [3] Philipp Fischer Olaf Ronneberger and Thomas Brox. U-net: Convolutional networks for bio- medical image segmentation. 1:8, Nov 2015. [4] Tsung-Yi Lin Priya Goyal Ross Girshick Kaiming He Piotr Dollar. Focal loss for dense object detection. page 10, Feb 2018. [5] MLCommons. Scenarios and metrics of mlperf. https://mlcommons.org/benchmarks/ inference-edge/. (visitado 24-05-2024). [6] Gordon E. Moore. Cramming more components onto integrated circuits. Electronics, 38(8):114– 117, 1965. [7] NVIDIA. Nvidia a30. https://www.nvidia.com/es-es/data-center/products/a30-gpu/. (visitado 29-05-2024). [8] Nvidia. Nvidia jetson orin. https://www.nvidia.com/es-es/autonomous-machines/ embedded-systems/jetson-orin/. (visitado 01-05-2024). [9] Intel. Procesador intel® xeon® silver 4314. https://ark.intel.com/content/www/xl/es/ ark/products/215269/intel-xeon-silver-4314-processor-24m-cache-2-40-ghz.html. (visitado 24-05-2024). [10] NVIDIA. Nvpmodel. https://docs.nvidia.com/jetson/archives/ r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/ JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#supported-modes-and-power-efficiency. (visitado 30-04-2024). [11] Reddi V.J. et al. Mlperf inference benchamrk. page 14, may 2020. [12] Alex Graves. Sequence transduction with recurrent neural networks. 1:9, Nov 2012. [13] Shaoqing Ren Jian Sun Kaiming He, Xiangyu Zhang. Deep residual learning for image recog- nition. page 12, Dec 2015. [14] MathWorks. Anchor boxes. https://es.mathworks.com/help/vision/ug/ anchor-boxes-for-object-detection.html. (visitado 03-05-2024). [15] Ben Wang and Aran Komatsuzaki. GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model. https://github.com/kingoflolz/mesh-transformer-jax, May 2021. 52 https://github.com/mlcommons/inference/tree/master/loadgen https://github.com/mlcommons/inference/tree/master/loadgen https://mlcommons.org/benchmarks/inference-edge/ https://mlcommons.org/benchmarks/inference-edge/ https://www.nvidia.com/es-es/data-center/products/a30-gpu/ https://www.nvidia.com/es-es/autonomous-machines/embedded-systems/jetson-orin/ https://www.nvidia.com/es-es/autonomous-machines/embedded-systems/jetson-orin/ https://ark.intel.com/content/www/xl/es/ark/products/215269/intel-xeon-silver-4314-processor-24m-cache-2-40-ghz.html https://ark.intel.com/content/www/xl/es/ark/products/215269/intel-xeon-silver-4314-processor-24m-cache-2-40-ghz.html https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#supported-modes-and-power-efficiency https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#supported-modes-and-power-efficiency https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#supported-modes-and-power-efficiency https://es.mathworks.com/help/vision/ug/anchor-boxes-for-object-detection.html https://es.mathworks.com/help/vision/ug/anchor-boxes-for-object-detection.html https://github.com/kingoflolz/mesh-transformer-jax [16] Sid Black Laurence Golding Travis Hoppe Charles Foster Jason Phang Horace He Anish Thite Noa Nabeshima Shawn Presser Connor Leahy Leo Gao, Stella Biderman. The pile: An 800gb dataset of diverse text for language modeling. page 39, Dec 2020. [17] NVIDIA. Canales de monitorización de potencia. https://docs.nvidia.com/ jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/ JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#jetson-agx-orin-series. (visitado 1-05-2024). [18] Academic Torrents. Imagenet torrents. https://academictorrents.com/browse.php? search=imagenet&sort_field=seeders&sort_dir=DESC. (visitado 05-04-2024). 53 https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#jetson-agx-orin-series https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#jetson-agx-orin-series https://docs.nvidia.com/jetson/archives/r34.1/DeveloperGuide/text/SD/PlatformPowerAndPerformance/JetsonOrinNxSeriesAndJetsonAgxOrinSeries.html#jetson-agx-orin-series https://academictorrents.com/browse.php?search=imagenet&sort_field=seeders&sort_dir=DESC https://academictorrents.com/browse.php?search=imagenet&sort_field=seeders&sort_dir=DESC Introducción Motivación Objetivos Introduction Motivation Objectives Arquitecturas utilizadas NVIDIA A30 Jetson AGX Orin MLPerf Estructura general y escenarios Modelos disponibles BERT RNN-T 3D-Unet ResNet50 RetinaNet GPT-J DLRMV2 Medición de potencia Servidor NVIDIA A30 Jetson AGX Cliente Diseño de los experimentos Limitaciones Resultados Obtenidos Bert Throughput Eficiencia Energética Latencia Resnet50 Throughput Eficiencia energética Latencia Rnnt Througthput Eficiencia energética Latencia 3D-Unet Conclusión final y trabajo futuro Conclusion and future work Script para automatizar MLPerf Ejemplo de cliente PMLib utilizando ctypes