Servir LLMs (LLM Serving)

O que é serviço de modelos de linguagem grandes?

Serviço de modelos de linguagem grandes (LLM serving) é a infraestrutura e o conjunto de softwares que transforma um modelo de linguagem grande (large language model, LLM) treinado em um serviço online confiável, escalável e de baixa latência. Na prática, isso significa responder a solicitações como “gere 200 tokens para este prompt” (muitas vezes com transmissão em tempo real (streaming)), enquanto atende a requisitos de produção: objetivos de nível de serviço (service level objectives, SLOs), controles de custo (cost controls), limites de segurança (safety limits), observabilidade (observability), lançamentos (rollouts) e resposta a incidentes (incident response).

O serviço de modelos de linguagem grandes se encaixa dentro da disciplina mais ampla de Serviço de Modelos, mas tem restrições específicas porque a inferência (inference) de LLM é:

  • Autorregressiva (autoregressive): o modelo gera um token por vez.
  • De comprimento variável (variable-length): os comprimentos do prompt e da saída variam amplamente.
  • Pesada em memória (memory-heavy): a atenção usa um cache KV (KV cache) crescente ao longo dos tokens gerados (veja Cache KV).
  • Sensível à latência (latency-sensitive): usuários frequentemente se importam mais com o tempo até o primeiro token (time-to-first-token, TTFT) do que com o tempo total até a conclusão.
  • Altamente sujeita a rajadas (highly bursty): picos de tráfego podem causar enfileiramento e sobrecarga de GPU se não forem controlados.

Dois mecanismos de serviço de código aberto amplamente usados hoje são vLLM e TGI (Text Generation Inference). Este artigo explica como funcionam as pilhas de serviço de LLM e as compensações operacionais envolvidas.

Por que o serviço de LLM é diferente de uma inferência “normal”

A inferência online tradicional para classificadores/regressores geralmente é “uma requisição → uma passagem para a frente (forward pass) → uma resposta”. No serviço de LLM, normalmente é “uma requisição → muitas passagens para a frente”, porque cada token de saída depende dos tokens anteriores.

Fases de pré-preenchimento vs decodificação

Um modelo mental útil divide a geração em:

  • Pré-preenchimento (prefill): processamento dos tokens do prompt em um lote (batch) (frequentemente pesado em computação e eficiente em GPUs).
  • Decodificação (decode): geração de tokens um a um (frequentemente limitada por memória/largura de banda e sensível ao escalonamento).

Na decodificação, cada passo lê/escreve o cache KV e executa atenção sobre o contexto crescente. Isso torna agrupamento em lote (batching) e escalonamento (scheduling) preocupações de primeira ordem (veja Agrupamento em Lote e Escalonamento de Inferência).

Principais métricas com as quais você deve se importar

O desempenho do serviço de LLM geralmente é acompanhado por:

  • TTFT (Time to First Token): responsividade percebida (especialmente para UIs com transmissão em tempo real).
  • Tokens/s (vazão (throughput)): eficiência agregada; frequentemente medida como tokens de saída/s por GPU.
  • Latência P50/P95: a latência de cauda (tail latency) importa muito sob carga.
  • Tempo de fila (queue time): tempo gasto esperando por um slot/lote de GPU.
  • Contexto máximo / taxa de OOM: com que frequência requisições falham por pressão de memória.

Essas métricas se conectam diretamente a decisões de Custo/Desempenho e devem fazer parte dos seus SLOs (veja SLOs para Funcionalidades de IA).

Anatomia de uma pilha de serviço de LLM

Um sistema de serviço de LLM em produção geralmente inclui:

1) Camada de API (“porta de entrada”)

Responsabilidades:

  • Autenticação, autorização
  • Validação de requisições (comprimento máximo do prompt, máximo de tokens de saída)
  • Limitação de taxa e cotas (veja Cache e Limitação de Taxa)
  • Endpoints compatíveis com OpenAI (OpenAI-compatible) e/ou endpoints personalizados
  • Transmissão em tempo real via SSE/websockets

2) Tokenização

O servidor converte texto em IDs de token e acompanha:

  • Comprimento do prompt (tokens de entrada)
  • Comprimento de saída solicitado (máximo de novos tokens)
  • Sequências de parada, logprobs, etc.

A escolha do tokenizador deve corresponder ao modelo (uma fonte frequente de bugs sutis durante upgrades).

3) Agendador / formador de lotes

Isso decide:

  • Quais requisições compartilham um lote
  • Como intercalar passos de decodificação entre requisições
  • Se deve priorizar TTFT ou vazão
  • Quando preemptar ou rejeitar requisições sob carga

Servidores modernos fazem agrupamento contínuo (continuous batching) (também chamado de “agrupamento em nível de iteração (iteration-level batching)”): eles mantêm a GPU ocupada mesclando novas requisições na próxima iteração de decodificação, em vez de esperar por um limite fixo de lote.

4) Mecanismo de inferência

Isso executa as passagens para a frente reais de forma eficiente usando:

  • Kernels de atenção (attention kernels) otimizados (por exemplo, abordagens do tipo FlashAttention)
  • Paralelismo de tensores (tensor parallelism) entre GPUs (para modelos grandes)
  • Gerenciamento do cache KV (crítico para contextos longos)
  • Recursos opcionais como decodificação especulativa (speculative decoding) (veja Decodificação Especulativa)

5) Artefato do modelo e gestão de runtime

Preocupações operacionais incluem:

  • Formatos de pesos do modelo (por exemplo, safetensors, checkpoints fragmentados (sharded checkpoints))
  • Quantização (quantization) (FP16/BF16 vs INT8/4-bit) (veja Otimização de Inferência)
  • Tempo de inicialização (cold start, download de pesos)
  • Versionamento e reversão (rollback) (veja Registro de Modelos)

6) Observabilidade e controles de confiabilidade

  • Métricas (utilização de GPU, memória, tokens/s, tempo de fila)
  • Logs (requisições, erros, eventos de truncamento)
  • Rastreamento (tracing) ao longo da pilha do aplicativo (veja Observabilidade para Aplicações com LLM)
  • Disjuntores (circuit breakers), descarte de carga (load shedding) e timeouts
  • Deploys canário (canary deploys) e estratégias seguras de rollout (veja CI/CD para Modelos)

Mecanismos de serviço na prática: vLLM e TGI

Tanto vLLM quanto TGI são projetados para inferência de transformadores (transformer inference) com alta vazão, mas fazem escolhas de design diferentes e expõem diferentes controles operacionais.

vLLM (alta vazão, design centrado no agendador)

vLLM é um mecanismo de serviço focado em maximizar a utilização de GPU para LLMs somente-decodificador (decoder-only). Ele é amplamente adotado porque combina:

  • Agrupamento contínuo para manter a GPU ocupada sob padrões diversos de requisição
  • Gerenciamento sofisticado do cache KV (uma grande alavanca de desempenho)
  • Forte ênfase em vazão sem abrir mão de latência em excesso
  • Ergonomia prática de implantação (comumente usado com um modo de API compatível com OpenAI)

Uma ideia-chave associada ao vLLM é o tratamento do cache KV “tipo paginação” (frequentemente descrito como “PagedAttention” na literatura do vLLM), que busca reduzir a fragmentação de memória e melhorar a utilização ao atender muitas requisições simultâneas com comprimentos de sequência variáveis.

Implicações operacionais:

  • Excelente para concorrência de múltiplas requisições e tráfego “estilo chat”.
  • Você ainda precisará ajustar contexto máximo, concorrência máxima e folga de memória para evitar OOM.
  • As escolhas do agendador podem mudar o equilíbrio entre TTFT e vazão.

TGI (Text Generation Inference; integração com o ecossistema HF voltada à produção)

TGI é um servidor de inferência desenvolvido originalmente no ecossistema Hugging Face para cargas de trabalho de geração de texto. Ele normalmente enfatiza:

  • Fortes recursos de produção (métricas, logging, padrões de containerização)
  • Geração eficiente com agrupamento em lote e kernels otimizados
  • Boa integração com fluxos comuns de distribuição de modelos (por exemplo, obter a partir de hubs de modelo, pesos fragmentados)

Implicações operacionais:

  • Frequentemente atraente se sua organização já está padronizada em ferramentas da Hugging Face.
  • A cobertura de funcionalidades (métodos de quantização, famílias de modelos, estratégias multi-GPU) depende da versão específica do TGI e da arquitetura do modelo.
  • A superfície de API pode diferir de endpoints “estilo OpenAI”; muitas equipes colocam um adaptador/gateway à frente.

Capacidades comuns (e onde elas diferem)

Ambos os sistemas tipicamente suportam:

  • Geração com streaming
  • Agrupamento em lote multi-requisição
  • Paralelismo de tensores / fragmentação (sharding) para modelos grandes (com restrições)
  • Métricas e endpoints de saúde (em graus variados)
  • Implantação em Docker/Kubernetes

Onde você verá diferenças práticas:

  • Comportamento de escalonamento sob carga (latência de cauda e justiça)
  • Estratégia de cache KV e eficiência de memória sob alta concorrência
  • Compatibilidade de modelo/quantização para sua família de modelos específica
  • Ergonomia operacional: opções de configuração, logs, limites padrão, cadência de upgrades
  • Aderência ao ecossistema: APIs compatíveis com OpenAI, integração com gateways, ou fluxos nativos da HF

Se possível, faça benchmark dos dois no seu workload (comprimentos de prompt, padrões de concorrência, latência-alvo), em vez de confiar em números genéricos.

Exemplos práticos: executando um servidor e chamando-o

Flags e imagens exatas mudam rapidamente entre releases, mas o padrão é estável: iniciar o servidor com um modelo e, então, enviar requisições HTTP.

Exemplo: iniciar um servidor vLLM compatível com OpenAI (estilo Docker)

docker run --gpus all --rm -p 8000:8000 \
  -e HF_TOKEN="$HF_TOKEN" \
  vllm/vllm-openai:latest \
  --model meta-llama/Meta-Llama-3-8B-Instruct \
  --dtype bfloat16 \
  --max-model-len 8192

Chame-o com uma requisição no estilo OpenAI:

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Meta-Llama-3-8B-Instruct",
    "messages": [{"role":"user","content":"Explain TTFT in one paragraph."}],
    "temperature": 0.2,
    "max_tokens": 120,
    "stream": false
  }'

Exemplo: iniciar um servidor TGI (estilo Docker) e gerar

docker run --gpus all --rm -p 8080:80 \
  -e HF_TOKEN="$HF_TOKEN" \
  ghcr.io/huggingface/text-generation-inference:latest \
  --model-id meta-llama/Meta-Llama-3-8B-Instruct \
  --dtype bfloat16 \
  --max-input-length 4096 \
  --max-total-tokens 8192

Um endpoint de geração comum no estilo TGI é:

curl http://localhost:8080/generate \
  -H "Content-Type: application/json" \
  -d '{
    "inputs": "Write a haiku about GPU memory.",
    "parameters": { "max_new_tokens": 60, "temperature": 0.7 }
  }'

Em produção, muitas equipes colocam um gateway de API (API gateway) à frente para normalizar formatos de requisição/resposta, impor cotas e centralizar autenticação.

Principais compensações operacionais

Serviço de LLM é, em grande parte, sobre navegar compensações. A resposta certa depende do formato do seu produto (UI de chat vs sumarização em lote), do seu orçamento de hardware e dos seus SLOs de latência.

Latência vs vazão

  • Estratégias de vazão máxima:
    • Lotes contínuos maiores/mais longos
    • Enfileiramento mais agressivo
    • Maior utilização de GPU
  • Estratégias de baixa latência:
    • Lotes menores, prazos de fila mais rígidos
    • Priorizar TTFT (especialmente para streaming)
    • Possivelmente reservar capacidade para tráfego interativo

Na prática, você normalmente escolhe uma política: por exemplo, “otimizar P95 de TTFT abaixo de 1,5s para até N usuários concorrentes”, e então aceitar um teto de vazão.

Memória vs comprimento de contexto (pressão do cache KV)

Contextos mais longos e mais requisições concorrentes aumentam a memória do cache KV. Sistemas de serviço frequentemente impõem:

  • max_model_len / max_total_tokens
  • Limites de concorrência
  • Limites por requisição baseados no comprimento do prompt

Se você tem OOM com frequência:

  • Reduza o contexto máximo ou a concorrência máxima
  • Use quantização / dtype menor quando for seguro
  • Considere paralelismo multi-GPU para pools de memória maiores (com sua própria complexidade)
  • Melhore o controle de admissão (admission control) (rejeitar ou adiar requisições caras)

A mecânica do cache KV é fundamental; veja Cache KV.

Flexibilidade vs eficiência (recursos dinâmicos custam dinheiro)

Funcionalidades comuns de produto adicionam overhead:

  • Chamadas de ferramentas / esquemas de chamada de função (function calling)
  • Restrições de saída JSON/estruturada
  • Prompts de sistema longos (política, guias de estilo)
  • n mais alto (múltiplos candidatos) ou logprobs
  • Max tokens muito alto “por via das dúvidas”

Esses recursos podem aumentar materialmente o custo e a latência de cauda. Trate-os como escolhas de design de API sensíveis a desempenho, não como “opções grátis”.

Tamanho do modelo vs complexidade da frota

  • Um único modelo grande simplifica o roteamento, mas é caro e difícil de escalar.
  • Um sistema em camadas (pequeno/rápido + fallback grande/preciso) pode reduzir custo, mas exige lógica de roteamento e avaliação.

Isso frequentemente é tratado na camada de aplicação com padrões descritos em Padrões de Design de Sistemas com LLM.

Compensações de quantização

A quantização pode melhorar:

  • Vazão (às vezes)
  • Pegada de memória (frequentemente)
  • Viabilidade (encaixar modelos maiores em GPUs menores)

Mas pode introduzir:

  • Regressões de acurácia (dependente da tarefa)
  • Restrições de compatibilidade (suporte de kernel/modelo)
  • Complexidade operacional (mais variantes de artefato)

Quantização faz parte de Otimização de Inferência e deve ser medida por benchmark em prompts representativos.

Multilocação vs isolamento

Executar múltiplos modelos (ou muitos usuários) na mesma frota de GPUs melhora a utilização, mas traz problemas:

  • Vizinhos ruidosos (noisy neighbors) (uma requisição com contexto longo eleva memória e latência)
  • Justiça no escalonamento
  • Cotas e cobrança por locatário
  • Segurança e privacidade nos logs (veja Privacidade em Logging)

Algumas equipes escolhem “um modelo por pool” (mais simples) enquanto outras usam um pool compartilhado com controle de admissão estrito (mais eficiente).

Padrões de implantação

Modelo único, GPU única (simples, limitado)

Melhor para:

  • Prototipação
  • Modelos pequenos
  • Baixa concorrência

Riscos:

  • Fácil atingir OOM com contextos longos
  • Sem redundância a menos que você rode múltiplas réplicas

Pods replicados atrás de um balanceador de carga (comum)

  • Múltiplas réplicas idênticas
  • Autoscaling horizontal baseado em utilização de GPU, tempo de fila ou taxa de requisições

Requisito-chave: manter as réplicas aquecidas (modelo carregado, kernels inicializados), ou cold starts destruirão a latência.

Serviço com paralelismo de tensores multi-GPU (necessário para modelos grandes)

Use quando:

  • Os pesos do modelo não cabem em uma GPU
  • Você precisa de maior folga para o cache KV

Compensações:

  • Maior complexidade (coordenação entre GPUs)
  • Escalabilidade mais frágil (uma “réplica” agora é um grupo de GPUs)
  • Falhas podem derrubar uma parcela maior da capacidade

Separar frotas por tipo de tráfego

Uma abordagem pragmática:

  • Pool interativo: ajustado para baixo TTFT, limites estritos
  • Pool em lote: ajustado para vazão, lotes maiores e enfileiramento mais longo

Isso se alinha com estratégias de controle de custo discutidas em Custo/Desempenho.

Observabilidade: o que medir em produção

Serviço de LLM é difícil de operar sem boa telemetria. No mínimo, instrumente:

  • Métricas de requisição
    • Tokens de entrada, tokens de saída, tokens totais
    • TTFT, latência total, latência por token (aprox.)
    • Tempo de fila
    • Taxas de rejeição/timeout e motivos (muito longo, sobrecarga, política)
  • Métricas do mecanismo
    • Tokens/s por GPU
    • Sequências ativas / concorrência
    • Uso do cache KV (alocado vs usado)
    • Contagem de OOM / indicadores de fragmentação de memória
  • Métricas de GPU/sistema
    • Utilização de GPU, memória, energia
    • Utilização de CPU (tokenização pode ser pesada em CPU)
    • Vazão de rede (especialmente para setups multi-GPU, multi-nó)

Conecte isso a alertas e SLOs (veja Monitoramento e SLOs para Funcionalidades de IA). Para aplicações com LLM de ponta a ponta, inclua rastreamento ao longo de recuperação/ferramentas/construção de prompt (veja Observabilidade para Aplicações com LLM).

Controles de confiabilidade e segurança na camada de serviço

Mesmo que sua aplicação tenha guardrails, a camada de serviço deve impor limites rígidos:

  • Comprimento máximo de entrada / máximo de tokens totais: evita crescimento de memória sem limites
  • Timeouts: a geração pode travar sob sobrecarga; imponha prazos
  • Descarte de carga: rejeite cedo quando saturado (melhor do que expirar mais tarde)
  • Backpressure: limites de fila; não aceite streams concorrentes infinitos
  • Modos determinísticos de falha: erros consistentes para “muito longo” e “sem capacidade”
  • Higiene de logging: evitar vazar prompts ou segredos (veja Privacidade em Logging)

Esses controles complementam padrões de nível mais alto como fallbacks e roteamento em Padrões de Design de Sistemas com LLM.

Como escolher entre vLLM e TGI (um critério prático)

Escolha com base nas suas restrições, não em preferência por marca:

  • Escolha vLLM quando você quer:

    • Forte vazão sob tráfego de comprimentos mistos e alta concorrência
    • Escalonamento moderno/eficiência de cache KV como ponto central de design
    • Um caminho fácil para serviço compatível com OpenAI para muitos aplicativos
  • Escolha TGI quando você quer:

    • Uma pilha de serviço que se encaixa naturalmente em fluxos centrados na Hugging Face
    • Empacotamento orientado à produção e uma pegada operacional bem conhecida
    • Um conjunto de funcionalidades que corresponda à sua família de modelos e às suas necessidades de geração

Em ambos os casos, o fator decisivo geralmente é benchmark sob carga realista:

  • Distribuição real de comprimentos de prompt
  • Concorrência real e padrões de rajada
  • Configurações reais de amostragem (temperature/top-p) e máximo de tokens
  • Comprimentos reais de contexto (pressão do cache KV)

Checklist de produção (alto sinal)

  • Planejamento de capacidade
    • Defina TTFT-alvo e latência P95
    • Faça benchmark de tokens/s por GPU para seu modelo + configurações
  • Limites
    • Limite prompt máximo, saída máxima e máximo de tokens totais
    • Limite concorrência e profundidade de fila
  • Escalonamento
    • Rode pelo menos 2 réplicas por modelo para redundância
    • Faça autoscaling com base no tempo de fila ou saturação, não apenas na taxa de requisições
  • Controles de custo
  • Observabilidade
    • Acompanhe TTFT, tempo de fila, tokens/s, uso do cache KV, OOMs
    • Adicione traces para decompor a latência ponta a ponta
  • Rollouts

No fim, o serviço de LLM trata de transformar as capacidades teóricas da Arquitetura Transformer em um serviço que seja rápido, previsível e econômico. Mecanismos como vLLM e TGI fornecem o runtime central, mas o verdadeiro sucesso vem da disciplina operacional: limites, escolhas de escalonamento, benchmarking cuidadoso e forte observabilidade.