Mejore el rendimiento de los modelos Falcon con Amazon SageMaker | Servicios web de Amazon

Mejore el rendimiento de los modelos Falcon con Amazon SageMaker | Servicios web de Amazon

¿Cuál es el marco y la configuración óptimos para alojar modelos de lenguaje grandes (LLM) para aplicaciones de IA generativa que generan texto? A pesar de la abundancia de opciones para prestar servicios a los LLM, esta es una pregunta difícil de responder debido al tamaño de los modelos, las diferentes arquitecturas de los modelos, los requisitos de rendimiento de las aplicaciones y más. El Amazon SageMaker Contenedor de inferencia de modelo grande (LMI) simplifica la prestación de servicios a los LLM al reunir una serie de marcos y técnicas diferentes que optimizan la implementación de los LLM. El contenedor LMI tiene una poderosa pila de servicio llamada sirviendo DJL que es independiente del LLM subyacente. Proporciona parámetros de configuración a nivel de sistema que se pueden ajustar para extraer el mejor rendimiento de la infraestructura de alojamiento para un LLM determinado. También es compatible con optimizaciones recientes como el procesamiento por lotes continuo, también conocido como procesamiento por lotes iterativo o procesamiento por lotes continuo, que proporciona mejoras significativas en el rendimiento.

En un post, mostramos cómo se puede utilizar el contenedor LMI para implementar la familia de modelos Falcon en SageMaker. En esta publicación, demostramos cómo mejorar el rendimiento y la latencia del servicio Falcon-40B con técnicas como el procesamiento por lotes continuo. También proporcionamos una comprensión intuitiva de los parámetros de configuración proporcionados por el contenedor LMI de SageMaker que puede ayudarlo a encontrar la mejor configuración para su aplicación del mundo real.

Fundamentos de la inferencia generativa de texto para LLM

Primero veamos algunos fundamentos sobre cómo realizar inferencias para LLM para la generación de texto.

Pase directo, activaciones y caché KV

Dada una secuencia de entrada de tokens, se ejecutan en un forward pass en todas las capas del LLM (como Falcon) para generar el siguiente token. A forward pass se refiere al proceso de pasar datos de entrada a través de una red neuronal para producir una salida. En el caso de la generación de texto, el paso hacia adelante implica introducir una semilla o contexto inicial en el modelo de lenguaje y generar el siguiente carácter o token en la secuencia. Para generar una secuencia de texto, el proceso suele realizarse de forma iterativa, lo que significa que se repite para cada paso o posición en la secuencia de salida. En cada iteración, el modelo genera el siguiente carácter o token, que pasa a formar parte del texto generado, y este proceso continúa hasta que se genera la longitud de texto deseada.

La generación de texto con modelos de lenguaje como Falcon o GPT son autoregressive. Esto significa que el modelo genera un token a la vez mientras se condiciona a los tokens generados previamente. En otras palabras, en cada iteración, el modelo toma como entrada el texto generado previamente y predice el siguiente token en función de ese contexto. Como se menciona en vLLM: LLM fácil, rápido y económico con PagedAttention, en este proceso de decodificación autorregresiva, todos los tokens de entrada al LLM producen sus tensores de valor y clave de atención, y estos tensores se guardan en la memoria de la GPU para generar los siguientes tokens. Estos tensores de valores y claves almacenados en caché a menudo se denominan KV cache.

Fases de precarga y decodificación

En un proceso de decodificación autorregresivo, como el que se utiliza en la generación de texto con modelos de lenguaje como Falcon, normalmente hay dos fases principales: la prefill fase y la decode fase. Estas fases son cruciales para generar un texto coherente y contextualmente relevante.

La fase de precarga incluye lo siguiente:

  • Contexto inicial – La fase de prellenado comienza con un contexto inicial o texto inicial proporcionado por el usuario. Este contexto inicial puede ser una oración, una frase o incluso una sola palabra. Establece el punto de partida para la generación de texto y proporciona contexto para lo que viene a continuación.
  • Condicionamiento modelo – El contexto proporcionado se utiliza para condicionar el modelo de lenguaje. El modelo toma este contexto como entrada y genera el siguiente token (palabra o carácter) en la secuencia en función de su comprensión del contexto.
  • Generación de tokens – El modelo genera un token a la vez, prediciendo lo que debería seguir en el texto. Este token se agrega al contexto, ampliándolo efectivamente.
  • Proceso iterativo – El proceso de generación de tokens se repite de forma iterativa. En cada paso, el modelo genera un token teniendo en cuenta el contexto actualizado, que ahora incluye los tokens generados en los pasos anteriores.

La fase de prellenado continúa hasta que se cumple una condición de parada predeterminada. Esta condición puede ser una longitud máxima para el texto generado, un token específico que señala el final del texto o cualquier otro criterio establecido por el usuario o la aplicación.

La fase de decodificación incluye lo siguiente:

  • Cierre – Después de la fase de prerrelleno, tienes un texto parcialmente generado que puede estar incompleto o cortado en algún momento. La fase de decodificación se encarga de completar el texto para hacerlo coherente y gramaticalmente correcto.
  • Continuación del último token. – En la fase de decodificación, el modelo comienza desde el último token generado durante la fase de precarga. Utiliza este token como contexto inicial y genera el siguiente token para continuar el texto.
  • Finalización iterativa – Al igual que en la fase de precarga, el proceso de generación de tokens vuelve a ser iterativo. El modelo genera un token a la vez, condicionado a los tokens anteriores en la secuencia.
  • condición de parada – La fase de decodificación también tiene una condición de parada, que puede ser la misma que en la fase de precarga, como alcanzar una longitud máxima o encontrar un token de fin de texto. Cuando se cumple esta condición, el proceso de generación se detiene.

La combinación de las fases de prellenado y decodificación permite que los modelos autorregresivos generen texto que se basa en un contexto inicial y produce secuencias de texto coherentes, contextualmente relevantes y contextualmente consistentes.

Consulte Un sistema de servicio distribuido para modelos generativos basados ​​en transformadores para una explicación detallada del proceso.

Optimización del rendimiento mediante procesamiento por lotes dinámico

Hasta ahora, sólo hemos hablado de una única entrada. En la práctica, esperamos tratar con múltiples solicitudes que llegan aleatoriamente desde los clientes de la aplicación para realizar inferencias de forma simultánea o escalonada. De la forma tradicional, se puede utilizar el procesamiento por lotes básico para aumentar el rendimiento y la utilización de los recursos informáticos de la GPU. El procesamiento por lotes consiste en combinar de manera efectiva las representaciones numéricas de más de una solicitud en un lote y realizar ejecuciones paralelas de los pases directos autorregresivos. Este procesamiento por lotes inteligente se realiza en el lado de servicio. El servidor DJLServing de SageMaker LMI se puede configurar para agrupar múltiples solicitudes y procesarlas en paralelo configurando los siguientes parámetros en sirviendo.propiedades:

  • max_batch_delay = 100 – El retraso máximo para la agregación de lotes en milisegundos. El valor predeterminado es 100 milisegundos.
  • tamaño del lote = 32 – El tamaño del lote dinámico. El valor predeterminado es 1.

Básicamente, esto muestra que DJLServing pondrá en cola las solicitudes durante 100 milisegundos a la vez o si la cantidad de solicitudes en cola es igual al tamaño de lote especificado, el lote se programará para ejecutarse en el backend para realizar inferencias. Esto se conoce como dynamic batching. Es dinámico porque el tamaño del lote puede cambiar entre lotes dependiendo de cuántas solicitudes se agregaron en ese período de tiempo. Sin embargo, debido a que las solicitudes pueden tener características diferentes (por ejemplo, algunas solicitudes pueden tener la forma de 20 tokens de entrada y 500 tokens de salida, mientras que otras pueden ser invertidas, con 500 tokens de entrada pero solo 20 de salida), algunas solicitudes pueden complete el procesamiento más rápido que otros en el mismo lote. Esto podría dar como resultado una subutilización de la GPU mientras se espera que todas las solicitudes en curso del lote completen su etapa de decodificación, incluso si hay solicitudes adicionales esperando ser procesadas en la cola. El siguiente diagrama ilustra este proceso.

Visual de procesamiento por lotes dinámico simple

Visual de procesamiento por lotes dinámico: observe las ventanas inactivas al final de las Solicitudes 2 y 3

Optimización del rendimiento mediante procesamiento por lotes continuo

Con continuous batching, también conocido como iterative or rolling procesamiento por lotes, aprovechamos las diferencias entre las etapas de precarga y decodificación. Para activar el procesamiento por lotes continuo, DJServing proporciona las siguientes configuraciones adicionales según server.properties:

  • motor=MPI: le recomendamos que utilice el motor MPI para el procesamiento por lotes continuo.
  • opción.rolling_batch=auto o lmi-dist: recomendamos usar auto porque seleccionará automáticamente el algoritmo de lote continuo más apropiado junto con otras optimizaciones en el futuro.
  • opción.max_rolling_batch_size=32: esto limita el número de solicitudes simultáneas. El valor predeterminado es 32.

Con el procesamiento por lotes continuo, la pila de entrega (DJLServing) no espera a que todas las solicitudes en curso de un lote completen su etapa de decodificación. Más bien, en las pausas lógicas (al final de una iteración en la etapa de decodificación), atrae solicitudes adicionales que están esperando en la cola mientras el lote actual aún se está procesando (de ahí el nombre lote rodante). Realiza esta verificación de solicitudes pendientes al final de cada iteración de la etapa de decodificación. Recuerde, para cada solicitud, debemos ejecutar la etapa de prellenado seguida de la etapa de decodificación secuencial. Debido a que podemos procesar todos los tokens desde el mensaje inicial de una solicitud en paralelo para su etapa de prellenado, cada vez que se ingresa una nueva solicitud, pausamos temporalmente la etapa de decodificación de las solicitudes en curso del lote; guardamos temporalmente su caché KV. y activaciones en memoria y ejecutar la etapa de prellenado de las nuevas solicitudes.

El tamaño de este caché se puede configurar con la siguiente opción:

Cuando se completa el llenado previo, combinamos las solicitudes nuevas y las solicitudes antiguas en pausa en un nuevo lote continuo, que puede continuar con su etapa de decodificación en paralelo. Tenga en cuenta que las antiguas solicitudes pausadas pueden continuar su etapa de decodificación donde las dejaron y las nuevas solicitudes comenzarán desde su primer token nuevo.

Visual de lotes continuo o iterativo

Visual de lotes continuo o iterativo: observe que los tiempos de inactividad se reemplazan con solicitudes de seguimiento

Es posible que ya se haya dado cuenta de que el procesamiento por lotes continuo es un enfoque casi similar con el que naturalmente paralelizamos tareas en nuestra vida diaria. Tenemos mensajes, correos electrónicos, notificaciones telefónicas (solicitudes potencialmente nuevas) que llegan en momentos aleatorios (análogo a las solicitudes múltiples que llegan de forma escalonada al azar para las GPU). Todo esto sucede mientras completamos nuestras tareas durante el vuelo: redactar correos electrónicos, codificar, participar en reuniones (análogas a las tareas de procesamiento actual en las GPU). En las pausas lógicas, pausamos nuestras tareas en vuelo y verificamos nuestras notificaciones para decidir si se requiere alguna acción de nuestra parte y, si es así, la agregamos a nuestras tareas en vuelo (lote rodante de la vida real), o póngalo en una lista de tareas pendientes (la cola).

Poniéndolo todo junto: cómo pensar en la utilización de la memoria de las GPU

Se recomienda realizar una prueba de carga de su modelo para ver qué configuración es la más rentable para su caso de uso empresarial. Para comprenderlo, visualicemos la huella de memoria de las GPU a medida que se carga el modelo y a medida que las solicitudes sucesivas se procesan en un lote continuo. Para esta publicación, supongamos que estamos cargando el modelo Falcon-40B en uno de los tipos de instancia G5 que se instalan con GPU NVIDIA A10G, cada una con 24 GB de memoria. Tenga en cuenta que se aplica un entendimiento similar para los tipos de instancias p3, p4 y p5, que vienen con las series de GPU V100, A100 y H100.

La siguiente es una descripción general para obtener un valor aproximado de la memoria total requerida para dar servicio al Falcon-40B:

  • Tamaño del modelo = Número de parámetros del modelo (40 mil millones para Falcon-40B) x 4 bytes por parámetro (para FP32) = 160 GB
  • Memoria total aproximada requerida para cargar Falcon-40B para inferencia = Tamaño del modelo (=160 GB) + Caché KV (caché de atención) (=*20 GB) + sobrecarga de memoria adicional por parte de ML Frameworks (aproximadamente 2 GB)
Memoria Visual

Memory Visual: comprensión del uso de memoria de un modelo Falcon-40B cargado

Para Falcon-40B, si comprimimos el modelo cuantizándolo al tipo de datos bfloat16 (2 bytes), el tamaño del modelo pasa a ser de aproximadamente 80 GB. Como puede ver, esto es aún mayor que la memoria admitida por un dispositivo acelerador, por lo que debemos adoptar una técnica de partición (fragmentación) de modelos con un método especial. paralelismo tensorial (TP) enfoque y fragmente el modelo en múltiples dispositivos aceleradores. Supongamos que hemos elegido g5.24xlarge, que tiene 4 dispositivos GPU A10G. Si configuramos DJLServing (serving.properties) con lo siguiente, podemos esperar que los 80 GB de peso del modelo se dividan equitativamente entre las 4 GPU:

Con tensor_parallel_degree Si se establece en 4, aproximadamente 20 GB de la memoria de la GPU de 24 GB (aproximadamente el 84 %) ya se utilizan incluso antes de que se haya procesado una sola solicitud. El 16% restante de la GPU se utilizará para la caché KV para las solicitudes entrantes. Es posible que para su escenario empresarial y sus requisitos de latencia y rendimiento, 2 o 3 GB de memoria restante sean más que suficientes. De lo contrario, puede aumentar el tamaño de la instancia a g5.48xlarge, que tiene 8 GPU y usa tensor_parallel_title establecido en 8. En tal caso, solo se utilizan aproximadamente 10 GB de los 24 GB de memoria disponibles de cada GPU para los pesos del modelo y nosotros tenga aproximadamente el 60% de la GPU restante para las activaciones y el caché KV. Intuitivamente podemos ver que esta configuración puede permitirnos tener un mayor rendimiento. Además, debido a que ahora tenemos un búfer más grande, podemos aumentar el max_rolling_batch_prefill_tokens y max_rolling_batch_size parámetros para optimizar aún más el rendimiento. Juntos, estos dos parámetros controlarán las asignaciones previas de los precargas de activación y la caché KV para el modelo. Un número mayor para estos dos parámetros se correlacionará con un rendimiento mayor, suponiendo que tenga suficiente búfer para la caché KV en la memoria de la GPU.

Procesamiento por lotes continuo con PagedAttention

PagedAttention es un nuevo algoritmo de optimización desarrollado por UC Berkeley que mejora el proceso de procesamiento por lotes continuo al permitir que el caché de atención (caché KV) no sea contiguo mediante la asignación de memoria en páginas o bloques de tamaño fijo. Está inspirado en la memoria virtual y los conceptos de paginación utilizados por los sistemas operativos.

Según el vllm En papel, el caché de atención de cada secuencia de tokens se divide en bloques y se asigna a bloques físicos a través de una tabla de bloques. Durante el cálculo de la atención, un kernel PagedAttention puede usar la tabla de bloques para recuperar de manera eficiente los bloques de la memoria física. Esto da como resultado una reducción significativa del desperdicio de memoria y permite un mayor tamaño de lote, una mayor utilización de la GPU y un mayor rendimiento.

Comparación de rendimiento

Para garantizar una prueba de carga eficaz de la configuración de su implementación, se recomienda comenzar considerando el escenario empresarial y definiendo claramente las características de la entrada y salida de la aplicación basada en LLM. Por ejemplo, si está trabajando en un caso de uso de resumen de un centro de llamadas, la entrada podría consistir en texto más grande, como una transcripción de chat de 500 tokens entre un agente de servicio al cliente y un cliente, pero la salida podría ser relativamente más pequeña, alrededor de 100. tokens, que representan un resumen de la transcripción. Por otro lado, si está trabajando en un escenario de generación de código, la entrada podría ser tan corta como 15 tokens, como "escribir una implementación eficiente en Python para describir todos los recursos de EC2, incluida la paginación", pero la salida podría ser mucho mayor. más grande, llegando a 500 tokens. También es importante considerar si lograr una latencia más baja o maximizar el rendimiento es la máxima prioridad para su escenario específico.

Después de obtener una comprensión integral del escenario empresarial, podrá analizar y determinar la configuración óptima para su entorno de alojamiento. En este contexto, el entorno de alojamiento abarca varios elementos clave, incluido el tipo de instancia y otros parámetros de configuración como tensor_paralelo_grado, max_rolling_batch_size, max_rolling_batch_prefill_tokens, y más. Nuestro objetivo es identificar la configuración más eficaz para respaldar nuestros requisitos de tiempo de respuesta, rendimiento y calidad de salida del modelo.

En nuestro análisis, comparamos el rendimiento para ilustrar los beneficios del procesamiento por lotes continuo sobre el procesamiento por lotes dinámico tradicional. Usamos las configuraciones detalladas en la siguiente tabla en server.properties para procesamiento por lotes dinámico y procesamiento por lotes iterativo, usando un contenedor LMI en SageMaker.

Lotes dinámicos Dosificación continua Procesamiento por lotes continuo con PagedAttention

motor = Python

option.model_id=tiiuae/falcon-40b

opción.tensor_parallel_grado=8

opción.dtype=fp16

tamaño_lote=4

max_batch_delay=100

option.trust_remote_code = verdadero

motor = MPI

opción.model_id = {{s3_url}}

option.trust_remote_code = verdadero

opción.tensor_parallel_grado = 8

opción.max_rolling_batch_size = 32

opción.rolling_batch = automático

opción.dtype = fp16

opción.max_rolling_batch_prefill_tokens = 1024

option.paged_attention = Falso

motor = MPI

opción.model_id = {{s3_url}}

option.trust_remote_code = verdadero

opción.tensor_parallel_grado = 8

opción.max_rolling_batch_size = 32

opción.rolling_batch = automático

opción.dtype = fp16

opción.max_rolling_batch_prefill_tokens = 1024

option.paged_attention = Verdadero

Las dos configuraciones se compararon para Falcon-40B con el tipo de datos FP16 implementado en ml.g5.48xlarge en un par de escenarios diferentes que representan aplicaciones del mundo real:

  • Se genera una pequeña cantidad de tokens de entrada con una gran cantidad de tokens – En este escenario, la cantidad de tokens de entrada se fijó en 32 y se generaron 128 tokens nuevos.
Estrategia de lotes Rendimiento (tokens/seg) Latencia p90 (segundos)
Lotes dinámicos 5.53 58.34
Dosificación continua 56.04 4.74
Procesamiento por lotes continuo con PagedAttention 59.18 4.76
  • Se genera una gran entrada con una pequeña cantidad de tokens. – Aquí, fijamos la cantidad de tokens de entrada en 256 y solicitamos al LLM que resuma la entrada en 32 tokens.
Estrategia de lotes Rendimiento (tokens/seg) Latencia p90 (segundos)
Lotes dinámicos 19.96 59.31
Dosificación continua 46.69 3.88
Procesamiento por lotes continuo con PagedAttention 44.75 2.67

Podemos ver que el procesamiento por lotes continuo con PagedAttention proporciona una mejora del rendimiento 10 veces mayor en el escenario 1 y 2.3 veces en el escenario 2 en comparación con el uso del procesamiento por lotes dinámico en SageMaker mientras se usa el contenedor LMI.

Conclusión

En esta publicación, analizamos cómo los LLM usan la memoria y explicamos cómo el procesamiento por lotes continuo mejora el rendimiento usando un contenedor LMI en SageMaker. Demostramos los beneficios del procesamiento por lotes continuo para Falcon-40B utilizando un contenedor LMI en SageMaker mostrando resultados de referencia. Puedes encontrar el código en el Repositorio GitHub.


Acerca de los autores

Abhigyan ShivadityaAbhi Shivaditya es arquitecto sénior de soluciones en AWS y trabaja con organizaciones empresariales globales estratégicas para facilitar la adopción de los servicios de AWS en áreas como la inteligencia artificial, la computación distribuida, las redes y el almacenamiento. Su experiencia radica en el Aprendizaje Profundo en los dominios de Procesamiento del Lenguaje Natural (PNL) y Visión por Computador. Abhi ayuda a los clientes a implementar modelos de aprendizaje automático de alto rendimiento de manera eficiente dentro del ecosistema de AWS.

Mejore el rendimiento de los modelos Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Inteligencia de datos. Búsqueda vertical. Ai.Patel Dhawal es Arquitecto Principal de Aprendizaje Automático en AWS. Ha trabajado con organizaciones que van desde grandes empresas hasta empresas emergentes medianas en problemas relacionados con la computación distribuida y la inteligencia artificial. Se enfoca en el aprendizaje profundo, incluidos los dominios de NLP y Computer Vision. Ayuda a los clientes a lograr una inferencia de modelos de alto rendimiento en SageMaker.

Mejore el rendimiento de los modelos Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Inteligencia de datos. Búsqueda vertical. Ai.Panigrahi rosa trabaja con clientes para crear soluciones impulsadas por el aprendizaje automático para resolver problemas comerciales estratégicos en AWS. Cuando no está ocupado con el aprendizaje automático, se le puede encontrar haciendo una caminata, leyendo un libro o viendo deportes.

Mejore el rendimiento de los modelos Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Inteligencia de datos. Búsqueda vertical. Ai.Abhi Sodhani ocupa el puesto de Arquitecto senior de soluciones de IA/ML en AWS, donde se especializa en ofrecer experiencia técnica y orientación sobre soluciones de IA generativa y ML a los clientes. Su objetivo principal es ayudar a las empresas nativas digitales a aprovechar todo el potencial de las tecnologías de IA generativa y aprendizaje automático, permitiéndoles alcanzar sus objetivos comerciales de forma eficaz. Más allá de sus esfuerzos profesionales, Abhi muestra una gran pasión por actividades intelectuales como la lectura, además de participar en actividades que promueven el bienestar físico y mental, como el yoga y la meditación.

Mejore el rendimiento de los modelos Falcon con Amazon SageMaker | Amazon Web Services PlatoBlockchain Inteligencia de datos. Búsqueda vertical. Ai.qing-lan es ingeniero de desarrollo de software en AWS. Ha estado trabajando en varios productos desafiantes en Amazon, incluidas soluciones de inferencia ML de alto rendimiento y un sistema de registro de alto rendimiento. El equipo de Qing lanzó con éxito el primer modelo de mil millones de parámetros en Amazon Advertising con una latencia muy baja requerida. Qing tiene un conocimiento profundo sobre la optimización de la infraestructura y la aceleración del aprendizaje profundo.

Sello de tiempo:

Mas de Aprendizaje automático de AWS