Modelos y plataformas de IA

Optimización de la memoria para inferencia y ajuste fino de modelos de lenguaje grande

mm
Añade Unite.AI a tus fuentes preferidas en Google

Los modelos de lenguaje grande (LLM) como GPT-4, Bloom y LLaMA han logrado capacidades notables al escalar hasta miles de millones de parámetros. Sin embargo, desplegar estos modelos masivos para inferencia o ajuste fino es un desafío debido a sus enormes requisitos de memoria. En este blog técnico, exploraremos técnicas para estimar y optimizar el consumo de memoria durante la inferencia y el ajuste fino de LLM en varios entornos de hardware.

Comprensión de los requisitos de memoria

La memoria necesaria para cargar un LLM se determina principalmente por la cantidad de parámetros y la precisión numérica utilizada para almacenar los parámetros. Una regla general es:

  • Cargar un modelo con X mil millones de parámetros requiere aproximadamente 4X GB de VRAM en precisión de 32 bits float
  • Cargar un modelo con X mil millones de parámetros requiere aproximadamente 2X GB de VRAM en precisión de 16 bits bfloat16/float16

Por ejemplo, cargar el modelo GPT-3 de 175 mil millones de parámetros requeriría aproximadamente 350 GB de VRAM en precisión bfloat16. Actualmente, las GPU más grandes disponibles comercialmente, como la NVIDIA A100 y H100, ofrecen solo 80 GB de VRAM, lo que hace necesarias técnicas de paralelismo de tensor y paralelismo de modelo.

Durante la inferencia, la huella de memoria está dominada por los parámetros del modelo y los tensores de activación temporales producidos. Una estimación de alto nivel para el uso máximo de memoria durante la inferencia es la suma de la memoria necesaria para cargar los parámetros del modelo y la memoria para las activaciones.

Cuantificación de la memoria de inferencia

Veamos los requisitos de memoria para la inferencia utilizando el modelo OctoCode, que tiene alrededor de 15 mil millones de parámetros en formato bfloat16 (~ 31 GB). Utilizaremos la biblioteca Transformers para cargar el modelo y generar texto:

from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
import torch

<p>model = AutoModelForCausalLM.from_pretrained(&quot;bigcode/octocoder&quot;,
torch_dtype=torch.bfloat16,
device_map=&quot;auto&quot;,
pad_token_id=0)
tokenizer = AutoTokenizer.from_pretrained(&quot;bigcode/octocoder&quot;)
pipe = pipeline(&quot;text-generation&quot;, model=model, tokenizer=tokenizer)</p>

<p>prompt = &quot;Pregunta: Por favor, escriba una función de Python para convertir bytes a gigabytes.\n\nRespuesta:&quot;
result = pipe(prompt, max_new_tokens=60)[0][&quot;generated_text&quot;][len(prompt):]</p>

<p>def bytes_to_gigabytes(bytes):
return bytes / 1024 / 1024 / 1024</p>

<p>bytes_to_gigabytes(torch.cuda.max_memory_allocated())

Salida:

29.0260648727417

El uso máximo de memoria de la GPU es de aproximadamente 29 GB, lo que coincide con nuestra estimación de 31 GB para cargar los parámetros del modelo en formato bfloat16.

Optimización de la memoria de inferencia con cuantización

Si bien bfloat16 es la precisión común utilizada para entrenar LLM, los investigadores han encontrado que cuantizar los pesos del modelo a tipos de datos de precisión más baja como enteros de 8 bits (int8) o enteros de 4 bits puede reducir significativamente el uso de memoria con una pérdida mínima de precisión para tareas de inferencia como la generación de texto.

Veamos los ahorros de memoria de la cuantización de 8 bits y 4 bits del modelo OctoCode:

&amp;lt;/div&amp;gt;
# Cuantización de 8 bits
model = AutoModelForCausalLM.from_pretrained(&quot;bigcode/octocoder&quot;, load_in_8bit=True,
pad_token_id=0)
pipe = pipeline(&quot;text-generation&quot;, model=model, tokenizer=tokenizer)
result = pipe(prompt, max_new_tokens=60)[0][&quot;generated_text&quot;][len(prompt):]
bytes_to_gigabytes(torch.cuda.max_memory_allocated())&lt;/pre&gt;
Salida:
15.219234466552734
# Cuantización de 4 bits
model = AutoModelForCausalLM.from_pretrained(&quot;bigcode/octocoder&quot;, load_in_4bit=True,
low_cpu_mem_usage=True, pad_token_id=0)
pipe = pipeline(&quot;text-generation&quot;, model=model, tokenizer=tokenizer)
result = pipe(prompt, max_new_tokens=60)[0][&quot;generated_text&quot;][len(prompt):]
bytes_to_gigabytes(torch.cuda.max_memory_allocated())

Salida:

9.543574333190918

Con la cuantización de 8 bits, el requisito de memoria disminuye de 31 GB a 15 GB, mientras que la cuantización de 4 bits lo reduce aún más a solo 9,5 GB. Esto permite ejecutar el modelo OctoCode de 15 mil millones de parámetros en GPUs de consumo como la RTX 3090 (24 GB de VRAM).

Sin embargo, tenga en cuenta que la cuantización más agresiva como la de 4 bits puede llevar a una degradación de la precisión en comparación con la precisión de 8 bits o bfloat16. Hay un equilibrio entre ahorro de memoria y precisión que los usuarios deben evaluar para su caso de uso.

La cuantización es una técnica poderosa que puede permitir el despliegue de LLM en entornos con recursos limitados como instancias en la nube, dispositivos de borde o incluso teléfonos móviles al reducir drásticamente la huella de memoria.

Estimación de la memoria para el ajuste fino

Mientras que la cuantización se utiliza principalmente para la inferencia eficiente, técnicas como el paralelismo de tensor y el paralelismo de modelo son cruciales para gestionar los requisitos de memoria durante el entrenamiento o ajuste fino de modelos de lenguaje grande.

El consumo máximo de memoria durante el ajuste fino es generalmente 3-4 veces mayor que el de la inferencia debido a los requisitos de memoria adicionales para:

  • Gradientes
  • Estados del optimizador
  • Activaciones de la pasada hacia adelante almacenadas para la retropropagación

Una estimación conservadora es que el ajuste fino de un LLM con X mil millones de parámetros requiere alrededor de 4 * (2X) = 8X GB de VRAM en precisión bfloat16.

Por ejemplo, el ajuste fino del modelo LLaMA de 7 mil millones de parámetros requeriría aproximadamente 7 * 8 = 56 GB de VRAM por GPU en precisión bfloat16. Esto supera la capacidad de memoria de las GPUs actuales, lo que hace necesarias técnicas de ajuste fino distribuido.

Técnicas de ajuste fino distribuido

Se han propuesto varios métodos de ajuste fino distribuido para superar las limitaciones de memoria de la GPU para modelos grandes:

  1. Paralelismo de datos: El enfoque clásico de paralelismo de datos replica el modelo completo en varias GPUs mientras divide y distribuye los lotes de datos de entrenamiento. Esto reduce el tiempo de entrenamiento linealmente con la cantidad de GPUs, pero no reduce el requisito de memoria máximo en cada GPU.
  2. ZeRO Stage 3: Una forma avanzada de paralelismo de datos que divide los parámetros del modelo, los gradientes y los estados del optimizador en las GPUs. Reduce la memoria en comparación con el paralelismo de datos clásico al mantener solo los datos particionados necesarios en cada GPU durante las diferentes fases del entrenamiento.
  3. Paralelismo de tensor: En lugar de replicar el modelo, el paralelismo de tensor divide los parámetros del modelo en filas o columnas y los distribuye en las GPUs. Cada GPU opera en un conjunto particionado de parámetros, gradientes y estados del optimizador, lo que lleva a un ahorro de memoria sustancial.
  4. Paralelismo de tubería: Esta técnica divide las capas del modelo en diferentes GPUs/trabajadores, con cada dispositivo ejecutando un subconjunto de las capas. Las activaciones se pasan entre los trabajadores, lo que reduce la memoria máxima pero aumenta la sobrecarga de comunicación.

Estimar el uso de memoria para estos métodos distribuidos es no trivial, ya que la distribución de parámetros, gradientes, activaciones y estados del optimizador varía entre técnicas. Además, diferentes componentes como el cuerpo del transformador y la cabeza de modelado de lenguaje pueden exhibir diferentes comportamientos de asignación de memoria.

La solución LLMem

Los investigadores han propuesto recientemente LLMem, una solución que estima con precisión el consumo de memoria de la GPU cuando se aplican métodos de ajuste fino distribuido a LLM en varias GPUs.

Estimación del uso de memoria de la GPU para el ajuste fino de LLM preentrenados

Estimación del uso de memoria de la GPU para el ajuste fino de LLM preentrenados

LLMem considera factores como la recombinación de parámetros antes del cálculo (ZeRO Stage 3), la recopilación de salidas en la pasada hacia atrás (paralelismo de tensor) y las diferentes estrategias de asignación de memoria para el cuerpo del transformador y la cabeza de modelado de lenguaje.

Los resultados experimentales muestran que LLMem puede estimar el uso máximo de memoria de la GPU para el ajuste fino de LLM en una sola GPU con tasas de error de hasta 1,6%, superando la tasa de error promedio de 42,6% de DNNMem. Cuando se aplican métodos de ajuste fino distribuido a LLM con más de mil millones de parámetros en varias GPUs, LLMem logra una tasa de error promedio de 3,0%.

Al estimar con precisión los requisitos de memoria de antemano, LLMem puede ayudar a los usuarios a seleccionar la configuración de ajuste fino más eficiente que evite problemas de memoria agotada mientras minimiza el tiempo de entrenamiento.

Técnicas emergentes

Mientras que la cuantización, el paralelismo de tensor y el paralelismo de modelo son técnicas establecidas, los investigadores siguen explorando métodos novedosos para impulsar la eficiencia en el entrenamiento y despliegue de LLM.

  1. LoRA y QLoRA: Estas técnicas implican entrenar un módulo de adaptador residual más pequeño para actualizar el LLM preentrenado con nuevos conocimientos en lugar de ajustar directamente la gran cantidad de parámetros. Esto puede llevar a un ahorro de memoria sustancial mientras se mantiene la mayoría del rendimiento del modelo.
  2. FlashAttention: El mecanismo de autoatención es un cuello de botella de memoria y cálculo en los modelos de transformador. FlashAttention aproxima la atención estándar con complejidad lineal, reduciendo los requisitos de memoria de cuadrática a lineal en la longitud de la secuencia de entrada.
  3. Mixture-of-Experts: Este enfoque enruta condicionalmente cada muestra de datos de entrada a un modelo de experto especializado en lugar de procesarla a través de todo el modelo. Esta espacialidad dinámica puede ahorrar memoria al activar solo un subconjunto de expertos para cada muestra.
  4. Cirugía de modelo revertida: Los investigadores han explorado la compresión de modelos quirúrgicos mediante la eliminación iterativa de componentes menos importantes como cabezas de atención para intercambiar memoria/velocidad por precisión.
  5. Descarga: Finalmente, las técnicas que descargan parámetros, estados del optimizador o activaciones en la RAM de la CPU o en disco pueden complementar la memoria limitada de la GPU para modelos grandes.

Estos métodos de vanguardia ilustran el ecosistema de investigación vibrante centrado en democratizar el entrenamiento y despliegue eficientes de LLM en diversos entornos de hardware.

Conclusión

Los requisitos de memoria de los modelos de lenguaje grande plantean desafíos significativos para su adopción generalizada en aplicaciones del mundo real. Al comprender las técnicas de estimación de memoria y aprovechar la cuantización, las estrategias de entrenamiento distribuido y las innovaciones emergentes, podemos optimizar los despliegues de LLM en dispositivos con recursos limitados.

Herramientas como LLMem allanan el camino hacia la estimación precisa de la memoria, lo que permite a los usuarios seleccionar la configuración de ajuste fino más adecuada. A medida que evoluciona el hardware y avanza la investigación, podemos anticipar un entrenamiento y una inferencia de LLM más eficientes, lo que impulsará el progreso en el procesamiento de lenguaje natural y la inteligencia artificial.

Encontrar el equilibrio adecuado entre la capacidad del modelo, la precisión y la utilización de recursos será crucial para desbloquear todo el potencial de los modelos de lenguaje grande en diversos dominios y casos de uso. Al adoptar técnicas de optimización de memoria, nos acercamos a un futuro en el que la inteligencia de lenguaje de vanguardia es accesible, escalable y sostenible.

He dedicado los últimos cinco años sumergiéndome en el fascinante mundo de Machine Learning y Deep Learning. Mi pasión y experiencia me han llevado a contribuir a más de 50 proyectos de ingeniería de software diversos, con un enfoque particular en AI/ML. Mi curiosidad en curso también me ha llevado hacia el Procesamiento de Lenguaje Natural, un campo que estoy ansioso por explorar más a fondo.