vLLM: serving LLMs on Linux with high throughput — history, installation, and first steps

Mascote do LinuxPro paginando blocos de memória na VRAM, placa 3D do V do vLLM na parede

If you've already run a local LLM with Ollama or llama.cpp, you know it works very well — for one or two people. When the demand becomes an API that needs to serve dozens of simultaneous requests, the game changes: the GPU sits idle waiting, memory gets fragmented, and throughput plummets. That's exactly the problem that vLLM solves, and that's why it has become the standard inference engine for much of the industry.

This post covers the project's history, the technical idea that made it fast, and a step-by-step walkthrough to install and launch your first server on Linux.

Where vLLM came from

vLLM was born at the Sky Computing Lab at UC Berkeley. The repository was created in February 2023, and the public release came in 20 June 2023, in a post announcing something hard to believe at the time: up to 24x more throughput than HuggingFace Transformers and up to 3,5x more than Text Generation Inference, without changing the model's architecture.

It wasn't lab marketing. Before the announcement, vLLM had already been running in production for two months serving the Vicuna and the Chatbot Arena from LMSYS — it was the technology that allowed a small research group to pay the GPU bill for a public chatbot.

The core idea was published in the paper “Efficient Memory Management for Large Language Model Serving with PagedAttention”, presented at SOSP 2023 — which says a lot about the nature of the work: it's not a machine learning paper, it's a paper about operating systems.

The timeline since then:

  • Feb/2023 — repository created on GitHub, under Apache license 2.0.
  • Jun/2023 — public release; already in production on Vicuna and Chatbot Arena.
  • Oct/2023 — PagedAttention paper at SOSP.
  • Jan/2025vLLM V1, core rewrite (scheduler, KV cache manager, worker, sampler, and API server), with ~1,7x gain. Released in alpha in v0.7.0 and becomes the default engine in v0.8.0.
  • May/2025 — the project is now hosted by PyTorch Foundation, one of the first platform projects under this umbrella.
  • Aug/2026 — version v0.28.0. The repository has over 91 thousand stars and 21 thousand forks, with hundreds of contributors.

PagedAttention in 30 seconds

To generate each new token, the model needs to query the attention keys and values of all previous tokens. This material resides in VRAM and is called KV cache. It is large (reaching 1,7 GB per sequence in LLaMA-13B) and, worse, dynamic: the size depends on the conversation length, which no one knows in advance.

Previous engines solved this by reserving a contiguous block of the maximum possible size for each request. Result: the authors measured 60% to 80% of waste of VRAM from fragmentation and over-provisioning. Wasted memory means fewer concurrent requests, which means idle GPU.

PagedAttention applies to the KV cache the same solution the kernel has used for system memory for decades — paging:

  • the KV cache of each sequence is divided into blocks of fixed size;
  • the blocks don't need to be contiguous in VRAM, and are allocated on demand;
  • a block table maps logical blocks to physical ones, exactly like a page table.

The analogy is literal: blocks are pages, tokens are bytes, sequences are processes. And, as in any system with paging, you get for free the sharing: two requests with the same prompt point to the same physical blocks, with reference counting and copy-on-write. Waste drops to less than 4%, and there's leftover VRAM to group many more sequences in the same batch.

Add to that the continuous batching — instead of waiting for the entire batch to finish, the scheduler slots in new requests as soon as a spot opens — and you have the full explanation of the throughput gain.

vLLM, Ollama, or llama.cpp?

All three serve LLMs, but they solve different problems. Choosing wrong hurts:

vLLM Ollama / llama.cpp
Use case Multi-user API, production, high throughput Personal use and small teams; they handle concurrency (--parallel, OLLAMA_NUM_PARALLEL), but throughput drops quickly under load
Hardware Dedicated GPU with spare VRAM Runs on CPU, partial GPU, little VRAM
Model format Hugging Face weights (safetensors) Quantized GGUF
Model doesn't fit in VRAM Offload to RAM is possible (--cpu-offload-gb), but it's costly in latency Faz offload para a RAM e segue
Ease of use Exige planejar VRAM e parâmetros One command and it works

Regra prática: se é você sozinho no seu desktop, use Ollama ou o llama-server. Se é um serviço que várias pessoas ou vários agentes vão consumir ao mesmo tempo, vLLM.

Requirements

  • Linux (é a plataforma de primeira classe) e Python 3.10 a 3.13.
  • GPU NVIDIA com driver e CUDA funcionando é o caminho mais suave. Há suporte a AMD (ROCm), Intel (XPU), TPU do Google e NPUs Ascend, além do vLLM-Metal para Apple Silicon.
  • VRAM: conte com os pesos do modelo mais o KV cache. Um modelo de 7B em FP16 pede ~14 GB só de pesos — some folga para o cache. É a partir de 24 GB que a coisa fica confortável.

Confira o ambiente antes:

nvidia-smi
python3 --version

Installing

A forma recomendada pela documentação é com o uv, que resolve o índice correto do PyTorch para a sua versão de CUDA automaticamente:

# 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

The --torch-backend=auto inspeciona o driver CUDA instalado e escolhe a build certa. Para fixar uma versão específica, troque por --torch-backend=cu129 (ou a build que você usa — os binários do vLLM hoje saem para CUDA 12.8, 12.9 e 13.0).

Quer só experimentar, sem criar ambiente nenhum?

uv run --with vllm vllm --help

Em GPUs AMD, o índice é outro:

uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install vllm --extra-index-url https://wheels.vllm.ai/rocm/

The --python 3.12 não é detalhe: as wheels ROCm só existem para essa versão. Com 3.11 ou 3.13 o instalador cai silenciosamente na wheel CUDA do PyPI, e o erro só aparece na hora de rodar, como libcudart.so: cannot open shared object file.

E, se você prefere não instalar nada no host, a imagem oficial resolve:

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

The --ipc=host dá ao container acesso à memória compartilhada do host. Sem ele, /dev/shm fica minúsculo e o vLLM quebra ao inicializar os workers — sobretudo com tensor parallel. A alternativa, se você não quiser compartilhar o namespace de IPC, é --shm-size=8g.

First test: batch inference

Antes de subir o servidor, vale um teste offline para confirmar que a GPU está sendo usada. Crie 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

Repare que os três prompts são processados juntos, não em fila — é o batching em ação. Um detalhe que pega muita gente: llm.generate() não aplica o chat template do modelo. Para modelos instruct, use llm.chat() com a mesma estrutura de mensagens da API da OpenAI:

mensagens = [[{"role": "user", "content": p}] for p in prompts]
outputs = llm.chat(mensagens, sampling_params)

Launching the server compatible with the OpenAI API

Esta é a principal razão de ser do vLLM em produção: ele fala o protocolo da OpenAI, então qualquer aplicação, SDK ou agente que já usa a OpenAI aponta para ele trocando apenas a base_url.

vllm serve Qwen/Qwen2.5-1.5B-Instruct

O servidor sobe em http://localhost:8000. Teste:

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
  }'

Do lado do cliente Python, é o SDK oficial da OpenAI sem gambiarra:

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 autenticação, suba com --api-key SEGREDO — ou, melhor, com a variável VLLM_API_KEY, porque chave em linha de comando aparece no ps aux. O servidor aceita múltiplas chaves, o que facilita rotação sem downtime.

Attention: o --api-key autentica apenas os prefixos /v1, /v2 and /inference. Endpoints como /invocations (que expõe a mesma capacidade de inferência), /pooling, /classify e os de controle /pause, /resume and /abort_requests continuam abertos. Se você usa --host 0.0.0.0, ponha o vLLM atrás de um proxy reverso que libere só as rotas que você quer expor — nunca deixe a porta 8000 acessível direto da rede.

The parameters that really matter

O padrão funciona, mas quase nunca é o que você quer em produção:

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 — fração da VRAM que o vLLM pode ocupar (padrão 0.92 nas versões atuais; era 0.9 até a v0.11). Baixe se a GPU também roda seu monitor ou outro processo; suba se a placa é dedicada. Quanto maior, mais KV cache, mais requisições simultâneas.
  • --max-model-len — janela de contexto máxima. É o parâmetro nº 1 para resolver erro de memória na inicialização: modelos anunciam contextos gigantes (128k) que exigem KV cache que você não tem. Corte para o que a sua aplicação de fato usa.
  • --tensor-parallel-size — número de GPUs para dividir o modelo. Use quando o modelo não cabe em uma placa só.
  • --dtypeauto resolve bem; bfloat16 em placas Ampere ou mais novas, float16 em placas antigas.
  • --quantization — para pesos AWQ, GPTQ ou FP8, que cortam a VRAM pela metade ou mais.
  • --served-model-name — o nome que a API expõe, útil para não vazar o caminho do Hugging Face para os clientes.
  • --cpu-offload-gb — quantos GiB dos pesos empurrar para a RAM, por GPU. Salva o dia quando o modelo não cabe, mas cada forward pass passa pelo barramento: só use se a alternativa for não rodar.

Modelos com licença restrita (Llama, Gemma) exigem token do Hugging Face:

export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxx
vllm serve meta-llama/Llama-3.1-8B-Instruct

Running as a service in systemd

Em um servidor de verdade, o vLLM tem que subir sozinho no boot. Crie /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

The TimeoutStartSec alto é proposital: na primeira execução o modelo é baixado do Hugging Face e os kernels são compilados, e com o padrão de 90 segundos o systemd desistiria no meio do caminho. Repare que ele só tem efeito porque a unit usa Type=exec — com Type=simple, o systemd considera o serviço iniciado já no fork() e o timeout de partida nunca chega a valer.

O serviço roda com usuário e ambiente próprios — o .venv que você criou no seu diretório pessoal não serve aqui:

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

Common problems

  • CUDA out of memory na inicialização — quase sempre é o KV cache, não os pesos. Reduza --max-model-len primeiro, depois --gpu-memory-utilization, e só então troque de modelo ou parta para pesos quantizados.
  • Erro de /dev/shm ou workers morrendo em Docker — faltou --ipc=host.
  • Modelo responde bobagem em modo chat — você usou generate() em vez de chat(), e o chat template não foi aplicado.
  • A subida do serviço demora muito — é a compilação dos kernels e a captura dos CUDA graphs, que acontecem na inicialização do engine, não na primeira requisição. Os artefatos ficam em ~/.cache/vllm e são reaproveitados nos boots seguintes; em Docker, monte um volume nesse caminho para não recompilar a cada container. Para pular a etapa, ao custo de decode mais lento, use --enforce-eager.
  • Download travando ou 401 — modelo com licença restrita: aceite os termos na página do Hugging Face e exporte HF_TOKEN.

Closing

O vLLM é o exemplo raro de projeto de pesquisa que virou infraestrutura padrão porque resolveu o problema certo: tratou a VRAM de uma GPU como o kernel trata a RAM de um servidor. A lição é velha — paginação, tabela de páginas, copy-on-write — só o hardware é novo.

Para continuar: entenda a diferença entre GPU e TPU, veja como montar um refurbished AI server with 64 GB of VRAM para hospedar tudo isso sem quebrar o orçamento, ou compare com o caminho mais simples de rodar IA local com Ollama.

Official links: vllm.ai · github.com/vllm-project/vllm · documentation · paper do PagedAttention