Ir al contenido principal

Ingeniería del rendimiento de la inferencia de LLM: prácticas recomendadas


Comparte esta publicación
LLM Inference Performance Engineering: Best Practices

En esta entrada de blog, el equipo de ingeniería de MosaicML comparte las prácticas recomendadas sobre cómo capitalizar los modelos de lenguaje grandes (LLM) de código abierto populares para el uso en producción. También proporcionamos directrices para implementar servicios de inferencia construidos en torno a estos modelos para ayudar a los usuarios en su selección de modelos y hardware de implementación. Hemos trabajado con varios backends basados en PyTorch en producción; estas directrices se extraen de nuestra experiencia con FasterTransformers, vLLM, TensorRT-LLM de NVIDIA, que se lanzará próximamente, y otros.

Comprensión de la generación de texto de LLM

Los modelos de lenguaje grandes (LLM) generan texto en un proceso de dos pasos: "prefill", donde los tokens en el prompt de entrada se procesan en paralelo, y "decoding", donde el texto se genera un "token" a la vez de manera autorregresiva. Cada token generado se anexa a la entrada y se retroalimenta al modelo para generar el siguiente token. La generación se detiene cuando el LLM genera un token de parada especial o cuando se cumple una condición definida por el usuario (por ejemplo, se ha generado un número máximo de tokens). Si desea obtener más información sobre cómo los LLM utilizan bloques de decodificador, consulte esta entrada de blog.

Los tokens pueden ser palabras o subpalabras; las reglas exactas para dividir el texto en tokens varían de un modelo a otro. Por ejemplo, puede comparar cómo los modelos Llama tokenizan el texto con cómo los modelos OpenAI tokenizan el texto. Aunque los proveedores de inferencia de LLM a menudo hablan sobre el rendimiento en métricas basadas en tokens (por ejemplo, tokens/segundo), estos números no siempre son comparables entre los tipos de modelos dadas estas variaciones. Para un ejemplo concreto, el equipo de Anyscale descubrió que la tokenización de Llama 2 es un 19 % más larga que la tokenización de ChatGPT (pero aún tiene un costo general mucho menor). Y los investigadores de HuggingFace también descubrieron que Llama 2 requería ~20 % más de tokens para entrenar sobre la misma cantidad de texto que GPT-4.

Métricas importantes para el servicio de LLM

Entonces, ¿cómo debemos pensar exactamente sobre la velocidad de inferencia?

Nuestro equipo utiliza cuatro métricas clave para el servicio de LLM:

  1. Tiempo hasta el primer token (TTFT): la rapidez con la que los usuarios comienzan a ver la salida del modelo después de ingresar su consulta. Los tiempos de espera bajos para una respuesta son esenciales en las interacciones en tiempo real, pero menos importantes en las cargas de trabajo sin conexión. Esta métrica se basa en el tiempo necesario para procesar el prompt y luego generar el primer token de salida.
  2. Tiempo por token de salida (TPOT): tiempo para generar un token de salida para cada usuario que consulta nuestro sistema. Esta métrica se corresponde con cómo cada usuario percibirá la "velocidad" del modelo. Por ejemplo, un TPOT de 100 milisegundos/tok sería de 10 tokens por segundo por usuario, o ~450 palabras por minuto, lo que es más rápido de lo que una persona típica puede leer.
  3. Latencia: el tiempo total que tarda el modelo en generar la respuesta completa para un usuario. La latencia general de la respuesta se puede calcular utilizando las dos métricas anteriores: latencia = (TTFT) + (TPOT) * (el número de tokens que se generarán).
  4. Rendimiento: el número de tokens de salida por segundo que un servidor de inferencia puede generar en todos los usuarios y solicitudes.

¿Nuestro objetivo? El tiempo más rápido hasta el primer token, el mayor rendimiento y el tiempo más rápido por token de salida. En otras palabras, queremos que nuestros modelos generen texto lo más rápido posible para tantos usuarios como podamos admitir.

En particular, existe una compensación entre el rendimiento y el tiempo por token de salida: si procesamos 16 consultas de usuario simultáneamente, tendremos un rendimiento mayor en comparación con la ejecución de las consultas secuencialmente, pero tardaremos más en generar tokens de salida para cada usuario.

Si tiene objetivos generales de latencia de inferencia, aquí hay algunas heurísticas útiles para evaluar modelos:

  • La longitud de salida domina la latencia general de la respuesta: para la latencia promedio, generalmente puede tomar la longitud de token de salida máxima/esperada y multiplicarla por un tiempo promedio general por token de salida para el modelo.
  • La longitud de entrada no es significativa para el rendimiento, pero es importante para los requisitos de hardware: la adición de 512 tokens de entrada aumenta la latencia menos que la producción de 8 tokens de salida adicionales en los modelos MPT. Sin embargo, la necesidad de admitir entradas largas puede dificultar el servicio de los modelos. Por ejemplo, recomendamos usar el A100-80GB (o más reciente) para servir MPT-7B con su longitud de contexto máxima de 2048 tokens.
  • La latencia general se escala de forma sublineal con el tamaño del modelo: en el mismo hardware, los modelos más grandes son más lentos, pero la relación de velocidad no necesariamente coincidirá con la relación de recuento de parámetros. La latencia de MPT-30B es ~2,5 veces la latencia de MPT-7B. La latencia de Llama2-70B es ~2 veces la latencia de Llama2-13B.

Los clientes potenciales a menudo nos piden que proporcionemos una latencia de inferencia promedio. Recomendamos que antes de anclarse a objetivos de latencia específicos ("necesitamos menos de 20 ms por token"), dedique algún tiempo a caracterizar su entrada esperada y las longitudes de salida deseadas.

Desafíos en la inferencia de LLM

La optimización de la inferencia de LLM se beneficia de técnicas generales como:

  • Fusión de operadores: la combinación de diferentes operadores adyacentes a menudo da como resultado una mejor latencia.
  • Cuantificación: las activaciones y los pesos se comprimen para usar un número menor de bits.
  • Compresión: dispersión o destilación.
  • Paralelización: paralelismo de tensores en varios dispositivos o paralelismo de canalización para modelos más grandes.

Más allá de estos métodos, existen muchas optimizaciones importantes específicas del transformador. Un excelente ejemplo de esto es el almacenamiento en caché KV (clave-valor). El mecanismo de atención en los modelos basados en transformadores solo de decodificador es computacionalmente ineficiente. Cada token atiende a todos los tokens vistos anteriormente y, por lo tanto, vuelve a calcular muchos de los mismos valores a medida que se genera cada nuevo token. Por ejemplo, al generar el token N, el token (N-1) atiende a los tokens (N-2), (N-3) … 1. Del mismo modo, al generar el token (N+1), la atención para el token N nuevamente necesita mirar los tokens (N-1), (N-2), (N-3), … 1. El almacenamiento en caché KV, es decir, el guardado de claves/valores intermedios para las capas de atención, se utiliza para preservar esos resultados para su reutilización posterior, evitando la computación repetida.

El ancho de banda de la memoria es clave

Los cálculos en los LLM están dominados principalmente por operaciones de multiplicación matriz-matriz; estas operaciones con pequeñas dimensiones suelen estar limitadas por el ancho de banda de la memoria en la mayoría del hardware. Al generar tokens de manera autorregresiva, una de las dimensiones de la matriz de activación (definida por el tamaño del lote y el número de tokens en la secuencia) es pequeña en tamaños de lote pequeños. Por lo tanto, la velocidad depende de la rapidez con la que podemos cargar los parámetros del modelo desde la memoria de la GPU a las cachés/registros locales, en lugar de la rapidez con la que podemos calcular los datos cargados. El ancho de banda de memoria disponible y alcanzado en el hardware de inferencia es un mejor predictor de la velocidad de generación de tokens que su rendimiento máximo de cálculo.

La utilización del hardware de inferencia es muy importante en términos de costos de servicio. Las GPU son caras y necesitamos que hagan la mayor cantidad de trabajo posible. Los servicios de inferencia compartidos prometen mantener los costos bajos al combinar las cargas de trabajo de muchos usuarios, llenar los vacíos individuales y agrupar las solicitudes superpuestas. Para modelos grandes como Llama2-70B, solo logramos una buena relación costo/rendimiento en tamaños de lote grandes. Tener un sistema de servicio de inferencia que pueda operar en tamaños de lote grandes es fundamental para la eficiencia de costos. Sin embargo, un lote grande significa un tamaño de caché KV más grande, y eso a su vez aumenta el número de GPU necesarias para servir el modelo. Aquí hay un tira y afloja y los operadores de servicios compartidos deben hacer algunas compensaciones de costos e implementar optimizaciones de sistemas.

Utilización del ancho de banda del modelo (MBU)

¿Qué tan optimizado está un servidor de inferencia de LLM?

Como se explicó brevemente antes, la inferencia para LLM en tamaños de lote más pequeños, especialmente en el tiempo de decodificación, está limitada por la rapidez con la que podemos cargar los parámetros del modelo desde la memoria del dispositivo a las unidades de cálculo. El ancho de banda de la memoria dicta la rapidez con la que se produce el movimiento de datos. Para medir la utilización del hardware subyacente, presentamos una nueva métrica llamada Utilización del ancho de banda del modelo (MBU). MBU se define como (ancho de banda de memoria alcanzado) / (ancho de banda de memoria máximo) donde el ancho de banda de memoria alcanzado es ((tamaño total del parámetro del modelo + tamaño de la caché KV) / TPOT).

Por ejemplo, si un parámetro de 7B que se ejecuta con una precisión de 16 bits tiene un TPOT igual a 14 ms, entonces está moviendo 14 GB de parámetros en 14 ms, lo que se traduce en un uso de ancho de banda de 1 TB/segundo. Si el ancho de banda máximo de la máquina es de 2 TB/segundo, estamos ejecutando a un MBU del 50 %. Para simplificar, este ejemplo ignora el tamaño de la caché KV, que es pequeño para tamaños de lote más pequeños y longitudes de secuencia más cortas. Los valores de MBU cercanos al 100 % implican que el sistema de inferencia está utilizando eficazmente el ancho de banda de memoria disponible. MBU también es útil para comparar diferentes sistemas de inferencia (hardware + software) de manera normalizada. MBU es complementario a la métrica de Utilización de Flops del modelo (MFU; introducida en el documento de PaLM), que es importante en configuraciones limitadas por el cálculo.

La figura 1 muestra una representación pictórica de MBU en un gráfico similar a un gráfico de línea de techo. La línea inclinada sólida de la región sombreada en naranja muestra el rendimiento máximo posible si el ancho de banda de la memoria está completamente saturado al 100 %. Sin embargo, en realidad para tamaños de lote bajos (punto blanco), el rendimiento observado es inferior al máximo: cuánto más bajo es una medida del MBU. Para tamaños de lote grandes (región amarilla), el sistema está limitado por el cálculo, y el rendimiento alcanzado como una fracción del rendimiento máximo posible se mide como la Utilización de Flops del modelo (MFU).

Utilización del ancho de banda del modelo
Figura 1: ilustra MBU (Utilización del ancho de banda del modelo) y MFU (Utilización de Flops del modelo). MBU y MFU son fracciones de los picos alcanzados en las regiones limitadas por la memoria y limitadas por el cálculo, respectivamente.

MBU y MFU determinan cuánto más espacio está disponible para impulsar aún más la velocidad de inferencia en una configuración de hardware determinada. La figura 2 muestra el MBU medido para diferentes grados de paralelismo de tensores con nuestro servidor de inferencia basado en TensorRT-LLM. La utilización máxima del ancho de banda de la memoria se alcanza al transferir grandes fragmentos de memoria contiguos. Cuando los modelos más pequeños como MPT-7B se distribuyen en varias GPU, observamos un MBU más bajo a medida que movemos fragmentos de memoria más pequeños en cada GPU.

MBU observado empíricamente
Figura 2: MBU observado empíricamente para diferentes grados de paralelismo de tensores con TensorRT-LLM en GPU A100-40G. Solicitudes: secuencias de 512 tokens de entrada con un tamaño de lote de 1.

La figura 3 muestra el MBU observado empíricamente para diferentes grados de paralelismo de tensores y tamaños de lote en las GPU NVIDIA H100. MBU disminuye a medida que aumenta el tamaño del lote. Sin embargo, a medida que escalamos las GPU, la disminución relativa en MBU es menos significativa. También vale la pena señalar que elegir hardware con un mayor ancho de banda de memoria puede aumentar el rendimiento con menos GPU. En el tamaño de lote 1, podemos lograr un MBU más alto del 60 % en 2xH100-80GB en comparación con el 55 % en GPU 4xA100-40GB (Figura 2).

Modos de paralelismo de tensores
Figura 3: MBU observado empíricamente para diferentes tamaños de lote y modos de paralelismo de tensores en GPU H100-80G. Solicitudes: secuencias de 512 tokens de entrada

Resultados de la evaluación comparativa

Latencia

Hemos medido el tiempo hasta el primer token (TTFT) y el tiempo por token de salida (TPOT) en diferentes grados de paralelismo de tensores para los modelos MPT-7B y Llama2-70B. A medida que los prompts de entrada se alargan, el tiempo para generar el primer token comienza a consumir una parte sustancial de la latencia total. El paralelismo de tensores en varias GPU ayuda a reducir esta latencia.

A diferencia del entrenamiento del modelo, el escalado a más GPU ofrece importantes rendimientos decrecientes para la latencia de inferencia. Por ejemplo, para Llama2-70B, pasar de 4x a 8x GPU solo disminuye la latencia en 0,7x en tamaños de lote pequeños. Una razón para esto es que un mayor paralelismo tiene un MBU más bajo (como se discutió anteriormente). Otra razón es que el paralelismo de tensores introduce una sobrecarga de comunicación en un nodo de GPU.

  Tiempo hasta el primer token (ms)
Modelo 1xA100-40GB 2xA100-40GB 4xA100-40GB 8xA100-40GB
MPT-7B 46 (1x) 34 (0,73x) 26 (0,56x) -
Llama2-70B No encaja 154 (1x) 114 (0,74x)

Tabla 1: Tiempo hasta el primer token dado que las solicitudes de entrada tienen una longitud de 512 tokens con un tamaño de lote de 1. Los modelos más grandes como Llama2 70B necesitan al menos GPU 4xA100-40B para caber en la memoria

En tamaños de lote más grandes, un mayor paralelismo de tensores conduce a una disminución relativa más significativa en la latencia de los tokens. La figura 4 muestra cómo varía el tiempo por token de salida para MPT-7B. En el tamaño de lote 1, pasar de 2x a 4x solo reduce la latencia de los tokens en ~12 %. En el tamaño de lote 16, la latencia con 4x es un 33 % más baja que con 2x. Esto va en línea con nuestra observación anterior de que la disminución relativa en MBU es menor en grados más altos de paralelismo de tensores para el tamaño de lote 16 en comparación con el tamaño de lote 1.

Aumento del número de GPU
Figura 4: Tiempo por token de salida por usuario a medida que escalamos MPT-7B en GPU A100-40GB. La latencia no se escala linealmente con el aumento del número de GPU. Solicitudes: secuencias de 128 tokens de entrada y 64 tokens de salida

La figura 5 muestra resultados similares para Llama2-70B, excepto que la mejora relativa entre 4x y 8x es menos pronunciada. También comparamos el escalado de GPU en dos hardware diferentes. Debido a que H100-80GB tiene un ancho de banda de memoria de GPU 2,15 veces mayor en comparación con A100-40GB, podemos ver que la latencia es un 36 % más baja en el tamaño de lote 1 y un 52 % más baja en el tamaño de lote 16 para los sistemas 4x.

Múltiples GPU
Figura 5: Tiempo por token de salida por usuario a medida que escalamos Llama-v2-70B en múltiples GPU (solicitudes de entrada: longitud de token de 512). Tenga en cuenta que faltan los números de GPU de 1x40GB, 2x40GB y 1x80GB aquí porque Llama-v2-70B (en float16) no cabe en esos sistemas.

Rendimiento

Podemos intercambiar el rendimiento y el tiempo por token agrupando las solicitudes. Agrupar las consultas durante la evaluación de la GPU aumenta el rendimiento en comparación con el procesamiento de las consultas secuencialmente, pero cada consulta tardará más en completarse (ignorando los efectos de la cola).

Existen algunas técnicas comunes para agrupar las solicitudes de inferencia:

  • Agrupación estática: el cliente empaqueta múltiples prompts en solicitudes y se devuelve una respuesta después de que se hayan completado todas las secuencias en el lote. Nuestros servidores de inferencia admiten esto, pero no lo requieren.
  • Agrupación dinámica: los prompts se agrupan sobre la marcha dentro del servidor. Por lo general, este método funciona peor que la agrupación estática, pero puede acercarse a lo óptimo si las respuestas son cortas o de longitud uniforme. No funciona bien cuando las solicitudes tienen diferentes parámetros.
  • Agrupación continua: la idea de agrupar las solicitudes a medida que llegan se introdujo en este excelente documento y actualmente es el método SOTA. En lugar de esperar a que terminen todas las secuencias en un lote, agrupa las secuencias en el nivel de iteración. Puede lograr un rendimiento de 10 a 20 veces mejor que la agrupación dinámica.

Servicio de LLM
Figura 6: Diferentes tipos de agrupación con el servicio de LLM. La agrupación es una forma eficaz de mejorar la eficiencia de la inferencia.

La agrupación continua suele ser el mejor enfoque para los servicios compartidos, pero hay situaciones en las que los otros dos podrían ser mejores. En entornos de QPS bajos, la agrupación dinámica puede superar a la agrupación continua. A veces es más fácil implementar optimizaciones de GPU de bajo nivel en un marco de agrupación más simple. Para las cargas de trabajo de inferencia por lotes sin conexión, la agrupación estática puede evitar una sobrecarga significativa y lograr un mejor rendimiento.

Tamaño del lote

Qué tan bien funciona la agrupación depende en gran medida del flujo de solicitudes. Pero podemos obtener un límite superior en su rendimiento mediante la evaluación comparativa de la agrupación estática con solicitudes uniformes.

  Tamaño del lote
Hardware 1 4 8 16 32 64 128
1 x A10 0,4 (1x) 1,4 (3,5x) 2,3 (6x) 3,5 (9x) Error OOM (sin memoria)
2 x A10 0,8 2,5 4,0 7,0 8,0  
1 x A100 0,9 (1x) 3,2 (3,5x) 5,3 (6x) 8,0 (9x) 10,5 (12x) 12,5 (14x)  
2 x A100 1,3 3,0 5,5 9,5 14,5 17,0 22,0
4 x A100 1,7 6,2 11,5 18,0 25,0 33,0 36,5

Tabla 2: Rendimiento máximo de MPT-7B (req/seg) con agrupación estática y un backend basado en FasterTransformers. Solicitudes: 512 tokens de entrada y 64 tokens de salida. Para entradas más grandes, el límite de OOM estará en tamaños de lote más pequeños.

Compensación de latencia

La latencia de la solicitud aumenta con el tamaño del lote. Con una GPU NVIDIA A100, por ejemplo, si maximizamos el rendimiento con un tamaño de lote de 64, la latencia aumenta 4 veces mientras que el rendimiento aumenta 14 veces. Los servicios de inferencia compartidos suelen elegir un tamaño de lote equilibrado. Los usuarios que alojan sus propios modelos deben decidir la compensación adecuada de latencia/rendimiento para sus aplicaciones. En algunas aplicaciones, como los chatbots, la baja latencia para respuestas rápidas es la máxima prioridad. En otras aplicaciones, como el procesamiento por lotes de archivos PDF no estructurados, es posible que deseemos sacrificar la latencia para procesar un documento individual para procesarlos todos rápidamente en paralelo.

La figura 7 muestra la curva de rendimiento frente a latencia para el modelo 7B. Cada línea en esta curva se obtiene aumentando el tamaño del lote de 1 a 256. Esto es útil para determinar qué tan grande podemos hacer el tamaño del lote, sujeto a diferentes restricciones de latencia. Recordando nuestro gráfico de línea de techo anterior, encontramos que estas mediciones son consistentes con lo que esperaríamos. Después de un cierto tamaño de lote, es decir, cuando cruzamos al régimen limitado por el cálculo, cada duplicación del tamaño del lote simplemente aumenta la latencia sin aumentar el rendimiento.

Curva de latencia
Figura 7: Curva de latencia de rendimiento para el modelo MPT-7B. Esto permite a los usuarios elegir una configuración de hardware que cumpla con sus requisitos de rendimiento bajo una restricción de latencia.

Al usar el paralelismo, es importante comprender los detalles del hardware de bajo nivel. Por ejemplo