Escalando la inferencia del modelo similar a BERT en CPU modernas – Parte 2

'Scaling BERT-like model inference on modern CPUs - Part 2'

Introducción: Utilizando el software de Intel para optimizar la eficiencia de la IA en CPU

Como detallamos en nuestra publicación de blog anterior, los procesadores Intel Xeon proporcionan un conjunto de características especialmente diseñadas para cargas de trabajo de IA, como AVX512 o VNNI (Instrucciones de Redes Neuronales Vectoriales) para una inferencia eficiente utilizando una red neuronal cuantizada entera para la inferencia, junto con herramientas de sistema adicionales para garantizar que el trabajo se realice de la manera más eficiente. En esta publicación de blog, nos centraremos en las optimizaciones de software y le daremos una idea de las capacidades de la nueva generación de CPUs Xeon Ice Lake de Intel. Nuestro objetivo es brindarle una imagen completa de lo que está disponible en el lado del software para aprovechar al máximo su hardware de Intel. Al igual que en la publicación de blog anterior, mostramos el rendimiento con resultados de referencia y gráficos, junto con nuevas herramientas para facilitar el uso de todos estos ajustes y características.

En abril, Intel lanzó su última generación de procesadores Intel Xeon, con el nombre en clave Ice Lake, enfocados en cargas de trabajo de IA más eficientes y con mejor rendimiento. Más precisamente, los CPUs Xeon Ice Lake pueden lograr hasta un 75% de inferencia más rápida en una variedad de tareas de NLP al compararlos con la generación anterior de procesadores Xeon Cascade Lake. Esto se logra mediante una combinación de mejoras tanto de hardware como de software, como nuevas instrucciones y PCIe 4.0 presentes en la nueva arquitectura Sunny Cove para admitir cargas de trabajo de Aprendizaje Automático y Aprendizaje Profundo. Por último, pero no menos importante, Intel trabajó en optimizaciones dedicadas para varios frameworks que ahora vienen con las versiones de Intel, como la Extensión de Intel para Scikit Learn, Intel TensorFlow e Intel PyTorch Extension.

Todas estas características son de bajo nivel en la pila de lo que los Científicos de Datos e Ingenieros de Aprendizaje Automático utilizan en su conjunto de herramientas diario. En la gran mayoría de las situaciones, es más común confiar en frameworks y bibliotecas de nivel superior para manipular matrices multidimensionales, como PyTorch y TensorFlow, y utilizar operadores matemáticos altamente optimizados, como BLAS (Subrutinas Básicas de Álgebra Lineal) para la parte computacional.

En esta área, Intel desempeña un papel esencial al proporcionar componentes de software bajo el paraguas de oneAPI, lo que facilita el uso de rutinas de álgebra lineal altamente eficientes a través de Intel oneMKL (Math Kernel Library), un marco de paralelización de nivel superior con Intel OpenMP o los Threading Building Blocks (oneTBB). Además, oneAPI proporciona algunas bibliotecas específicas del dominio, como Intel oneDNN para primitivas de redes neuronales profundas (ReLU, totalmente conectadas, etc.) o oneCCL para comunicación colectiva especialmente útil al utilizar configuraciones distribuidas para acceder a operaciones de reducción eficientes en varios hosts.

Algunas de estas bibliotecas, especialmente MKL u oneDNN, se incluyen nativamente en frameworks como PyTorch y TensorFlow (a partir de la versión 2.5.0) para ofrecer todas las mejoras de rendimiento al usuario final desde el principio. Cuando uno desee aprovechar características de hardware muy específicas, Intel proporciona versiones personalizadas del software más común, especialmente optimizadas para la plataforma de Intel. Este es el caso, por ejemplo, con TensorFlow, para el cual Intel proporciona versiones personalizadas, altamente optimizadas y ajustadas del framework, o con la Extensión de PyTorch de Intel (IPEX), que se puede considerar como un laboratorio de funciones antes de ser incluido en PyTorch.

Profundizando: Aprovechando características avanzadas de Intel para mejorar el rendimiento de la IA

Perfeccionamiento del rendimiento

Como se mencionó anteriormente, vamos a cubrir un nuevo conjunto de elementos ajustables para mejorar el rendimiento de nuestra aplicación de IA. Desde un punto de vista de alto nivel, todos los frameworks de aprendizaje automático y aprendizaje profundo están compuestos por los mismos componentes:

  1. Una forma estructural de representar datos en memoria (vectores, matrices, etc.)
  2. Implementación de operadores matemáticos
  3. Paralelización eficiente de los cálculos en el hardware objetivo

Además de los puntos mencionados anteriormente, los frameworks de aprendizaje profundo proporcionan formas de representar el flujo de datos y las dependencias para calcular gradientes. Esto está fuera del alcance de esta publicación de blog, ¡y utiliza los mismos componentes que los mencionados anteriormente!

Figura 1. Resumen de las bibliotecas de Intel bajo el paraguas de oneAPI

1. Bibliotecas de asignación y gestión de memoria

Esta publicación de blog omitirá deliberadamente el primer punto sobre la representación de datos, ya que es algo específico de cada framework. Como referencia, PyTorch utiliza su propia implementación, llamada ATen, mientras que TensorFlow se basa en la biblioteca de código abierto Eigen para este propósito.

Aunque es muy complejo aplicar optimizaciones genéricas a diferentes estructuras y diseños de objetos, hay un área en la que podemos tener un impacto: la asignación de memoria. Como recordatorio breve, la asignación de memoria aquí se refiere al proceso de solicitar programáticamente al sistema operativo un área dinámica (desconocida de antemano) en el sistema donde podremos almacenar elementos, como el malloc y derivados en C o el operador new en C++. La eficiencia de la memoria, tanto en términos de velocidad como de fragmentación, es un amplio tema científico e ingenieril con múltiples soluciones dependiendo de la tarea y el hardware subyacente. En los últimos años, hemos visto cada vez más trabajos en esta área, con notablemente:

  • jemalloc (Facebook – 2005)
  • mimalloc (Microsoft – 2019)
  • tcmalloc (Google – 2020)

Cada uno impulsa diferentes enfoques para mejorar aspectos de la asignación y gestión de memoria en diversos software.

2. Paralelización eficiente de cálculos

Ahora que tenemos una forma eficiente de representar nuestros datos, necesitamos una forma de aprovechar al máximo el hardware computacional a nuestra disposición. Curiosamente, cuando se trata de inferencia, las CPUs tienen una ventaja potencial sobre las GPUs en el sentido de que están en todas partes y no requieren componentes de aplicación específicos y personal de administración para operar.

Las CPUs modernas cuentan con muchos núcleos y mecanismos complejos para aumentar el rendimiento general del software. Sin embargo, como destacamos en la primera publicación del blog, también tienen características que se pueden ajustar según el tipo de carga de trabajo (CPU o I/O) que se desea mejorar aún más el rendimiento de su aplicación.

Sin embargo, implementar algoritmos paralelos puede no ser tan simple como agregar más núcleos para hacer el trabajo. Muchos factores, como las estructuras de datos utilizadas, el acceso concurrente a datos, la invalidación de las cachés de la CPU, todos ellos pueden evitar que su algoritmo sea efectivamente más rápido. Como referencia, recomendamos la charla de Scott Meyers: CPU Caches and Why You Care si está interesado en profundizar más en el tema.

Afortunadamente, existen bibliotecas que facilitan el proceso de desarrollo de dichos algoritmos paralelos, reduciendo los errores. Entre las bibliotecas paralelas más comunes podemos mencionar OpenMP y TBB (Threading Building Blocks), que funcionan en varios niveles, desde la API de programación en C/C++ hasta la optimización de variables de entorno y programación dinámica. En hardware Intel, se recomienda utilizar la implementación de Intel de la especificación OpenMP, a menudo denominada “IOMP”, disponible como parte del kit de herramientas Intel oneAPI.

Figura 2. Fragmento de código que muestra cálculos paralelos realizados a través de OpenMP

3. Operadores matemáticos optimizados

Ahora que hemos cubierto los bloques de construcción necesarios para diseñar estructuras de datos eficientes y algoritmos paralelos, la última pieza restante es aquella que ejecuta los cálculos, aquella que implementa la variedad de operadores matemáticos y capas de redes neuronales para hacer lo que más nos gusta, ¡diseñar redes neuronales! 😊

En el kit de herramientas de todo programador, hay múltiples niveles que pueden brindar soporte para operaciones matemáticas, las cuales pueden optimizarse de manera diferente según varios factores, como el diseño de almacenamiento de datos utilizado (memoria contigua, segmentada, empaquetada, etc.), el formato de datos que representa cada elemento escalar (Float32, Integer, Long, Bfloat16, etc.) y, por supuesto, las diversas instrucciones admitidas por su procesador.

Hoy en día, casi todos los procesadores admiten operaciones matemáticas básicas en elementos escalares (un solo elemento a la vez) o en modo vectorizado (lo que significa que operan en múltiples elementos dentro de las mismas instrucciones de la CPU, denominadas SIMD “Single Instruction Multiple Data”). Conjuntos famosos de instrucciones SIMD son SSE2, AVX, AVX2 y AVX-512, presentes en las últimas generaciones de CPUs de Intel, capaces de operar sobre 16 bytes de contenido en un solo ciclo de CPU.

La mayoría de las veces, uno no tiene que preocuparse demasiado por el ensamblaje real que se genera para ejecutar una simple suma de elementos entre dos vectores, pero si lo hace, nuevamente existen algunas bibliotecas que le permiten ir un nivel más alto que escribir código que llama a instrucciones específicas de la CPU para implementar núcleos matemáticos eficientes. Esto es precisamente lo que proporciona la “Math Kernel Library” (MKL) de Intel, junto con la famosa interfaz BLAS “Basic Linear Algebra Subroutines” para implementar todas las operaciones básicas de álgebra lineal.

Por último, además de esto, se pueden encontrar bibliotecas específicas de dominio, como oneDNN de Intel, que proporciona todos los bloques de construcción más comunes y esenciales necesarios para implementar capas de redes neuronales. Intel MKL y oneDNN están integrados de forma nativa en el marco de PyTorch, donde pueden mejorar el rendimiento de ciertas operaciones como Linear + ReLU o Convolution. En el caso de TensorFlow, oneDNN se puede habilitar configurando la variable de entorno TF_ENABLE_ONEDNN_OPTS=1 (TensorFlow >= 2.5.0) para lograr una funcionalidad similar en el fondo.

Procesamiento de IA más eficiente en las últimas CPUs Intel Ice Lake

Para informar sobre el rendimiento de la línea de productos Ice Lake, seguiremos de cerca la metodología que usamos para la primera publicación del blog de esta serie. Como recordatorio, adoptaremos el mismo esquema exacto para evaluar las diversas configuraciones que destacaremos en esta segunda publicación del blog. Más precisamente, los resultados presentados en las siguientes secciones se basan en:

  • PyTorch: 1.9.0
  • TensorFlow: 2.5.0
  • Tamaños de lote: 1, 4, 8, 16, 32, 128
  • Longitudes de secuencia: 8, 16, 32, 64, 128, 384, 512

Presentaremos los resultados a través de métricas aceptadas por el campo para establecer el rendimiento de las optimizaciones propuestas:

  • Latencia: Tiempo que tarda en ejecutarse una única solicitud de inferencia (es decir, “llamada hacia adelante”) a través del modelo, expresado en milisegundos.
  • Rendimiento: Número de solicitudes de inferencia (es decir, “llamadas hacia adelante”) que el sistema puede mantener en un período definido, expresado en llamadas/segundo.

También proporcionaremos una línea base inicial que muestre los resultados tal como están y una segunda línea base aplicando todas las diferentes optimizaciones que destacamos en el primer artículo del blog. Todo se ejecutó en una instancia en la nube proporcionada por Intel que cuenta con el procesador Ice Lake Xeon Platinum 8380 y funciona en Ubuntu 20.04.2 LTS.

Puede encontrar los mismos procesadores en los diversos proveedores de servicios en la nube:

  • Instancias AWS m6i / c6i
  • Series Azure Ev5 / Dv5

Figura 3. Especificaciones del procesador Intel Ice Lake Xeon 8380

Estableciendo la línea base

Como se mencionó anteriormente, las líneas base estarán compuestas por dos configuraciones diferentes: – Tal como está: Ejecutamos las cargas de trabajo tal como están, sin ningún ajuste – Optimizado: Aplicamos los diferentes ajustes presentes en el Blog n.° 1

También, a partir de los comentarios que recibimos sobre el artículo del blog anterior, queríamos cambiar la forma en que presentamos el marco de trabajo dentro de los resultados obtenidos. Por lo tanto, a lo largo de este segundo artículo del blog, dividiremos los resultados de las pruebas de rendimiento del marco de trabajo de la siguiente manera:

  • Frameworks que utilizan el modo “eager” para cálculos (PyTorch, TensorFlow)
  • Frameworks que utilizan el modo “graph” para cálculos (TorchScript, TensorFlow Graph, Intel Tensorflow)

Línea base: Latencias de frameworks en modo “eager”

Los frameworks que operan en modo “eager” suelen descubrir el gráfico de cálculo real mientras lo ejecutan. Más precisamente, el gráfico de cálculo real no se conoce de antemano y gradualmente (de manera “eager”) se ejecuta un operador que se convertirá en la entrada del siguiente, etc., hasta llegar a los nodos hoja (salidas).

Estos frameworks suelen proporcionar mayor flexibilidad en el algoritmo que se implementa a costa de un mayor tiempo de ejecución y un uso ligeramente mayor de memoria para realizar un seguimiento de todos los elementos necesarios para el pase hacia atrás.

Por último, pero no menos importante, suele ser más difícil habilitar optimizaciones de gráficos en estos frameworks, como la fusión de operadores. Por ejemplo, muchas bibliotecas de aprendizaje profundo, como oneDNN, tienen núcleos optimizados para la operación de convolución + ReLU, pero en realidad necesitas saber antes de ejecutar el gráfico que este patrón ocurrirá en la secuencia de operaciones, lo cual, por diseño, no es algo posible dentro de los frameworks en modo “eager”.

Figura 4. Latencias de PyTorch en función del número de núcleos involucrados

Figura 5. Latencias de TensorFlow de Google en función del número de núcleos involucrados

Figura 6. Latencias de TensorFlow de Google con oneDNN habilitado en función del número de núcleos involucrados

Figura 7. Latencias de Intel TensorFlow en función del número de núcleos involucrados

La tendencia global destaca el impacto positivo del número de núcleos en las latencias observadas. En la mayoría de los casos, aumentar el número de núcleos reduce el tiempo de cálculo en diferentes tamaños de carga de trabajo. Sin embargo, asignar más núcleos a la tarea no siempre resulta en reducciones monótonas de la latencia, siempre hay un equilibrio entre el tamaño de la carga de trabajo y el número de recursos que asignas para ejecutar el trabajo.

Como se puede ver en los gráficos anteriores, un patrón muy común tiende a surgir al utilizar todos los núcleos disponibles en sistemas con más de una CPU (más de un socket). La comunicación entre sockets introduce una sobrecarga de latencia significativa y produce una mejora muy pequeña en la latencia total.

Además, esta sobrecarga de comunicación entre sockets tiende a ser menos perceptible a medida que la carga de trabajo se vuelve más grande, lo que significa que el uso de todos los recursos computacionales se beneficia al utilizar todos los núcleos disponibles. En este sentido, parece que PyTorch (Figura 1) e Intel TensorFlow (Figura 4) tienen un soporte de paralelismo ligeramente mejor, como se muestra en las longitudes de secuencia 384 y 512, para las cuales el uso de todos los núcleos aún reduce la latencia observada.

Referencia: Latencias de los frameworks de gráficos

Esta vez comparamos el rendimiento al usar frameworks en modo “gráfico”, donde el gráfico se conoce completamente de antemano y se pueden realizar todas las asignaciones y optimizaciones, como la poda de gráficos y la fusión de operadores.

Figura 8. Latencias de TorchScript en función del número de núcleos involucrados

Figura 9. Latencias de TensorFlow de Google en función del número de núcleos involucrados

Figura 10. Latencias de TensorFlow de Google con oneDNN habilitado en función del número de núcleos involucrados

Figura 11. Latencias de TensorFlow de Intel en función del número de núcleos involucrados

A menudo se hace referencia a esto como “trazar” el gráfico y, como puede ver aquí, los resultados no son muy diferentes entre TorchScript (modo de ejecución de gráficos de PyTorch) y TensorFlow(s). Todas las implementaciones de TensorFlow parecen funcionar mejor que TorchScript cuando la paralelización es limitada (bajo número de núcleos involucrados en los cálculos de operaciones internas), pero esto parece no escalar eficientemente a medida que aumentamos los recursos de cálculo, mientras que TorchScript parece poder aprovechar mejor la potencia de las CPUs modernas.

Aún así, la diferencia entre todos estos frameworks en la mayoría de los casos es muy limitada.

Ajuste del asignador de memoria: ¿Puede esto afectar las latencias observadas?

Un componente crucial en cada programa que asigna memoria dinámicamente es el asignador de memoria. Si está familiarizado con la programación en C/C++, este componente proporciona los detalles necesarios para malloc/free o new/delete. La mayoría de las veces no tienes que preocuparte mucho por ello y los asignadores predeterminados (como glibc en la mayoría de las distribuciones de Linux) brindarán un excelente rendimiento de manera predeterminada. Sin embargo, en algunas situaciones puede que no brinden el rendimiento más eficiente, ya que estos asignadores predeterminados están diseñados la mayor parte del tiempo para ser “buenos” en la mayoría de los casos, y no están optimizados para cargas de trabajo o paralelismo específicos.

Entonces, ¿cuáles son las alternativas y cuándo son más adecuadas que las predeterminadas? Bueno, nuevamente, depende del tipo de contexto en torno a su software.

Las posibles situaciones incluyen un gran número de asignaciones/desasignaciones que causan fragmentación con el tiempo, hardware y/o arquitectura específicos en los que se ejecuta su software y, finalmente, el nivel de paralelismo de su aplicación.

¿Ves hacia dónde va esto? El aprendizaje profundo y, por extensión, todas las aplicaciones que realizan cálculos intensivos son altamente multihilo, y ese también es el caso de bibliotecas de software como PyTorch, TensorFlow y cualquier otro framework dirigido a cargas de trabajo de aprendizaje automático.

Los estrategias de asignación de memoria predeterminadas a menudo dependen de grupos de memoria globales que requieren el uso de primitivas de sincronización para funcionar, lo que aumenta la presión general en el sistema y reduce el rendimiento de su aplicación. Algunos trabajos recientes de empresas como Google, Facebook y Microsoft han proporcionado estrategias de asignación de memoria alternativas implementadas en bibliotecas de asignadores de memoria personalizadas que se pueden integrar fácilmente directamente en los componentes de su software o usar una biblioteca compartida dinámica para cambiar la biblioteca que se utiliza para lograr la asignación/desasignación.

Entre estas bibliotecas, podemos mencionar algunas como tcmalloc, jemalloc y mimalloc.

Figura 12. Comparación de diferentes asignadores de memoria en diferentes tareas

En esta publicación del blog, nos centraremos únicamente en la comparación de tcmalloc y jemalloc como posibles candidatos para reemplazar el asignador de memoria. Para ser completamente transparentes, para el alcance de los resultados a continuación, utilizamos tcmalloc como parte del paquete gperftools disponible en las distribuciones de Ubuntu versión 2.9 y jemalloc 5.1.0-1.

Comparaciones de asignadores de memoria

Nuevamente, primero comparamos el rendimiento con frameworks que se ejecutan de manera ansiosa. Este es potencialmente el caso de uso donde el asignador de memoria puede desempeñar el papel más importante: dado que el gráfico es desconocido antes de su ejecución, cada framework debe administrar la memoria requerida para cada operación cuando se encuentra con la ejecución real del nodo anterior, no es posible planificar con anticipación. En este contexto, el asignador de memoria es un componente importante debido a todas las llamadas al sistema para asignar y recuperar memoria.

Figura 13. Asignador de memoria de PyTorch y latencias en función de la escala de núcleos

Figura 14. Asignador de memoria de TensorFlow de Google y latencias en función de la escala de núcleos

Figura 15. Asignador de memoria de TensorFlow de Google con oneDNN habilitado y latencias en función de la escala de núcleos

Figura 16. Asignador de memoria de TensorFlow de Intel y latencias en función de la escala de núcleos

Según el gráfico anterior, se puede observar que el asignador de la biblioteca estándar (glibc) a menudo brinda un rendimiento inferior pero razonable. El asignador jemalloc a veces es el más rápido, pero en situaciones muy específicas donde la concurrencia no es tan alta, esto se puede explicar por la estructura interna que jemalloc utiliza, lo cual está fuera del alcance de este blog, pero puedes leer el blog de Facebook Engineering si quieres saber más al respecto.

Finalmente, tcmalloc parece ser el que ofrece en general el mejor rendimiento en todos los casos de prueba evaluados aquí. Una vez más, tcmalloc tiene un enfoque diferente al de Jemalloc en la forma en que asigna recursos, especialmente tcmalloc mantiene una piscina de segmentos de memoria localmente para cada hilo, lo que reduce la necesidad de tener caminos críticos globales, exclusivos y críticos.

Nuevamente, para obtener más detalles, te invito a leer el blog completo del equipo de Google Abseil.

Ahora, volvamos al modo gráfico donde evaluamos el marco de trabajo que tiene una representación omnisciente del gráfico de cálculo general.

Figura 17. Asignación de memoria de TorchScript y latencias de escalado de núcleos.

Figura 18. Asignación de memoria de TensorFlow de Google y latencias de escalado de núcleos.

Figura 19. Asignación de memoria de TensorFlow de Google con oneDNN habilitado y latencias de escalado de núcleos.

Figura 20. Asignación de memoria de TensorFlow de Intel y latencias de escalado de núcleos.

Esta vez, al conocer la estructura subyacente de los flujos de operadores y las formas de las matrices involucradas, el marco de trabajo puede planificar y reservar los recursos requeridos de antemano. En este contexto, y como se muestra en el gráfico anterior, la diferencia entre los marcos de trabajo es muy pequeña y no hay un claro ganador entre jemalloc y tcmalloc. Por supuesto, glibc sigue estando ligeramente por detrás como un asignador de memoria de propósito general, pero el margen es menos significativo que en la configuración ansiosa. En resumen, ajustar el asignador de memoria puede proporcionar un elemento interesante para aprovechar la mejora de los últimos milisegundos al final del proceso de optimización, especialmente si ya estás utilizando gráficos de cálculo rastreados.

OpenMP

En la sección anterior hablamos sobre la gestión de memoria dentro del software de aprendizaje automático que involucra principalmente cargas de trabajo limitadas por la CPU. Este tipo de software a menudo se basa en marcos de trabajo intermedios como PyTorch o TensorFlow para el aprendizaje profundo, que comúnmente abstraen todas las implementaciones subyacentes altamente paralelizadas de los operadores.

Escribir algoritmos altamente paralelos y optimizados de este tipo es un verdadero desafío de ingeniería y requiere un conocimiento muy detallado de todos los elementos reales que intervienen y que son operados por la CPU (sincronización, caché de memoria, validez de la caché, etc.). En este contexto, es muy importante poder aprovechar las primitivas para implementar algoritmos tan potentes, reduciendo el tiempo de entrega y el tiempo de cálculo en gran medida en comparación con implementar todo desde cero.

Existen muchas bibliotecas disponibles que proporcionan características de nivel superior para acelerar el desarrollo de algoritmos. Entre las más comunes, se pueden mencionar OpenMP, Thread Building Blocks y directamente desde C++ al apuntar a una versión reciente del estándar. En la siguiente parte de esta publicación de blog, nos limitaremos a OpenMP y especialmente a comparar la implementación de código abierto y basada en la comunidad de GNU con la de Intel OpenMP. Este último se enfoca especialmente en las CPUs de Intel y está optimizado para proporcionar un rendimiento de primer nivel cuando se utiliza como reemplazo directo del OpenMP de GNU.

OpenMP expone muchas variables de entorno para configurar automáticamente los recursos subyacentes que participarán en los cálculos, como el número de hilos que se utilizarán para despachar los cálculos (hilos intra-op), la forma en que el programador del sistema debe vincular cada uno de estos hilos con respecto a los recursos de la CPU (hilos, núcleos, sockets) y algunas otras variables que brindan un mayor control al usuario. Intel OpenMP expone más de estas variables de entorno para proporcionar al usuario aún más flexibilidad para ajustar el rendimiento de su software.

Figura 21. Latencias de OpenMP frente a Intel OpenMP ejecutando PyTorch.

Figura 22. Latencias de OpenMP frente a Intel OpenMP ejecutando PyTorch.

Como se mencionó anteriormente, ajustar OpenMP es algo que puedes comenzar a modificar cuando hayas probado todas las demás opciones de ajuste relacionadas con el sistema. Puede brindar una mejora final a la velocidad de tu modelo con solo un variable de entorno para configurar.

También es importante tener en cuenta que ajustar la biblioteca de OpenMP solo funcionará dentro del software que utiliza internamente la API de OpenMP. Específicamente, ahora solo PyTorch y TorchScript hacen uso real de OpenMP y, por lo tanto, se benefician de la optimización del backend de OpenMP.

Esto también explica por qué solo reportamos las latencias para estos dos marcos de trabajo.

Ajuste automático de rendimiento: Optimización bayesiana con Intel SigOpt

Como se mencionó anteriormente, se pueden ajustar muchos parámetros para mejorar la latencia y el rendimiento en las CPUs de Intel, pero debido a que hay muchos, ajustar todos ellos para obtener un rendimiento óptimo puede ser complicado. Por ejemplo, en nuestros experimentos, se ajustaron los siguientes parámetros:

  • El número de núcleos: aunque usar tantos núcleos como sea posible suele ser una buena idea, no siempre proporciona el mejor rendimiento porque también significa más comunicación entre los diferentes hilos. Además, tener un mejor rendimiento con menos núcleos puede ser muy útil, ya que permite ejecutar varias instancias al mismo tiempo, lo que resulta en una mejor latencia y rendimiento.
  • El asignador de memoria: ¿cuál de los asignadores de memoria predeterminados malloc, tcmalloc de Google y jemalloc de Facebook ofrece el mejor rendimiento?
  • La biblioteca de paralelismo: ¿cuál de las bibliotecas de paralelismo GNU OpenMP e Intel OpenMP ofrece el mejor rendimiento?
  • Páginas enormes transparentes: ¿habilitar las páginas enormes transparentes (THP) en el sistema proporciona un mejor rendimiento?
  • Parámetro de tiempo de bloqueo de KMP: establece el tiempo, en milisegundos, que un hilo debe esperar después de completar la ejecución de una región paralela antes de dormir.

Por supuesto, el enfoque de fuerza bruta, que consiste en probar todas las posibilidades, proporcionará los mejores valores de perilla a utilizar para obtener un rendimiento óptimo, pero el tamaño del espacio de búsqueda siendo N x 3 x 2 x 2 x 2 = 24N, puede llevar mucho tiempo: en una máquina con 80 núcleos físicos, ¡esto significa probar como máximo 24 x 80 = 1920 configuraciones diferentes! 😱

Afortunadamente, SigOpt de Intel, a través de la optimización bayesiana, nos permite realizar estos experimentos de ajuste de manera más rápida y conveniente de analizar, al tiempo que proporciona un rendimiento similar al enfoque de fuerza bruta.

Cuando analizamos la diferencia relativa entre la mejor latencia absoluta y lo que proporciona SigOpt, observamos que aunque a menudo no es tan bueno como la fuerza bruta (excepto para la longitud de secuencia = 512 en ese caso específico), proporciona un rendimiento muy similar, con una diferencia máxima de 8.6% en esta figura.

Figura 23. Mejor latencia absoluta encontrada por el ajuste automático de SigOpt frente a la fuerza bruta Figura 24. Mejor latencia relativa encontrada por el ajuste automático de SigOpt frente a la fuerza bruta

SigOpt también es muy útil para el análisis: proporciona muchas figuras e información valiosa. Primero, muestra el mejor valor que pudo encontrar, las perillas correspondientes y el historial de pruebas y cómo mejoró a medida que avanzaban las pruebas, por ejemplo, con longitud de secuencia = 20:

Figura 25. Informe del mejor valor de SigOpt Figura 26. Informe del mejor valor de SigOpt

En esta configuración específica, 16 núcleos junto con las otras perillas pudieron brindar los mejores resultados, lo cual es muy importante saber, porque como se mencionó antes, eso significa que varias instancias del modelo se pueden ejecutar en paralelo y aún así tener la mejor latencia para cada una.

También muestra que había convergido en aproximadamente 20 pruebas, lo que significa que tal vez 25 pruebas en lugar de 40 hubieran sido suficientes. Hay una amplia gama de otras información valiosa disponible, como la Importancia de los Parámetros:

Como era de esperar, el número de núcleos es, con mucho, el parámetro más importante, pero los demás también juegan un papel, y depende mucho del experimento. Por ejemplo, para el experimento de longitud de secuencia = 512, esta fue la Importancia de los Parámetros:

Figura 27. Mejor valor de SigOpt para Tamaño de Lote = 1, Longitud de Secuencia = 20 Figura 28. Mejor valor de SigOpt para Tamaño de Lote = 1, Longitud de Secuencia = 512

Aquí, no solo el impacto de usar OpenMP vs Intel OpenMP fue mayor que el impacto del asignador de memoria, sino que la importancia relativa de cada perilla es más equilibrada que en el experimento de longitud de secuencia = 20. Y hay muchas más figuras, a menudo interactivas, disponibles en SigOpt, como:

  • Historial de experimentos en 2D, que permite comparar perillas vs perillas o perillas vs objetivos
  • Historial de experimentos en 3D, que permite hacer lo mismo que el historial de experimentos en 2D con una perilla / objetivo más.

Conclusión – Acelerando Transformers para Producción

En esta publicación, mostramos cómo las nuevas CPUs Intel Ice Lake Xeon son adecuadas para ejecutar cargas de trabajo de IA a gran escala junto con los elementos de software que se pueden intercambiar y ajustar para aprovechar todo el potencial del hardware. Todos estos elementos deben considerarse después de configurar las varias perillas de nivel inferior detalladas en el blog anterior para maximizar el uso de todos los núcleos y recursos.

En Hugging Face, tenemos la misión de democratizar el aprendizaje automático de vanguardia, y una parte crítica de nuestro trabajo es hacer que estos modelos de vanguardia sean lo más eficientes posible, que utilicen menos energía y memoria a escala, y que sean más asequibles de ejecutar para empresas de todos los tamaños.

Nuestra colaboración con Intel a través del Programa de Socios de Hardware 🤗 nos permite poner fácilmente a disposición de la comunidad técnicas avanzadas de eficiencia y optimización a través de nuestra nueva biblioteca de código abierto 🤗 Optimum dedicada al rendimiento en producción.

Para las empresas que buscan acelerar la inferencia de sus modelos Transformer, nuestro nuevo producto 🤗 Infinity ofrece una solución lista para usar, logrando una latencia de hasta 1 ms en GPU y 2 ms en CPUs Intel Xeon Ice Lake.

Si encontraste esta publicación interesante o útil para tu trabajo, por favor considera darle una estrella a Optimum. Y si esta publicación fue música para tus oídos, ¡considera unirte a nuestro equipo de Optimización de Aprendizaje Automático!

We will continue to update Zepes; if you have any questions or suggestions, please contact us!

Share:

Was this article helpful?

93 out of 132 found this helpful

Discover more

Ciencia de Datos

Investigadores crean una herramienta para simular con precisión sistemas complejos.

El sistema que desarrollaron elimina una fuente de sesgo en las simulaciones, lo que conduce a algoritmos mejorados q...

Inteligencia Artificial

Haciendo la vida más amigable con robots personales

Sharifa Alghowinem, una científica investigadora del Media Lab, explora la tecnología de robots personales que explic...

Inteligencia Artificial

Deci presenta DeciCoder un modelo de lenguaje grande de código abierto con 1 billón de parámetros para generación de código.

En el mundo acelerado de la IA, la generación eficiente de código es un desafío que no se puede pasar por alto. Con l...

Inteligencia Artificial

El modelo de IA puede ayudar a determinar dónde se originó el cáncer de un paciente

Las predicciones del modelo OncoNPC podrían permitir a los médicos elegir tratamientos específicos para tumores difíc...

Inteligencia Artificial

¡Atención Industria del Gaming! No más espejos extraños con Mirror-NeRF

Las NeRF o Campos de Radiancia Neurales utilizan una combinación de RNN y CNN para capturar las características físic...

Ciencias de la Computación

Más personas están quedando ciegas. La IA puede ayudar a combatirlo.

La detección temprana es crucial para tratar enfermedades oculares. Los análisis de escaneo ocular mejorados por AI p...