vLLM es un motor de inferencia para modelos de lenguaje diseñado para servir muchas peticiones simultáneas en GPU. Gracias a PagedAttention, que gestiona la caché KV en bloques como si fuera memoria paginada, y al batching continuo de peticiones, consigue un rendimiento muy superior al de servir el modelo con un bucle de transformers. En este tutorial instalarás vLLM en Ubuntu 24.04 con una GPU NVIDIA, servirás un modelo con su API compatible con OpenAI, usarás un modelo cuantizado con AWQ y lo dejarás en producción como servicio systemd con clave de API.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS de 64 bits (x86_64) y una GPU NVIDIA, por ejemplo un servidor con GPU de CubePath. Se recomienda una GPU de arquitectura Ampere o posterior (A10, L4, A100, H100, RTX 30xx/40xx o superior).
  • El driver de NVIDIA instalado y funcionando (nvidia-smi debe mostrar la GPU). Si no lo tienes, instálalo con sudo ubuntu-drivers install y reinicia.
  • Al menos 16 GB de RAM y 50 GB libres en disco para el entorno de Python y los modelos.
  • Un usuario no root con privilegios sudo.

Como referencia, esta es la memoria de GPU que necesitan algunos modelos. vLLM reserva además memoria para la caché KV, así que la GPU debe tener margen por encima del tamaño de los pesos:

ModeloPrecisiónPesos aprox.GPU recomendada
Qwen/Qwen2.5-1.5B-InstructBF163,5 GB8 GB o más
Qwen/Qwen2.5-7B-Instruct-AWQ4 bits (AWQ)5,5 GB12 GB o más
Qwen/Qwen2.5-7B-InstructBF1615 GB24 GB o más

Comprueba la GPU y la memoria disponible:

nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv
name, driver_version, memory.total [MiB]
NVIDIA L4, 570.172.08, 23034 MiB

Paso 1: Instalar las dependencias del sistema

vLLM se distribuye como paquete de Python e incluye PyTorch con sus propias librerías CUDA, por lo que no hace falta instalar el CUDA Toolkit. Sí necesitas un compilador y las cabeceras de Python, porque Triton compila pequeños módulos la primera vez que arranca el motor:

sudo apt update
sudo apt install python3-venv python3-dev build-essential

Paso 2: Crear un usuario de servicio e instalar vLLM

Ejecutar vLLM con un usuario sin privilegios limita el impacto de cualquier fallo, y su directorio personal alojará la caché de modelos de Hugging Face. Crea el usuario vllm con su directorio en /var/lib/vllm:

sudo useradd --system --home-dir /var/lib/vllm --create-home --shell /usr/sbin/nologin vllm

Crea un entorno virtual en /opt/vllm e instala vLLM. La descarga ocupa varios GB, porque incluye PyTorch y las librerías CUDA:

sudo python3 -m venv /opt/vllm/venv
sudo /opt/vllm/venv/bin/pip install --upgrade pip
sudo /opt/vllm/venv/bin/pip install vllm

Comprueba la instalación y que PyTorch ve la GPU:

/opt/vllm/venv/bin/vllm --version
/opt/vllm/venv/bin/python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
0.11.0
True NVIDIA L4

Si la segunda línea muestra False, el driver de NVIDIA es demasiado antiguo para la versión de CUDA con la que se compiló PyTorch. Actualiza el driver antes de continuar.

Paso 3: Servir un primer modelo

Empieza con un modelo pequeño para verificar que todo funciona. vllm serve descarga el modelo de Hugging Face la primera vez y lo guarda en ~/.cache/huggingface del usuario que lo ejecuta. Arranca el servidor en primer plano como usuario vllm (la opción -H hace que HOME sea /var/lib/vllm):

sudo -u vllm -H /opt/vllm/venv/bin/vllm serve Qwen/Qwen2.5-1.5B-Instruct \
  --host 127.0.0.1 \
  --port 8000 \
  --max-model-len 8192
  • --host 127.0.0.1: por defecto vLLM escucha en todas las interfaces; aquí lo limitas a local.
  • --max-model-len: longitud máxima de contexto (prompt más respuesta). Limitarla reduce la memoria que se reserva para la caché KV.

El arranque tarda entre 30 segundos y varios minutos: descarga el modelo, lo carga en la GPU, reserva la caché KV y compila los kernels. Está listo cuando veas:

INFO:     Started server process [5123]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

En otra sesión SSH, comprueba el endpoint de salud y la lista de modelos:

curl -i http://127.0.0.1:8000/health
curl -s http://127.0.0.1:8000/v1/models | python3 -m json.tool | grep '"id"'
HTTP/1.1 200 OK
...
            "id": "Qwen/Qwen2.5-1.5B-Instruct",

Paso 4: Usar la API compatible con OpenAI

vLLM implementa los endpoints /v1/chat/completions y /v1/completions de OpenAI, así que cualquier cliente de OpenAI funciona cambiando solo la URL base. Envía una petición de chat:

curl -s http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen/Qwen2.5-1.5B-Instruct",
    "messages": [
      {"role": "system", "content": "Eres un administrador de sistemas Linux. Responde de forma breve."},
      {"role": "user", "content": "¿Cómo veo los puertos en escucha?"}
    ],
    "max_tokens": 200,
    "temperature": 0.3
  }' | python3 -m json.tool

La respuesta sigue el formato de OpenAI: el texto está en choices[0].message.content y el consumo de tokens en usage.

Desde Python puedes usar el SDK oficial de OpenAI. Instálalo en un entorno propio y crea un script de prueba:

python3 -m venv ~/openai-env
~/openai-env/bin/pip install openai
nano ~/prueba_vllm.py
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="sin-clave")

stream = client.chat.completions.create(
    model="Qwen/Qwen2.5-1.5B-Instruct",
    messages=[{"role": "user", "content": "Explica qué es PagedAttention en tres frases."}],
    max_tokens=300,
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
print()
~/openai-env/bin/python ~/prueba_vllm.py

La respuesta aparece token a token. El streaming es lo recomendable en aplicaciones interactivas, porque el usuario ve el texto desde el primer token. Mientras no configures una clave en el servidor, el valor de api_key es indiferente.

Detén el servidor con Ctrl+C en la primera sesión antes de continuar.

Paso 5: Usar un modelo cuantizado con AWQ

La cuantización reduce los pesos a 4 u 8 bits, de modo que un modelo mayor cabe en la misma GPU a cambio de una pérdida de calidad pequeña. En Hugging Face hay versiones ya cuantizadas de los modelos más usados; vLLM detecta el método de cuantización a partir de la configuración del modelo, sin opciones adicionales.

Descarga antes el modelo con la CLI de Hugging Face, que viene instalada con vLLM. Así el arranque del servicio no depende de la descarga y puedes comprobar que se ha completado:

sudo -u vllm -H /opt/vllm/venv/bin/hf download Qwen/Qwen2.5-7B-Instruct-AWQ

Al terminar, el comando imprime la ruta local del modelo. Arráncalo con un nombre corto para la API mediante --served-model-name:

sudo -u vllm -H /opt/vllm/venv/bin/vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ \
  --host 127.0.0.1 \
  --port 8000 \
  --max-model-len 16384 \
  --served-model-name qwen2.5-7b

En el log de arranque verás que vLLM ha detectado la cuantización AWQ. Comprueba en otra sesión cuánta memoria ocupa:

nvidia-smi --query-gpu=memory.used,memory.total --format=csv

El uso será cercano al 90 % de la GPU aunque el modelo pese 5,5 GB: vLLM reserva por defecto el 90 % de la VRAM (--gpu-memory-utilization 0.9) y dedica lo que sobra a la caché KV, que determina cuántas peticiones puede atender a la vez.

Otras opciones de cuantización:

  • Modelos GPTQ ya cuantizados: se cargan igual que los AWQ, vLLM los detecta automáticamente.
  • FP8 al vuelo: en GPUs Ada Lovelace o Hopper (L4, L40S, RTX 40xx, H100), --quantization fp8 cuantiza un modelo BF16 al cargarlo y reduce su memoria a la mitad.

Detén el servidor con Ctrl+C.

Paso 6: Ejecutar vLLM como servicio con clave de API

Para producción, vLLM debe arrancar con el sistema, reiniciarse si falla y exigir una clave de API. Genera una clave aleatoria:

openssl rand -hex 32

Guárdala en un archivo de entorno legible solo por root y el grupo vllm:

sudo mkdir -p /etc/vllm
sudo nano /etc/vllm/vllm.env
VLLM_API_KEY=your_api_key
sudo chown root:vllm /etc/vllm/vllm.env
sudo chmod 640 /etc/vllm/vllm.env

Crea la unidad de systemd:

sudo nano /etc/systemd/system/vllm.service
[Unit]
Description=vLLM OpenAI-compatible server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=vllm
Group=vllm
WorkingDirectory=/var/lib/vllm
Environment=HOME=/var/lib/vllm
Environment=HF_HUB_OFFLINE=1
EnvironmentFile=/etc/vllm/vllm.env
ExecStart=/opt/vllm/venv/bin/vllm serve Qwen/Qwen2.5-7B-Instruct-AWQ \
    --host 127.0.0.1 \
    --port 8000 \
    --max-model-len 16384 \
    --served-model-name qwen2.5-7b \
    --api-key ${VLLM_API_KEY}
Restart=on-failure
RestartSec=10
TimeoutStartSec=600

[Install]
WantedBy=multi-user.target
  • HF_HUB_OFFLINE=1: usa solo el modelo ya descargado en la caché, sin consultar Hugging Face en cada arranque.
  • --api-key: exige la cabecera Authorization: Bearer <clave> en los endpoints /v1.
  • TimeoutStartSec=600: da margen a la carga del modelo en arranques lentos.

Carga y arranca el servicio, y sigue el log hasta ver Application startup complete.:

sudo systemctl daemon-reload
sudo systemctl enable --now vllm
sudo journalctl -u vllm -f

Comprueba que sin clave la API rechaza la petición y con ella responde:

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/v1/models
curl -s http://127.0.0.1:8000/v1/models -H "Authorization: Bearer your_api_key" | python3 -m json.tool | grep '"id"'
401
            "id": "qwen2.5-7b",

Si el servidor tiene varias GPUs y el modelo no cabe en una, añade --tensor-parallel-size 2 (o el número de GPUs) para repartir cada capa entre ellas. vLLM expone además métricas en formato Prometheus en http://127.0.0.1:8000/metrics, con peticiones en cola, uso de la caché KV y latencias.

Paso 7: Inferencia por lotes sin servidor

Para procesar muchos textos de una vez (clasificar, resumir o traducir un conjunto de datos), puedes usar vLLM como librería sin levantar la API. Es más eficiente que enviar las peticiones una a una, porque el motor las agrupa todas. La GPU no puede estar ocupada por el servicio, así que detenlo primero:

sudo systemctl stop vllm

Crea el script en el directorio del usuario vllm:

sudo -u vllm nano /var/lib/vllm/lotes.py
from vllm import LLM, SamplingParams


def main():
    llm = LLM(model="Qwen/Qwen2.5-7B-Instruct-AWQ", max_model_len=4096)
    params = SamplingParams(temperature=0.2, max_tokens=150)

    preguntas = [
        "¿Qué hace el comando chmod 640?",
        "Diferencia entre un proceso y un hilo en una frase.",
        "¿Para qué sirve el archivo /etc/fstab?",
    ]
    conversaciones = [[{"role": "user", "content": p}] for p in preguntas]

    for pregunta, salida in zip(preguntas, llm.chat(conversaciones, params)):
        print(f"### {pregunta}\n{salida.outputs[0].text.strip()}\n")


if __name__ == "__main__":
    main()

llm.chat aplica la plantilla de chat del modelo y procesa todas las conversaciones en un único lote. El bloque if __name__ == "__main__" es necesario porque vLLM arranca procesos auxiliares. Ejecútalo:

sudo -u vllm -H env HF_HUB_OFFLINE=1 /opt/vllm/venv/bin/python /var/lib/vllm/lotes.py

Tras cargar el modelo verás las tres respuestas. Vuelve a arrancar el servicio con sudo systemctl start vllm.

Solución de problemas

torch.OutOfMemoryError: CUDA out of memory o ValueError sobre la caché KV al arrancar. El modelo y la caché no caben en la GPU con el contexto pedido. Reduce --max-model-len, usa un modelo cuantizado o baja --max-num-seqs para limitar las peticiones simultáneas. Si otro proceso usa la GPU, nvidia-smi lo mostrará.

El servicio no arranca y el log muestra un error de conexión con Hugging Face. Con HF_HUB_OFFLINE=1 el modelo debe estar ya en la caché del usuario vllm. Descárgalo con el comando hf download del paso 5 ejecutado como ese usuario. En versiones antiguas de huggingface_hub la CLI se llama huggingface-cli download.

Error 403 o gated repo al descargar un modelo. Modelos como Llama requieren aceptar su licencia en Hugging Face y un token de acceso. Añade HF_TOKEN=your_hf_token a /etc/vllm/vllm.env y pásalo también al descargar con sudo -u vllm -H env HF_TOKEN=your_hf_token /opt/vllm/venv/bin/hf download ....

El primer token tarda mucho con prompts largos. Es el tiempo de procesar el prompt (prefill) y crece con su longitud. Usa streaming en la aplicación y reutiliza prefijos comunes (el mismo mensaje de sistema), que vLLM cachea automáticamente.

Conclusión

Tienes vLLM sirviendo un modelo cuantizado en Ubuntu 24.04 con una API compatible con OpenAI, clave de acceso y un servicio systemd, además de un script para inferencia por lotes. Como siguientes pasos, coloca un proxy inverso con HTTPS delante del puerto 8000 si otras máquinas deben consumir la API, recoge las métricas de /metrics en tu sistema de monitorización y prueba distintos valores de --max-num-seqs y --max-model-len para equilibrar latencia y concurrencia con tu tráfico real.