Inteligencia artificial
Optimización de la implementación de LLM: vLLM PagedAttention y el futuro de la entrega eficiente de IA

Los modelos de lenguaje grande (LLM) que se implementan en aplicaciones del mundo real presentan desafíos únicos, particularmente en términos de recursos computacionales, latencia y rentabilidad. En esta guía integral, exploraremos el panorama de la entrega de LLM, con un enfoque particular en vLLM (modelo de lenguaje vectorial), una solución que está cambiando la forma en que implementamos y interactuamos con estos poderosos modelos.
Los desafíos de la entrega de modelos de lenguaje grande
Antes de sumergirnos en soluciones específicas, examinemos los desafíos clave que hacen que la entrega de LLM sea una tarea compleja:
Recursos computacionales
Los LLM son notorios por sus enormes cantidades de parámetros, que van desde miles de millones hasta cientos de miles de millones. Por ejemplo, GPT-3 cuenta con 175.000 millones de parámetros, mientras que modelos más recientes como GPT-4 se estima que tienen aún más. Este tamaño enorme se traduce en requisitos computacionales significativos para la inferencia.
Ejemplo:
Considera un LLM modesto con 13.000 millones de parámetros, como LLaMA-13B. Incluso este modelo requiere:
– Aproximadamente 26 GB de memoria solo para almacenar los parámetros del modelo (asumiendo precisión de 16 bits)
– Memoria adicional para activaciones, mecanismos de atención y cálculos intermedios
– Una gran potencia de cálculo de GPU para la inferencia en tiempo real
Latencia
En muchas aplicaciones, como chatbots o generación de contenido en tiempo real, la baja latencia es crucial para una buena experiencia del usuario. Sin embargo, la complejidad de los LLM puede llevar a tiempos de procesamiento significativos, especialmente para secuencias más largas.
Ejemplo:
Imagina un chatbot de servicio al cliente impulsado por un LLM. Si cada respuesta tarda varios segundos en generarse, la conversación se sentirá poco natural y frustrante para los usuarios.
Costo
El hardware necesario para ejecutar LLM a gran escala puede ser extremadamente costoso. A menudo se necesitan GPUs o TPUs de alta gama, y el consumo de energía de estos sistemas es sustancial.
Ejemplo:
Ejecutar un clúster de GPUs NVIDIA A100 (a menudo utilizado para la inferencia de LLM) puede costar miles de dólares al día en tarifas de computación en la nube.
Enfoques tradicionales para la entrega de LLM
Antes de explorar soluciones más avanzadas, repasemos algunos enfoques tradicionales para la entrega de LLM:
Implementación simple con Hugging Face Transformers
La biblioteca Hugging Face Transformers proporciona una forma sencilla de implementar LLM, pero no está optimizada para la entrega de alta capacidad.
Código de ejemplo:
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
nombre_del_modelo = "meta-llama/Llama-2-13b-hf"
modelo = AutoModelForCausalLM.from_pretrained(nombre_del_modelo, device_map="auto")
tokenizador = AutoTokenizer.from_pretrained(nombre_del_modelo)
def generar_texto(prompt, max_length=100):
entradas = tokenizador(prompt, return_tensors="pt").to(modelo.device)
salidas = modelo.generate(**entradas, max_length=max_length)
return tokenizador.decode(salidas[0], skip_special_tokens=True)
print(generar_texto("El futuro de la IA es"))
Aunque este enfoque funciona, no es adecuado para aplicaciones de alto tráfico debido a su uso ineficiente de recursos y falta de optimizaciones para la entrega.
Uso de TorchServe o marcos similares
Marcos como TorchServe proporcionan capacidades de entrega más robustas, incluyendo equilibrio de carga y control de versiones de modelo. Sin embargo, todavía no abordan los desafíos específicos de la entrega de LLM, como la gestión eficiente de la memoria para modelos grandes.
Comprensión de la gestión de memoria en la entrega de LLM
La gestión eficiente de la memoria es fundamental para la entrega de modelos de lenguaje grande (LLM) debido a los recursos computacionales extensos necesarios. Las siguientes imágenes ilustran varios aspectos de la gestión de la memoria, que son integrales para optimizar el rendimiento de LLM.
Memoria segmentada vs. memoria paginada
Estas dos diagramas comparan las técnicas de gestión de memoria segmentada y paginada, comúnmente utilizadas en sistemas operativos (OS).
- Memoria segmentada: Esta técnica divide la memoria en diferentes segmentos, cada uno correspondiente a un programa o proceso diferente. Por ejemplo, en un contexto de entrega de LLM, diferentes segmentos podrían asignarse a varios componentes del modelo, como tokenización, incrustación y mecanismos de atención. Cada segmento puede crecer o disminuir de forma independiente, lo que proporciona flexibilidad pero puede llevar a la fragmentación si los segmentos no se gestionan adecuadamente.
- Memoria paginada: Aquí, la memoria se divide en páginas de tamaño fijo, que se asignan a la memoria física. Las páginas se pueden intercambiar según sea necesario, lo que permite un uso eficiente de los recursos de memoria. En la entrega de LLM, esto puede ser crucial para gestionar las grandes cantidades de memoria necesarias para almacenar los pesos del modelo y los cálculos intermedios.
Gestión de memoria en OS vs. vLLM
Esta imagen contrasta la gestión de memoria tradicional de OS con el enfoque de gestión de memoria utilizado en vLLM.
- Gestión de memoria de OS: En sistemas operativos tradicionales, los procesos (por ejemplo, Proceso A y Proceso B) se asignan páginas de memoria (Página 0, Página 1, etc.) en la memoria física. Esta asignación puede llevar a la fragmentación con el tiempo a medida que los procesos solicitan y liberan memoria.
- Gestión de memoria de vLLM: El marco vLLM utiliza una caché de clave-valor (KV) para gestionar la memoria de forma más eficiente. Las solicitudes (por ejemplo, Solicitud A y Solicitud B) se asignan bloques de la caché KV (Bloque KV 0, Bloque KV 1, etc.). Este enfoque ayuda a minimizar la fragmentación y optimiza el uso de la memoria, lo que permite una entrega de modelos más rápida y eficiente.
Mecanismo de atención en LLM
El mecanismo de atención es un componente fundamental de los modelos de transformadores, que se utilizan comúnmente para LLM. Este diagrama ilustra la fórmula de atención y sus componentes:
- Consulta (Q): Un nuevo token en el paso del decodificador o el último token que el modelo ha visto.
- Clave (K): Contexto anterior al que el modelo debe prestar atención.
- Valor (V): Suma ponderada sobre el contexto anterior.
La fórmula calcula las puntuaciones de atención tomando el producto escalar de la consulta con las claves, escalando por la raíz cuadrada de la dimensión de la clave, aplicando una función softmax y finalmente tomando el producto escalar con los valores. Este proceso permite al modelo centrarse en las partes relevantes de la secuencia de entrada al generar cada token.
Comparación de rendimiento de entrega
Esta imagen presenta una comparación del rendimiento de entrega entre diferentes marcos (HF, TGI y vLLM) utilizando modelos LLaMA en diferentes configuraciones de hardware.
- LLaMA-13B, A100-40GB: vLLM logra un rendimiento 14 veces mayor que Hugging Face Transformers (HF) y 2,2 veces mayor que Hugging Face Text Generation Inference (TGI).
- LLaMA-7B, A10G: Se observan tendencias similares, con vLLM superando significativamente a HF y TGI.
vLLM: Una nueva arquitectura de entrega de LLM
vLLM, desarrollado por investigadores de la Universidad de California en Berkeley, representa un avance significativo en la tecnología de entrega de LLM. Exploraremos sus características y innovaciones clave:
PagedAttention
En el corazón de vLLM se encuentra PagedAttention, un algoritmo de atención novedoso inspirado en la gestión de memoria virtual en sistemas operativos. Aquí está cómo funciona:
– Particionamiento de la caché de clave-valor (KV): En lugar de almacenar toda la caché KV de forma contigua en la memoria, PagedAttention la divide en bloques de tamaño fijo.
– Almacenamiento no contiguo: Estos bloques se pueden almacenar de forma no contigua en la memoria, lo que permite una gestión de memoria más flexible.
– Asignación bajo demanda: Los bloques se asignan solo cuando se necesitan, lo que reduce el desperdicio de memoria.
– Compartir eficiente: Las secuencias múltiples pueden compartir bloques, lo que permite optimizaciones para técnicas como muestreo paralelo y búsqueda de haz.
Ilustración:
“`
Caché KV tradicional:
[Token 1 KV][Token 2 KV][Token 3 KV]…[Token N KV]
(Asignación de memoria contigua)
Caché KV de PagedAttention:
[Bloque 1] -> Dirección física A
[Bloque 2] -> Dirección física C
[Bloque 3] -> Dirección física B
…
(Asignación de memoria no contigua)
“`
Este enfoque reduce significativamente la fragmentación de la memoria y permite un uso mucho más eficiente de la memoria de la GPU.
Lote continuo
vLLM implementa un lote continuo, que procesa las solicitudes de forma dinámica a medida que llegan, en lugar de esperar a formar lotes de tamaño fijo. Esto conduce a una latencia más baja y un rendimiento más alto.
Ejemplo:
Imagina un flujo de solicitudes entrantes:
“`
Tiempo 0ms: Solicitud A llega
Tiempo 10ms: Comienza el procesamiento de la Solicitud A
Tiempo 15ms: Solicitud B llega
Tiempo 20ms: Comienza el procesamiento de la Solicitud B (en paralelo con A)
Tiempo 25ms: Solicitud C llega
…
“`
Con el lote continuo, vLLM puede comenzar a procesar cada solicitud de inmediato, en lugar de esperar a agruparlas en lotes predefinidos.
Muestreo paralelo eficiente
Para aplicaciones que requieren múltiples muestras de salida por prompt (por ejemplo, asistentes de escritura creativa), las capacidades de intercambio de memoria de vLLM brillan. Puede generar múltiples salidas mientras reutiliza la caché KV para prefijos compartidos.
Código de ejemplo utilizando vLLM:
from vllm import LLM, SamplingParams
llm = LLM(model="meta-llama/Llama-2-13b-hf")
prompts = ["El futuro de la IA es"]
# Generar 3 muestras por prompt
sampling_params = SamplingParams(n=3, temperature=0.8, max_tokens=100)
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(f"Prompt: {output.prompt}")
for i, out in enumerate(output.outputs):
print(f"Muestra {i + 1}: {out.text}")
Este código genera de forma eficiente múltiples muestras para el prompt dado, aprovechando las optimizaciones de vLLM.
Benchmarking del rendimiento de vLLM
Para apreciar realmente el impacto de vLLM, veamos algunas comparaciones de rendimiento:
Comparación de rendimiento
Basado en la información proporcionada, vLLM supera significativamente a otras soluciones de entrega:
– Hasta 24 veces más rendimiento que Hugging Face Transformers
– 2,2 a 3,5 veces más rendimiento que Hugging Face Text Generation Inference (TGI)
Ilustración:
“`
Rendimiento (Tokens/segundo)
|
| ****
| ****
| ****
| **** ****
| **** **** ****
|————————
HF TGI vLLM
“`
Eficiencia de memoria
El PagedAttention de vLLM resulta en un uso de memoria casi óptimo:
– Solo alrededor del 4% de desperdicio de memoria, en comparación con el 60-80% en sistemas tradicionales
– Esta eficiencia permite servir modelos más grandes o manejar más solicitudes concurrentes con el mismo hardware
Comenzando con vLLM
Ahora que hemos explorado los beneficios de vLLM, veamos el proceso de configurarlo y utilizarlo en sus proyectos.
6.1 Instalación
Instalar vLLM es sencillo utilizando pip:
!pip install vllm
6.2 Uso básico para inferencia sin conexión
Aquí hay un ejemplo simple de uso de vLLM para la generación de texto sin conexión:
from vllm import LLM, SamplingParams
# Inicializar el modelo
llm = LLM(model="meta-llama/Llama-2-13b-hf")
# Preparar prompts
prompts = [
"Escreba un poema corto sobre la inteligencia artificial:",
"Explique la computación cuántica en términos sencillos:"
]
# Establecer parámetros de muestreo
sampling_params = SamplingParams(temperature=0.8, max_tokens=100)
# Generar respuestas
outputs = llm.generate(prompts, sampling_params)
# Imprimir los resultados
for output in outputs:
print(f"Prompt: {output.prompt}")
print(f"Texto generado: {output.outputs[0].text}\n")
Este script demuestra cómo cargar un modelo, establecer parámetros de muestreo y generar texto para múltiples prompts.
6.3 Configuración de un servidor vLLM
Para la entrega en línea, vLLM proporciona un servidor de API compatible con OpenAI. Aquí está cómo configurarlo:
1. Iniciar el servidor:
python -m vllm.entrypoints.openai.api_server --model meta-llama/Llama-2-13b-hf
2. Consultar el servidor utilizando curl:
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{"model": "meta-llama/Llama-2-13b-hf", "prompt": "El futuro de la IA es", "max_tokens": 100, "temperature": 0.7}'
Esta configuración permite servir su LLM con una interfaz compatible con la API de OpenAI, lo que facilita la integración en aplicaciones existentes.
Temas avanzados sobre vLLM
Si bien vLLM ofrece mejoras significativas en la entrega de LLM, hay consideraciones y temas avanzados adicionales para explorar:
7.1 Cuantificación de modelos
Para una entrega aún más eficiente, especialmente en hardware con memoria limitada, se pueden emplear técnicas de cuantificación. Aunque vLLM no admite actualmente la cuantificación, se puede utilizar en conjunto con modelos cuantificados:
from transformers import AutoModelForCausalLM, AutoTokenizer import torch # Cargar un modelo cuantificado nombre_del_modelo = "meta-llama/Llama-2-13b-hf" modelo = AutoModelForCausalLM.from_pretrained(nombre_del_modelo, device_map="auto", load_in_8bit=True) tokenizador = AutoTokenizer.from_pretrained(nombre_del_modelo) # Utilizar el modelo cuantificado con vLLM from vllm import LLM llm = LLM(model=model, tokenizer=tokenizador)
7.2 Inferencia distribuida
Para modelos extremadamente grandes o aplicaciones de alto tráfico, es posible que se requiera inferencia distribuida en varias GPUs o máquinas. Aunque vLLM no admite esto de forma nativa, se puede integrar en sistemas distribuidos utilizando marcos como Ray:
import ray
from vllm import LLM
@ray.remote(num_gpus=1)
class DistributedLLM:
def __init__(self, model_name):
self.llm = LLM(model=model_name)
def generate(self, prompt, params):
return self.llm.generate(prompt, params)
# Inicializar LLM distribuidos
llm1 = DistributedLLM.remote("meta-llama/Llama-2-13b-hf")
llm2 = DistributedLLM.remote("meta-llama/Llama-2-13b-hf")
# Utilizarlos en paralelo
result1 = llm1.generate.remote("Prompt 1", sampling_params)
result2 = llm2.generate.remote("Prompt 2", sampling_params)
# Recuperar resultados
print(ray.get([result1, result2]))
7.3 Monitoreo y observabilidad
Al servir LLM en producción, el monitoreo es crucial. Aunque vLLM no proporciona un monitoreo integrado, se puede integrar con herramientas como Prometheus y Grafana:
from prometheus_client import start_http_server, Summary
from vllm import LLM
# Definir métricas
TIEMPO_DE_SOLICITUD = Summary('request_processing_seconds', 'Tiempo dedicado a procesar la solicitud')
# Inicializar vLLM
llm = LLM(model="meta-llama/Llama-2-13b-hf")
# Exponer métricas
start_http_server(8000)
# Utilizar el modelo con monitoreo
@TIEMPO_DE_SOLICITUD.time()
def procesar_solicitud(prompt):
return llm.generate(prompt)
# Su bucle de servicio aquí
Esta configuración permite realizar un seguimiento de métricas como el tiempo de procesamiento de la solicitud, que se puede visualizar en paneles de Grafana.
Conclusión
La entrega eficiente de modelos de lenguaje grande es una tarea compleja pero crucial en la era de la IA. vLLM, con su innovador algoritmo PagedAttention y su implementación optimizada, representa un avance significativo en la entrega de LLM.
Al mejorar drásticamente el rendimiento, reducir el desperdicio de memoria y permitir opciones de entrega más flexibles, vLLM abre nuevas posibilidades para integrar potentes modelos de lenguaje en una amplia gama de aplicaciones. Ya sea que esté construyendo un chatbot, un sistema de generación de contenido o cualquier otra aplicación impulsada por NLP, comprender y aprovechar herramientas como vLLM será clave para el éxito.














