Logo NascimentoIgor Nascimento / Blog
Infraestrutura

Do Ollama ao vLLM: servindo um time inteiro em duas RTX 3060, com o painel aberto

Existe um abismo entre rodar uma IA no terminal e servir um modelo para uma equipe usar em tempo real. Quase todo mundo descobre isso da pior forma, quando duas pessoas mandam mensagem ao mesmo tempo e a memória bate no teto. Uma conversa sobre por que o Ollama não bastou, o que mudou ao migrar para o vLLM, e a leitura, número por número, do painel que construí para enxergar o cluster.

InfraestruturaInferência15 min de leituraIgor Nascimento

O abismo entre o terminal e o time

Existe um abismo gigante entre "rodar uma IA no terminal" e "servir um modelo para uma equipe inteira usar em tempo real". E quase todo mundo só descobre isso da pior forma: quando a memória bate no teto e duas pessoas tentam mandar mensagem ao mesmo tempo.

Foi enfrentando exatamente esse gargalo que decidi construir uma infraestrutura própria: um servidor com duas GPUs dedicadas, duas RTX 3060 de 12 GB, capaz de entregar mais de 160 tokens por segundo agregados para o time usar simultaneamente, sem fila e com latência baixa. Este texto é sobre o caminho até lá, e sobre o painel que construí para enxergar o que estava acontecendo, porque sem ele eu estaria chutando.

Por que o Ollama não bastou

O Ollama é fantástico para prototipagem rápida e para uso individual. Baixa o modelo com um comando, troca de modelo em segundos, roda em qualquer máquina. Eu continuo usando no notebook. O problema começa quando se tenta transformar esse setup num serviço compartilhado. O fluxo tradicional atende uma requisição por vez por modelo carregado; há configuração para paralelismo, mas o motor não foi desenhado em torno disso. Se alguém pede um resumo longo, os outros ficam na fila esperando aquele decode terminar. E cada requisição tem seu KV cache alocado de forma simples, o que desperdiça VRAM e limita quantas cabem ao mesmo tempo.

Para um usuário, nada disso aparece. Para seis, aparece na primeira manhã.

Ollama

  • Instalação e troca de modelo imediatas
  • Convive com outras coisas na GPU; aloca o que precisa
  • Concorrência limitada; fila quando há mais de um pedido longo
  • Ideal para desenvolvimento e uso pessoal

vLLM

  • Reserva a GPU inteira na inicialização; troca de modelo é lenta
  • Continuous batching e PagedAttention de fábrica
  • Paralelismo de tensor entre placas com uma flag
  • Ideal para servir vários usuários com métricas

A barreira invisível da VRAM

Em inferência, velocidade é reflexo da largura de banda de memória. A VRAM de uma GPU dedicada transfere dados várias vezes mais rápido do que a RAM do sistema e ordens de grandeza mais rápido do que um NVMe. Para sustentar 160 tokens por segundo com fluidez constante, o modelo tem que estar inteiramente carregado na VRAM, com o KV cache junto. Se apenas 5% ou 10% transbordar para a RAM, o throughput desaba, e não proporcionalmente: a parte lenta domina a conta, como mostro em outro texto sobre a hierarquia de largura de banda. Essa foi a primeira lição do servidor, aprendida da forma dolorosa, com o modelo "quase cabendo".

A solução foi dividir o modelo entre as duas placas com paralelismo de tensor. Cada uma carrega metade dos pesos, e as bandas das duas VRAMs se somam. O custo é a sincronização pelo PCIe a cada camada, que existe e aparece nos números, mas é muito menor do que o custo de transbordar.

Continuous batching e o KV cache dinâmico

A virada de chave foi migrar para um motor de inferência feito para produção. Em vez de criar filas bloqueantes, o vLLM aplica continuous batching: requisições que chegam entram direto no ciclo de geração que já está rodando, e as que terminam saem, sem esperar o lote inteiro. Como o decode é limitado por memória, gerar para oito sequências ao mesmo tempo custa quase a mesma leitura de pesos que gerar para uma. É daí que vêm os 160 tokens por segundo agregados: não é uma sequência a 160, são várias sequências compartilhando a mesma passagem pelos pesos.

Aliado a isso, o PagedAttention transforma a alocação do KV cache em algo parecido com memória virtual: blocos pequenos, não contíguos, alocados sob demanda e compartilhados entre sequências que têm o mesmo prefixo. É o que permite altas taxas de acerto de cache e respostas rápidas mesmo sob concorrência.

A telemetria, número por número

Para validar e acompanhar esse comportamento, montei um painel próprio. Dois instantes dele contam a história melhor do que eu.

Painel do servidor em repouso: zero requisições, cache hit 97%, primeiro token em 0,87 segundo, GPUs a 88% de VRAM e 49 e 41 graus
Em repouso, depois de quase 23 horas no ar. Nenhuma requisição rodando, mas as GPUs a 88% de VRAM: é a reserva fixa do vLLM, pesos mais pool de KV cache. Cache hit de 97% e primeiro token em 0,87 segundo nas últimas requisições.
Painel do servidor sob carga: 95 tokens por segundo gerando, 12.968,6 lendo prompt, cache hit 85,5%, primeiro token em 1,72 segundo
Sob carga, um dia e meio depois. Gerando 95 tokens por segundo e lendo prompt a 12.968,6. Cache hit caiu para 85,5% e o primeiro token subiu para 1,72 segundo. A relação entre as duas coisas é direta.

O que cada número diz:

O painel também registra uso por pessoa: requisições, tokens gerados, tokens de prompt. Serve para duas coisas: saber quem usa o quê, para planejar capacidade, e perceber quando uma integração automatizada está consumindo mais do que todos os humanos juntos.

O que a migração custou

Nada disso veio de graça, e é importante dizer o que piorou:

A frase para levar

Colocar inteligência artificial para rodar localmente com alta performance não é mágica de software. É ajuste fino de arquitetura e respeito aos limites do hardware. E a única forma de respeitar um limite é conseguir enxergá-lo, o que é o motivo de o painel ter sido a primeira coisa que construí depois do servidor.

Resumo da conversa