La optimización de la inferencia es una parte crítica de las aplicaciones de IA generativa desplegadas en producción. Utilizar LLMs de forma eficiente a escala es un reto y en los últimos años se han desarrollado muchas técnicas para hacer la inferencia más rápida y barata. Repasemos estas técnicas en este artículo.
Los grandes modelos lingüísticos (LLM) se basan en la arquitectura de transformadores inventada en 2017 por Vaswani et al. La arquitectura de transformadores logra una precisión superior, un aprendizaje de pocos pasos y habilidades casi humanas en diversas tareas lingüísticas. Sin embargo, estos modelos básicos, que a menudo comprenden de decenas a cientos de miles de millones de parámetros, son costosos de entrenar y consumen muchos recursos durante la inferencia. Los costes de inferencia aumentan con contextos de entrada largos, que exigen una gran capacidad de procesamiento debido al gran volumen de datos de entrada. Esto hace que la inferencia eficiente sea un reto crítico, sobre todo en la gestión de la memoria y los recursos informáticos.

Más concretamente, los LLM más conocidos son los LLM de sólo descodificador, como GPT-3, GPT-4, LLaMA, Mistral, DeepSeek, etc. Estos modelos están preentrenados en una tarea de modelado causal y funcionan como predictores de la siguiente palabra. Procesan una secuencia de tokens como entrada y producen tokens siguientes de forma autorregresiva hasta que se alcanza una condición de parada.
La inferencia LLM en modelos sólo decodificadores implica dos fases clave: la fase de prellenado y la fase de decodificación. En la primera, el modelo procesa los tokens de entrada para calcular los estados intermedios (claves y valores) y generar el primer token nuevo. Esta fase, similar a una operación matriz-matriz, está altamente paralelizada y utiliza eficientemente las capacidades de la GPU. Por el contrario, la fase de descodificación genera los tokens, uno a uno, basándose en los estados de los tokens anteriores. Esta operación matriz-vector está limitada por la memoria, ya que la transferencia de datos a la GPU, en lugar de la velocidad de cálculo, es la que dicta la latencia, lo que provoca una infrautilización de la capacidad de cálculo de la GPU.
La optimización de la fase de descodificación es un punto central para abordar los retos de la inferencia. Las soluciones incluyen el desarrollo de mecanismos de atención eficientes y una mejor gestión de claves y valores para reducir los cuellos de botella de la memoria. El artículo destaca enfoques prácticos para mejorar el rendimiento de la inferencia, suponiendo que los lectores tengan un conocimiento básico de la arquitectura del transformador y de los mecanismos de atención. Estas optimizaciones son cruciales para mejorar el rendimiento y reducir la latencia en los despliegues LLM del mundo real.
Otra complicación surge del uso de diferentes tokenizadores en los LLM, lo que afecta a la comparabilidad de los tokens. Los tokens, que equivalen aproximadamente a cuatro caracteres ingleses, varían en su representación dependiendo del tokenizador, lo que hace que las comparaciones directas del rendimiento de la inferencia (por ejemplo, tokens por segundo) sean engañosas. Esta variabilidad subraya la necesidad de disponer de métricas de evaluación estandarizadas para evaluar y comparar con precisión el rendimiento de los LLM durante la inferencia.
El procesamiento por lotes es una estrategia clave para mejorar la utilización de la GPU y el rendimiento de los modelos lingüísticos de gran tamaño (LLM). Al procesar varias solicitudes simultáneamente utilizando el mismo modelo, el procesamiento por lotes distribuye el coste de memoria de las ponderaciones del modelo entre las solicitudes, lo que permite que los lotes más grandes aprovechen más potencia de cálculo de la GPU. Sin embargo, el tamaño de los lotes tiene un límite, ya que los lotes excesivamente grandes pueden provocar un desbordamiento de la memoria debido a las demandas de memoria de los LLM, especialmente relacionadas con el almacenamiento en caché de claves y valores (KV) (más información sobre este tema más adelante).

El procesamiento por lotes tradicional o estático tiene limitaciones porque las solicitudes dentro de un lote a menudo generan diferentes números de tokens de finalización, lo que lleva a tiempos de ejecución variados. Esto hace que todas las peticiones esperen a que se complete la más lenta, lo que puede ser problemático cuando las longitudes de generación varían significativamente. Para solucionar este problema, se han desarrollado técnicas avanzadas como la dosificación en vuelo para optimizar el rendimiento.
La dosificación en vuelo, también conocida como dosificación continua, aborda los retos que plantea la naturaleza dinámica de las cargas de trabajo de LLM, que pueden ir desde simples respuestas de chatbot a complejos resúmenes de documentos o generación de código. Estas tareas producen resultados de tamaños muy diferentes, lo que dificulta el procesamiento por lotes y la ejecución eficaz de solicitudes en paralelo. A diferencia de la dosificación estática, la dosificación en vuelo permite al servidor desalojar inmediatamente del lote las secuencias completadas y empezar a procesar nuevas peticiones mientras otras siguen en curso. Este método aumenta significativamente la utilización de la GPU al adaptarse a los distintos tiempos de ejecución de las peticiones en situaciones reales.
La paralelización de modelos es una estrategia fundamental para gestionar las demandas de memoria y cálculo de modelos de aprendizaje automático a gran escala distribuyéndolos entre varias GPU. Este enfoque permite manejar modelos más grandes o lotes de entrada que superan la capacidad de memoria de un único dispositivo, lo que lo hace esencial tanto para el entrenamiento como para la inferencia cuando las restricciones de memoria son estrictas. Existen varias técnicas para dividir los pesos de los modelos, como el paralelismo de canalización, el paralelismo de tensor y el paralelismo de secuencia, cada una de las cuales aborda diferentes aspectos de la distribución de modelos. A diferencia del paralelismo de datos, que se centra en replicar los pesos del modelo en distintos dispositivos para procesar lotes de entrada más grandes durante el entrenamiento, estos métodos son más relevantes para reducir las huellas de memoria durante el entrenamiento y la inferencia.

El paralelismo de canalización divide el modelo verticalmente en trozos secuenciales, cada uno de los cuales contiene un subconjunto de capas asignadas a un dispositivo distinto. Por ejemplo, en una configuración de canalización de cuatro vías, cada dispositivo gestiona una cuarta parte de las capas del modelo, pasando las salidas al siguiente dispositivo en secuencia. Aunque esto reduce significativamente los requisitos de memoria por dispositivo, introduce ineficiencias conocidas como "burbujas de canalización", en las que los dispositivos pueden estar inactivos mientras esperan las salidas de las capas anteriores. El microbatching, que divide los lotes de entrada en sublotes más pequeños para su procesamiento secuencial, puede reducir estas burbujas, pero no eliminarlas por completo, ya que los tiempos de inactividad persisten durante los pases hacia delante y hacia atrás.
El paralelismo tensorial, en cambio, divide horizontalmente las capas individuales en bloques computacionales más pequeños que pueden ejecutarse de forma independiente en distintos dispositivos. Esto resulta especialmente eficaz para componentes de transformadores como los bloques de atención y los perceptrones multicapa (MLP), en los que, por ejemplo, se pueden asignar distintos cabezales de atención a distintos dispositivos para el cálculo en paralelo. Sin embargo, el paralelismo tensorial es menos eficaz para operaciones como LayerNorm y Dropout, que no pueden dividirse fácilmente y deben replicarse entre dispositivos, lo que conlleva un uso redundante de memoria para almacenar activaciones. Esta limitación pone de manifiesto la necesidad de enfoques complementarios para optimizar la eficiencia de la memoria.
El paralelismo de secuencias aborda las ineficiencias de memoria de operaciones como LayerNorm y Dropout particionándolas a lo largo de la dimensión de la secuencia de entrada, aprovechando su independencia entre los elementos de la secuencia. Este método reduce la huella de memoria de las activaciones redundantes, lo que lo convierte en un valioso complemento del paralelismo tensorial. Estas técnicas de paralelización no se excluyen mutuamente y pueden combinarse para optimizar aún más los modelos de lenguaje de gran tamaño (LLM). Además, las estrategias de optimización específicas para el módulo de atención pueden mejorar la escalabilidad y reducir las demandas de memoria por GPU, permitiendo un entrenamiento y una inferencia más eficientes para modelos de gran tamaño.
El artículo de 2017 *Attention Is All You Need* de Vaswani et al. presentó el modelo Transformer, cuya piedra angular es la autoatención. La autoatención permite al modelo evaluar la relevancia de las diferentes palabras de una frase en relación con las demás, lo que mejora la comprensión contextual para tareas como el procesamiento del lenguaje natural. El artículo formaliza la autoatención, sobre todo a través del mecanismo de atención escalada punto-producto (SDPA), que asigna pares de consulta y clave-valor a una salida, lo que lo convierte en un componente fundamental de las redes neuronales modernas. He aquí algunas de las técnicas más importantes para optimizar los cálculos de atención:

La atención multicabezal (MHA) se basa en la SDPA para ejecutar múltiples operaciones de atención en paralelo, cada una con proyecciones distintas de las matrices de consulta, clave y valor. Estas operaciones paralelas, o "cabezas", se centran en diferentes subespacios de representación, enriqueciendo la comprensión de la entrada por parte del modelo. Los resultados de estas cabezas se concatenan y se proyectan linealmente, manteniendo una eficiencia computacional comparable a la atención de una sola cabeza al reducir la dimensionalidad de cada cabeza (por ejemplo, dividiendo la dimensión del modelo por el número de cabezas, como 8).
La atención a múltiples consultas (MQA) optimiza MHA para la inferencia compartiendo las proyecciones de claves y valores entre varios cabezales de atención, al tiempo que se mantienen las proyecciones de múltiples consultas. Esto reduce las demandas de ancho de banda de la memoria y el tamaño de la caché de clave-valor (KV), lo que permite tamaños de lote más grandes y una mejor utilización del cálculo. Sin embargo, MQA puede reducir ligeramente la precisión, y los modelos que lo aprovechan requieren entrenamiento o ajuste fino con MQA activado para mantener el rendimiento.
La atención a consultas agrupadas (GQA) equilibra la MHA y la MQA agrupando las cabezas de consulta y compartiendo las proyecciones clave-valor dentro de cada grupo, consiguiendo una calidad cercana a la MHA con una eficiencia computacional más cercana a la MQA. Modelos como Llama 2 70B utilizan GQA, y los entrenados con MHA pueden adaptarse a GQA con un entrenamiento adicional mínimo. Tanto MQA como GQA reducen la demanda de memoria caché KV, aunque siguen siendo necesarias nuevas optimizaciones en la gestión de la caché.
FlashAttention mejora los mecanismos de atención reordenando los cálculos para aprovechar mejor las jerarquías de memoria de la GPU. A diferencia del procesamiento tradicional capa por capa, FlashAttention fusiona las operaciones y utiliza "mosaicos" para calcular pequeñas porciones de la matriz de salida a la vez, lo que minimiza las operaciones de lectura/escritura en memoria. Este algoritmo de atención exacta y consciente de la E/S se integra a la perfección en los modelos existentes sin necesidad de modificaciones, ofreciendo importantes aumentos de velocidad gracias a la optimización del movimiento de datos.
El almacenamiento en caché de KV es una técnica de optimización crítica que se utiliza durante la fase de descodificación de los grandes modelos lingüísticos (LLM) para mejorar la eficiencia de los cálculos de autoatención. En esta fase, cada token generado depende de los tensores de clave (K) y valor (V) de todos los tokens anteriores, incluidos los calculados durante la fase de precompletado y los pasos de descodificación posteriores. En lugar de volver a calcular estos tensores para cada testigo en cada paso temporal, la caché KV los almacena en la memoria de la GPU, añadiendo nuevos tensores a la caché a medida que se calculan. Normalmente, se mantiene una caché KV distinta para cada capa del modelo, lo que reduce significativamente los cálculos redundantes y acelera el proceso de descodificación.

Los requisitos de memoria de los LLM en las GPU dependen principalmente de dos componentes: los pesos del modelo y la caché de KV. Los pesos del modelo, que consisten en los parámetros del modelo, ocupan una cantidad considerable de memoria; por ejemplo, un modelo de 7.000 millones de parámetros como Llama 2 7B en precisión de 16 bits requiere aproximadamente 14 GB. La caché KV, por su parte, almacena los tensores de autoatención para evitar el recálculo, y su tamaño viene determinado por factores como el número de capas, las cabezas de atención, las dimensiones de las cabezas y la precisión. Para cada token, el tamaño de la caché se calcula como 2 * num_layers * (num_heads * dim_head) * precision_in_bytes, donde el factor de 2 tiene en cuenta las matrices K y V. Para un lote de entradas, el tamaño total de la caché KV se escala con el tamaño del lote y la longitud de la secuencia, pudiendo alcanzar tamaños significativos, como ~2 GB para un modelo Llama 2 7B con una longitud de secuencia de 4.096 y un tamaño de lote de 1.
La gestión eficaz de la caché de KV plantea dificultades debido a su crecimiento lineal con el tamaño del lote y la longitud de la secuencia, lo que puede limitar el rendimiento y complicar la gestión de entradas de contexto largo. Una ineficiencia común surge de la sobredotación estática, en la que la memoria se reserva para la longitud máxima de secuencia admitida (por ejemplo, 2.048 tokens), independientemente del tamaño real de la entrada. Esto conduce a un importante desperdicio o fragmentación de la memoria, ya que gran parte del espacio reservado a menudo permanece sin utilizar durante el tiempo de vida de la solicitud, ocupando valiosos recursos de memoria de la GPU.
Para hacer frente a estas ineficiencias, el algoritmo PagedAttention introduce un novedoso enfoque inspirado en la paginación de los sistemas operativos. Divide la caché KV en bloques de tamaño fijo, cada uno de los cuales representa un número determinado de fichas, que pueden almacenarse de forma no contigua en la memoria. Una tabla de bloques rastrea estos bloques y los recupera cuando es necesario durante los cálculos de atención. A medida que se generan nuevas fichas, se asignan bloques adicionales de forma dinámica. Este método minimiza el desperdicio de memoria al eliminar la necesidad de asignaciones contiguas y de sobreaprovisionamiento, permitiendo tamaños de lote mayores y mejorando el rendimiento, lo que lo convierte en un avance significativo en la gestión de la memoria caché KV para los LLM.
En esta sección se analizan diversas técnicas de optimización de grandes modelos lingüísticos (LLM) para reducir su consumo de memoria y mejorar su rendimiento en las GPU. Entre los métodos clave se incluyen la cuantización, la dispersión y la destilación, cada uno de los cuales se centra en diferentes aspectos de la eficiencia del modelo. Estas técnicas modifican las ponderaciones de los modelos, aprovechan la aceleración del hardware de la GPU y transfieren conocimientos a modelos más pequeños, lo que permite ejecutar modelos más grandes en hardware limitado manteniendo el rendimiento. Estos métodos pueden degradar la precisión del modelo, por lo que deben utilizarse con precaución.
La cuantización reduce la precisión de los pesos y las activaciones de un modelo, normalmente de 32 o 16 bits a 8 o menos bits, lo que permite a los modelos ocupar menos memoria y transferir datos de forma más eficiente. Mientras que cuantificar los pesos es sencillo debido a su naturaleza fija tras el entrenamiento, cuantificar las activaciones es más complejo debido a los valores atípicos que amplían su rango dinámico. Técnicas como LLM.int8() solucionan este problema aplicando selectivamente una mayor precisión a determinadas activaciones o reutilizando el rango dinámico de los pesos cuantificados para las activaciones, aunque las GPU pueden requerir la conversión de los pesos a una mayor precisión para las operaciones.
La dispersión consiste en eliminar los valores del modelo cercanos a cero, creando matrices dispersas que requieren menos memoria. Las GPU admiten la sparsity estructurada, como la representación de dos de cada cuatro valores como ceros, lo que acelera los cálculos. Combinar la dispersión con la cuantización puede aumentar aún más la velocidad de ejecución. La investigación continúa explorando representaciones dispersas óptimas para los LLM, lo que indica una vía prometedora para mejorar la velocidad de inferencia.
La destilación transfiere conocimientos de un modelo "maestro" más grande a un modelo "alumno" más pequeño, comprimiendo el tamaño y conservando el rendimiento. Por ejemplo, DistilBERT consigue una reducción de tamaño del 40% y un aumento de velocidad del 60% en comparación con BERT, conservando el 97% de sus capacidades. La destilación puede implicar la imitación de los resultados del profesor o el uso de datos generados por el profesor para el entrenamiento, con métodos como "¡Destilar paso a paso!" que incorporan razonamientos para un aprendizaje eficiente. Sin embargo, las licencias restrictivas de muchos LLM avanzados limitan la disponibilidad de modelos de profesor adecuados para la destilación.
La inferencia especulativa, también conocida como muestreo especulativo o generación asistida, es un método para paralelizar la ejecución de grandes modelos autorregresivos del lenguaje (LLM), como los modelos de estilo GPT, que suelen generar texto token a token. En la ejecución estándar, cada token depende del contexto de todos los tokens anteriores, lo que hace imposible la generación paralela, ya que el enésimo token debe generarse antes que el (n+1)º. La inferencia especulativa soluciona este problema utilizando un modelo de borrador "más barato" para predecir múltiples tokens futuros simultáneamente, que luego son verificados o rechazados en paralelo por el modelo principal, lo que permite una generación de texto más rápida.
El proceso consiste en generar un borrador de continuación de varios tokens utilizando un método menos intensivo en recursos, seguido de una verificación paralela por parte del modelo principal utilizando el borrador como contexto especulativo. Si el modelo de verificación coincide con los tokens del borrador, se aceptan; en caso contrario, se descartan los tokens no coincidentes y los posteriores, y el proceso se repite con un nuevo borrador. Los borradores de fichas pueden generarse mediante varios métodos, como el entrenamiento de varios modelos, el ajuste de varias cabezas en un modelo preentrenado para predecir futuras fichas, o el empleo de un modelo de borrador más pequeño junto a un modelo de verificación más grande y capaz, cada uno con sus propias ventajas y desventajas.
La inferencia desagregada es una técnica en la que las tareas de cálculo se reparten entre distintos equipos para optimizar el rendimiento, el coste y el uso de recursos. En concreto, separa las fases de prellenado y descodificación. Al desagregar estas fases, cada una puede asignarse al hardware más adecuado para sus demandas computacionales, lo que mejora la eficiencia y la escalabilidad.

El prellenado es un proceso de cálculo intensivo que requiere importantes multiplicaciones de matrices para procesar toda la información de entrada y producir cachés KV. Esta fase se beneficia de hardware de alto rendimiento como GPUs o TPUs, que destacan en cálculos paralelos. Dado que el prellenado es una tarea que se realiza una sola vez por solicitud de inferencia, puede descargarse en un nodo de cálculo centralizado y potente optimizado para este tipo de cargas de trabajo. Esta configuración permite un procesamiento más rápido de las solicitudes de gran tamaño y reduce la carga de los dispositivos menos capaces, por lo que es ideal para entornos basados en la nube o centros de datos en los que se dispone de hardware de alto rendimiento.
La descodificación, por el contrario, está limitada a la memoria e implica la generación iterativa de tokens, dependiendo en gran medida del acceso a las cachés KV. Requiere menos potencia de cálculo, pero necesita un acceso rápido a la memoria, lo que la hace adecuada para hardware menos potente y optimizado para la memoria, como CPU o dispositivos periféricos. Al trasladar la descodificación a un hardware independiente -posiblemente más cercano al usuario final, como servidores locales o dispositivos periféricos-, la inferencia desagregada reduce la latencia y las demandas de ancho de banda de la red. Esta separación permite un despliegue flexible, en el que el prellenado se ejecuta en servidores en la nube de gama alta y la descodificación tiene lugar en dispositivos locales o periféricos, lo que optimiza la asignación de recursos y permite un escalado eficiente para aplicaciones como chatbots en tiempo real o sistemas de IA interactivos.
Recientemente se han inventado muchas técnicas de optimización de la inferencia para mejorar el rendimiento de los LLM.
La implementación de estas técnicas requiere un profundo conocimiento de la arquitectura LLM y del hardware que se está utilizando, por lo que generalmente es más fácil utilizar un motor de inferencia existente que ya haya implementado estas técnicas como vLLM, TensorRT-LLM, LMDeploy, etc. De hecho, hemos implementado estas técnicas en nuestro propio motor de inferencia en NLP Cloud y hemos escrito una entrada en el blog sobre motores de inferencia si desea implementar sus propios modelos: puede leerlo aquí.
Si no puede o no quiere desplegar sus propios LLM, puede utilizar NLP Cloud y aprovechar los rápidos modelos generativos de IA a escala en producción. ¡Pruebe ahora la inferencia rápida en NLP Cloud!
Si tiene alguna pregunta sobre motores de inferencia en general, no dude en consultarnos, ¡siempre es un placer asesorarle!
Julien
Director Técnico de NLP Cloud