
Si ya has ejecutado un LLM local con Ollama o llama.cpp, sabes que funciona muy bien — para una o dos personas. Cuando la demanda se convierte en una API que necesita atender decenas de peticiones simultáneas, el juego cambia: la GPU queda ociosa esperando, la memoria se fragmenta y el rendimiento se desploma. Ese es exactamente el problema que vLLM resuelve, y por eso se ha convertido en el motor de inferencia estándar de buena parte de la industria.
Este post cubre la historia del proyecto, la idea técnica que lo hizo rápido y un paso a paso ejecutable para instalar y levantar tu primer servidor en Linux.
¿De dónde viene vLLM?
vLLM nació en el Sky Computing Lab de UC Berkeley. El repositorio se creó en febrero de 2023, y el lanzamiento público llegó en 20 de junio de 2023, post que anunciaba algo difícil de creer en aquella época: hasta 24 veces más rendimiento que HuggingFace Transformers y hasta 3,5 veces más que Text Generation Inference, sin cambiar la arquitectura del modelo.
No era marketing de laboratorio. Antes del anuncio, el vLLM ya llevaba dos meses funcionando en producción dando servicio al Vicuna y la Chatbot Arena de LMSYS: era la tecnología que permitía a un pequeño grupo de investigación hacer frente a la factura de GPU de un chatbot público.
La idea central se publicó en el artículo “Efficient Memory Management for Large Language Model Serving with PagedAttention”, presentado en SOSP 2023 — lo que dice mucho sobre la naturaleza del trabajo: no es un paper de machine learning, es un paper de sistemas operativos.
La línea de tiempo desde entonces:
- Feb/2023 — repositorio creado en GitHub, bajo licencia Apache 2.0.
- Jun/2023 — lanzamiento público; ya en producción en Vicuna y en Chatbot Arena.
- Oct/2023 — paper de PagedAttention en SOSP.
- Ene/2025 — vLLM V1, reescritura del núcleo (scheduler, gestor de caché KV, worker, sampler y servidor de API), con ~1,7x de ganancia. Sale en alpha en la v0.7.0 y se convierte en el motor por defecto en la v0.8.0.
- May/2025 — el proyecto pasa a estar alojado por la PyTorch Foundation, uno de los primeros proyectos de plataforma bajo este paraguas.
- Ago/2026 — versión v0.28.0. El repositorio supera 91 mil estrellas y 21 mil forks, con cientos de colaboradores.
PagedAttention en 30 segundos
Para generar cada token nuevo, el modelo necesita consultar las claves y valores de atención de todos los tokens anteriores. Ese material permanece en la VRAM y se denomina KV cache. Es grande (llegaba a 1,7 GB por secuencia en LLaMA-13B) y, peor aún, dinámico: el tamaño depende de la longitud de la conversación, que nadie conoce de antemão.
Los motores anteriores lo resolvían reservando un bloque contiguo del tamaño máximo posible para cada solicitud. Resultado: los autores midieron un % a % de desperdicio de VRAM por fragmentación y reserva excesiva. Memoria desperdiciada significa menos solicitudes simultáneas, lo que significa GPU ociosa.
PagedAttention aplica a la caché KV la misma solución que el kernel lleva décadas usando para la memoria del sistema — paginación:
- la caché KV de cada secuencia se divide en bloques de tamaño fijo;
- los bloques no tienen por qué ser contiguos en la VRAM y se asignan bajo demanda;
- una block table mapea bloques lógicos a físicos, exactamente igual que una tabla de páginas.
La analogía es literal: los bloques son páginas, los tokens son bytes, las secuencias son procesos. Y, como en cualquier sistema con paginación, viene de regalo el compartido: dos solicitudes con el mismo prompt apuntan a los mismos bloques físicos, con conteo de referencias y copy-on-write. El desperdicio cae a menos de 4%, y sobra VRAM para agrupar muchas más secuencias en el mismo lote.
Súmale a eso el continuous batching — en lugar de esperar a que termine todo el lote, el planificador intercala nuevas solicitudes en cuanto se libera una plaza — y tienes la explicación completa de la ganancia de rendimiento.
¿vLLM, Ollama o llama.cpp?
Los tres sirven LLMs, pero resuelven problemas diferentes. Elegir mal duele:
| vLLM | Ollama / llama.cpp | |
|---|---|---|
| Caso de uso | API multiusuario, producción, alto rendimiento | Uso personal y equipos pequeños; admiten concurrencia (--parallel, OLLAMA_NUM_PARALLEL), pero el rendimiento cae rápido con la carga |
| Hardware | GPU dedicada con VRAM de sobra | Se ejecuta en CPU, GPU parcial, poca VRAM |
| Formato del modelo | Pesos de Hugging Face (safetensors) | GGUF cuantizado |
| El modelo no cabe en la VRAM | Es posible volcar a la RAM (--cpu-offload-gb), pero tiene un coste elevado en latencia |
Descarga a la RAM y continúa |
| Facilidad | Requiere planificar VRAM y parámetros | Un comando y funciona |
Regla práctica: si estás solo en tu escritorio, usa Ollama o el llama-server. Si es un servicio que varias personas o varios agentes van a consumir al mismo tiempo, vLLM.
Requisitos
- Linux (es la plataforma de primera clase) y Python 3.10 a 3.13.
- GPU NVIDIA con driver y CUDA funcionando es el camino más sencillo. Hay soporte para AMD (ROCm), Intel (XPU), TPU de Google y NPUs Ascend, además del
vLLM-Metalpara Apple Silicon. - VRAM: cuenta con los pesos del modelo más la caché KV. Un modelo de 7B en FP16 pide ~14 GB solo de pesos — añade margen para la caché. A partir de 24 GB la cosa va cómoda.
Comprueba el entorno antes:
nvidia-smi
python3 --version
Instalación
La forma recomendada por la documentación es con uv, que resuelve el índice correcto de PyTorch para tu versión de CUDA automáticamente:
# instala o uv, se ainda não tiver
curl -LsSf https://astral.sh/uv/install.sh | sh
# ambiente isolado + vLLM
uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --torch-backend=auto
O --torch-backend=auto inspecciona el driver CUDA instalado y elige la build adecuada. Para fijar una versión específica, cámbialo por --torch-backend=cu129 (o la build que uses — los binarios de vLLM hoy salen para CUDA 12.8, 12.9 y 13.0).
¿Solo quieres probar, sin crear ningún entorno?
uv run --with vllm vllm --help
En GPUs AMD, el índice es otro:
uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --extra-index-url https://wheels.vllm.ai/rocm/
O --python 3.12 no es un detalle: las wheels ROCm solo existen para esa versión. Con 3.11 o 3.13 el instalador recurre silenciosamente a la wheel CUDA de PyPI, y el error solo aparece al ejecutar, como libcudart.so: cannot open shared object file.
Y, si prefieres no instalar nada en el host, la imagen oficial lo resuelve:
docker run --runtime nvidia --gpus all
-v ~/.cache/huggingface:/root/.cache/huggingface
-p 8000:8000 --ipc=host
vllm/vllm-openai:latest
--model Qwen/Qwen2.5-1.5B-Instruct
--ipc=host da al contenedor acceso a la memoria compartida del host. Sin él, /dev/shm se queda minúsculo y vLLM falla al inicializar los workers — sobre todo con tensor parallel. La alternativa, si no quieres compartir el namespace de IPC, es --shm-size=8g.
Primera prueba: inferencia por lotes
Antes de levantar el servidor, merece la pena hacer una prueba offline para confirmar que se está usando la GPU. Crea teste.py:
from vllm import LLM, SamplingParams
prompts = [
"O kernel Linux foi criado por",
"A capital do Brasil é",
"Em uma frase, explique o que é paginação de memória:",
]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95, max_tokens=64)
llm = LLM(model="Qwen/Qwen2.5-1.5B-Instruct")
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(f">> {output.prompt!r}")
print(f" {output.outputs[0].text!r}n")
python3 teste.py
Fíjate en que los tres prompts se procesan juntos, no en cola, es el batching en acción. Un detalle que pilla a mucha gente: llm.generate() no aplica el chat template del modelo. Para modelos instruct, use llm.chat() con la misma estructura de mensajes de la API de OpenAI:
mensagens = [[{"role": "user", "content": p}] for p in prompts]
outputs = llm.chat(mensagens, sampling_params)
Levantando el servidor compatible con la API de OpenAI
Esta es la principal razón de ser de vLLM en producción: habla el protocolo de OpenAI, así que cualquier aplicación, SDK o agente que ya usa OpenAI apunta a él cambiando solo la base_url.
vllm serve Qwen/Qwen2.5-1.5B-Instruct
El servidor arranca en http://localhost:8000. Prueba:
curl http://localhost:8000/v1/models
curl http://localhost:8000/v1/chat/completions
-H "Content-Type: application/json"
-d '{
"model": "Qwen/Qwen2.5-1.5B-Instruct",
"messages": [{"role": "user", "content": "Explique inodes em duas frases."}],
"temperature": 0.7
}'
En el lado del cliente Python, es el SDK oficial de OpenAI sin truco:
from openai import OpenAI
client = OpenAI(api_key="EMPTY", base_url="http://localhost:8000/v1")
resposta = client.chat.completions.create(
model="Qwen/Qwen2.5-1.5B-Instruct",
messages=[{"role": "user", "content": "Escreva um one-liner que conta arquivos por extensão."}],
)
print(resposta.choices[0].message.content)
Para exigir autenticación, arráncalo con --api-key SEGREDO — o, mejor, con la variable VLLM_API_KEY, porque la clave en la línea de comandos aparece en el ps aux. El servidor acepta múltiples claves, lo que facilita la rotación sin tiempo de inactividad.
Atención: --api-key autentica solo los prefijos /v1, /v2 y /inference. Endpoints como /invocations (que expone la misma capacidad de inferencia), /pooling, /classify y los de control /pause, /resume y /abort_requests siguen abiertos. Si usas --host 0.0.0.0, pon vLLM detrás de un proxy inverso que abra solo las rutas que quieras exponer — nunca dejes el puerto 8000 accesible directamente desde la red.
Los parámetros que realmente importan
El valor predeterminado funciona, pero casi nunca es lo que quieres en producción:
vllm serve Qwen/Qwen2.5-7B-Instruct
--host 0.0.0.0 --port 8000
--gpu-memory-utilization 0.90
--max-model-len 8192
--tensor-parallel-size 1
--api-key MINHA_CHAVE
--gpu-memory-utilization— fracción de la VRAM que vLLM puede ocupar (por defecto 0.92 en las versiones actuales; era 0.9 hasta la v0.11). Descarga si la GPU también ejecuta su monitor u otro proceso; sube si la tarjeta es dedicada. Cuanto mayor, más caché KV, más solicitudes simultáneas.--max-model-len— ventana de contexto máxima. Es el parámetro nº 1 para resolver el error de memoria al iniciar: los modelos anuncian contextos enormes (128k) que exigen caché KV que no tienes. Recorta a lo que tu aplicación realmente usa.--tensor-parallel-size— número de GPUs para repartir el modelo. Úsalo cuando el modelo no cabe en una sola tarjeta.--dtype—autoresuelve bien;bfloat16en tarjetas Ampere o más nuevas,float16en tarjetas antiguas.--quantization— para pesos AWQ, GPTQ o FP8, que recortan la VRAM a la mitad o más.--served-model-name— el nombre que expone la API, útil para no filtrar la ruta de Hugging Face a los clientes.--cpu-offload-gb— cuántos GiB de los pesos empujar a la RAM, por GPU. Salva el día cuando el modelo no cabe, pero cada forward pass pasa por el bus: úselo solo si la alternativa es no ejecutar.
Los modelos con licencia restringida (Llama, Gemma) requieren token de Hugging Face:
export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxx
vllm serve meta-llama/Llama-3.1-8B-Instruct
Ejecutando como servicio en systemd
En un servidor de verdad, vLLM tiene que levantarse solo en el arranque. Cree /etc/systemd/system/vllm.service:
[Unit]
Description=vLLM OpenAI-compatible server
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
User=vllm
Group=vllm
WorkingDirectory=/opt/vllm
Environment="HF_HOME=/opt/vllm/hf"
Environment="VLLM_API_KEY=MINHA_CHAVE"
ExecStart=/opt/vllm/.venv/bin/vllm serve Qwen/Qwen2.5-7B-Instruct
--host 0.0.0.0 --port 8000
--gpu-memory-utilization 0.90
--max-model-len 8192
Restart=always
RestartSec=10
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target
O TimeoutStartSec alto es intencional: en la primera ejecución el modelo se descarga de Hugging Face y los kernels se compilan, y con el valor por defecto de 90 segundos systemd se rendiría a mitad de camino. Tenga en cuenta que solo surte efecto porque la unit usa Type=exec — con Type=simple, systemd considera el servicio iniciado ya en fork() y el tiempo de espera de arranque nunca llega a aplicarse.
El servicio se ejecuta con su propio usuario y entorno: el .venv que creó en su directorio personal no sirve aquí:
sudo useradd -r -s /usr/sbin/nologin -d /opt/vllm vllm
sudo mkdir -p /opt/vllm/hf
sudo chown -R vllm:vllm /opt/vllm
# instala o vLLM dentro de /opt/vllm, como o usuário do serviço
sudo -u vllm uv venv --python 3.12 --seed /opt/vllm/.venv
sudo -u vllm env VIRTUAL_ENV=/opt/vllm/.venv uv pip install vllm --torch-backend=auto
sudo systemctl daemon-reload
sudo systemctl enable --now vllm
journalctl -u vllm -f
Problemas comunes
CUDA out of memoryen el arranque — casi siempre es la caché KV, no los pesos. Reduzca--max-model-lenprimero, luego--gpu-memory-utilization, y solo entonces cambie de modelo o pase a pesos cuantizados.- Error de
/dev/shmo workers muriendo en Docker — faltó--ipc=host. - El modelo responde tonterías en modo chat — usted usó
generate()en lugar dechat(), y el chat template no se aplicó. - El arranque del servicio tarda mucho — es la compilación de los kernels y la captura de los CUDA graphs, que ocurren en el inicio del engine, no en la primera petición. Los artefactos quedan en
~/.cache/vllmy se reutilizan en los arranques siguientes; en Docker, monte un volumen en esa ruta para no recompilar en cada contenedor. Para saltarse ese paso, a costa de un decode más lento, use--enforce-eager. - Descarga atascada o 401 — modelo con licencia restringida: acepte los términos en la página de Hugging Face y expórtelo
HF_TOKEN.
Cerrando
vLLM es el ejemplo raro de proyecto de investigación que se convirtió en infraestructura estándar porque resolvió el problema adecuado: trató la VRAM de una GPU como el kernel trata la RAM de un servidor. La lección es vieja — paginación, tabla de páginas, copy-on-write — solo que el hardware es nuevo.
Para continuar: entienda la diferencia entre GPU y TPU, vea cómo montar un servidor de IA reacondicionado con 64 GB de VRAM para alojar todo esto sin salirse del presupuesto, o compare con el camino más sencillo de ejecutar IA local con Ollama.
Enlaces oficiales: vllm.ai · github.com/vllm-project/vllm · documentación · paper do PagedAttention