Capítulo 6.3 — Como estimar capacidade e custo de um AI server¶
🎯 Objetivo¶
Dar ao leitor as ferramentas para estimar - antes de comprar GPU, fechar contrato com provider ou prometer SLA - quanto custa, em latência e dinheiro, servir um modelo. O foco é raciocínio, não receita de bolo: as fórmulas são simples e as métricas reais variam por hardware, kernel, batch e tipo de carga.
🧠 Conceito principal¶
Inferência de LLM tem duas fases com economias muito diferentes:
- Prefill - processa o prompt inteiro em paralelo. Custo dominado por compute (FLOPs em paralelo). Latência aqui domina o TTFT (Time To First Token).
- Decode - gera tokens um a um. Custo dominado por memory bandwidth (precisa ler todos os pesos a cada token). Latência aqui domina o TPOT (Time Per Output Token).
Por isso, custo e capacidade dependem fortemente de comprimento de prompt vs comprimento de resposta, batch e uso de KV cache.
🧮 Métricas essenciais¶
| Métrica | O que mede | Por que importa | Como otimizar |
|---|---|---|---|
| tokens/s (decode) | Throughput de geração | Capacidade real do servidor | Batching, paged attention, speculative decoding |
| TTFT (Time To First Token) | Latência do prefill (ms até o 1º token) | UX percebida em streaming | Prefill chunking, prompt caching, modelos menores |
| TPOT (Time Per Output Token) | Tempo entre tokens (ms/token) | Latência de resposta longa | Mais memory bandwidth, batching agregado, KV cache otimizado |
| Requests/s (RPS) | Taxa de requisições | Capacidade de throughput | Continuous batching, paged attention |
| Concurrency suportada | Quantas requisições simultâneas | Dimensionamento | Otimizações de KV cache e batching |
| p50/p95/p99 latency | Distribuição de latência | SLA real | Backpressure, queue policy, autoscaling |
| GPU utilization | % uso de SM/Tensor cores | Eficiência | Batching, kernel tuning |
| Memory bandwidth utilization | % uso de HBM | Eficiência em decode | Quantização, paged attention |
| VRAM usado | Memória em GPU | Sizing | Quantização, KV offload, sequences mais curtas |
| Cost per task / per success | Custo por unidade de valor | FinOps real | Routing, caching, batching, modelos menores |
| Cost per tenant | Custo por cliente | Chargeback | Rate limits, budgets, alocação |
| Cost per error / retry | Custo desperdiçado | Identifica desperdício | Eval, retries com backoff, fallback |
🧮 Fórmulas conceituais¶
Memória para pesos do modelo:
VRAM_pesos ≈ params × bits_per_param / 8
Exemplos rápidos:
- 7B em FP16:
7e9 × 2 = ~14 GB - 7B em INT8:
~7 GB - 7B em INT4:
~3.5 GB - 70B em FP16:
~140 GB(típico de tensor parallelism em várias GPUs)
KV cache por sequência:
KV_per_token ≈ 2 × num_layers × num_heads × head_dim × bytes_per_value
KV_per_sequence ≈ KV_per_token × seq_len
KV_total ≈ KV_per_sequence × concurrency
Para um modelo 7B FP16 com 32 camadas, 32 cabeças, head_dim 128, 4 KB/token é uma ordem de grandeza razoável. Em 4096 tokens de janela e 32 sequências simultâneas, a KV cache pode passar facilmente de dezenas de GB, o que explica porque batching e paged attention são tão críticos.
Tokens totais por tarefa:
tokens_tarefa = tokens_prompt + tokens_resposta + overhead_tools + retries
Em RAG com tools, overhead pode dominar: cada tool call adiciona prompt + result no contexto da próxima chamada.
Custo por tarefa (API provider):
custo_tarefa ≈ tokens_prompt × preço_in + tokens_resposta × preço_out
+ outras_chamadas (embedding, reranker)
+ tokens_retries × p(falha)
Custo por tarefa (self-hosting):
custo_tarefa ≈ (custo_GPU_hora / 3600)
× (tokens_prompt / throughput_prefill
+ tokens_resposta / throughput_decode)
Concorrência suportada (aproximada):
concurrency ≈ throughput_decode_total / tokens_resposta_por_segundo_aceitos
Se o servidor entrega 2000 tokens/s no decode e cada usuário gera ~50 tokens/s perceptíveis, ele atende ~40 sessões simultâneas em decode.
Aviso. Estas fórmulas são pedagógicas. Os valores reais dependem de hardware, framework (vLLM, TensorRT-LLM, TGI, llama.cpp), formato (FP16/FP8/INT4), tamanho de prompt, padrão de chegada de requisições e heurísticas de scheduling. Use-as para ordens de grandeza, depois meça em ambiente equivalente ao de produção.
🧠 Por que reduzir VRAM nem sempre aumenta throughput¶
Cair de FP16 para INT4 corta pesos em ~4x, mas:
- decode é limitado por memory bandwidth - se o kernel INT4 não desempacota eficientemente, o ganho some;
- prefill é limitado por compute - INT4 pode até piorar se o kernel precisa de upcast frequente;
- batching tende a ser mais eficiente em FP16 com Tensor Cores dedicados;
- speculative decoding pode dar mais ganho que quantização agressiva, em cenários adequados.
Conclusão: meça, não suponha. VRAM cai sempre; throughput, depende.
🚀 Técnicas-chave de serving moderno¶
| Técnica | O que faz | Onde aparece |
|---|---|---|
| Continuous batching | Adiciona novas sequências ao batch a cada step, em vez de esperar batch fechar | vLLM, TGI, TensorRT-LLM in-flight batching |
| PagedAttention | KV cache fragmentado em blocos paginados, evita fragmentação e habilita batch grande | vLLM |
| Paged KV cache | Mesma ideia, exposta em outros engines | TensorRT-LLM, SGLang |
| Speculative decoding | Draft model rápido propõe tokens; modelo grande verifica em paralelo | vLLM, TensorRT-LLM, Medusa, Lookahead |
| Prompt caching | Reaproveita KV cache de prefixos estáveis | OpenAI, Anthropic, vLLM (prefix caching) |
| Semantic caching | Reaproveita respostas para queries similares | Gateways (Portkey, Kong AI, LiteLLM); usar com cuidado |
| Streaming | Entrega tokens à medida que decodam | Reduz TTFT percebido; não muda throughput |
| Chunked prefill | Quebra prefill em pedaços para não bloquear decode | vLLM e similares |
| Tensor parallelism | Divide o modelo em várias GPUs por tensor | Para modelos que não cabem em uma GPU |
| Pipeline parallelism | Divide camadas em GPUs sequenciais | Modelos muito grandes |
| CPU offload | KV cache ou pesos em RAM | Permite rodar maior em hardware menor; perde latência |
| Disaggregated prefill/decode | Separa em servidores especializados | Reduz contenção; complica orquestração |
🏗️ Self-hosting vs API provider¶
| Eixo | API provider (OpenAI, Anthropic, Bedrock, Vertex) | Self-hosting (vLLM, TGI, TensorRT-LLM) |
|---|---|---|
| Custo inicial | Baixo (paga por uso) | Alto (GPUs, equipe) |
| Custo em escala | Cresce linearmente | Pode ficar significativamente menor com utilização alta |
| Latência | Depende do provider e região | Controlável, mais previsível |
| Capacidade | Limitada por rate limits e quotas | Limitada por hardware |
| Modelos disponíveis | Catálogo do provider | Qualquer modelo open-weight compatível |
| Conformidade / residência | Depende do contrato | Total controle |
| Manutenção | Zero | Operação contínua de cluster |
| Sensibilidade a falha do provedor | Alta | Sob seu controle |
| Otimização | Limitada (provider faz por você) | Total (kernel, batch, prompt caching) |
Regra de bolso: tráfego pequeno e variável -> API provider. Tráfego alto e previsível -> self-hosting tende a compensar; o break-even depende de utilização real.
🚨 Modos de falha¶
- Definir SLA com base em média, não em p95/p99.
- Sizing baseado em VRAM dos pesos, ignorando KV cache.
- Esquecer overhead de tool calls em agentes (multiplica tokens).
- Subestimar custo de retries em pico de erro.
- Esquecer custos de embedding, reranker, gateway e observabilidade.
- Não isolar concurrency por tenant.
🛡️ Controles e mitigações¶
- Benchmarks com perfis realistas (mistura de tamanhos de prompt e resposta, picos, multi-tenant).
- Métricas em dois eixos: latência (TTFT/TPOT/p95/p99) e custo (por tarefa, por sucesso, por tenant).
- Autoscaling baseado em fila + memory bandwidth, não só CPU.
- Backpressure explícito: rejeitar antes de degradar todos os clientes.
- Budgets por tenant + alertas + circuit breakers.
📌 Checklist para sizing de AI server¶
- [ ] Existem números medidos (não estimados) para TTFT, TPOT, RPS no hardware-alvo?
- [ ] KV cache foi considerado no sizing de VRAM?
- [ ] Perfis de carga realistas (prompt + tools + retries) foram testados?
- [ ] SLA é em p95/p99 e em custo por sucesso?
- [ ] Comparação self-host vs API foi feita com utilização realista?
- [ ] Estão claros os modos de degradação sob pico (timeout, queue, reject)?
🧰 Exemplos práticos relacionados (planejados)¶
EX-LLM-11- load test sintético com vLLM medindo TTFT/TPOT/p95.EX-COST-03- calculadora de custo por sucesso (provider vs self-host).
📚 Referências¶
- vLLM - PagedAttention e continuous batching: https://docs.vllm.ai/en/latest/
- vLLM - Architecture overview: https://docs.vllm.ai/en/latest/design/arch_overview.html
- TensorRT-LLM - In-flight batching e paged KV cache: https://nvidia.github.io/TensorRT-LLM/
- Hugging Face TGI - Continuous batching: https://huggingface.co/docs/text-generation-inference/conceptual/streaming
- Leviathan et al. - Fast Inference from Transformers via Speculative Decoding: https://arxiv.org/abs/2211.17192
- Pope et al. - Efficiently Scaling Transformer Inference: https://arxiv.org/abs/2211.05102
- OpenAI - Latency optimization: https://developers.openai.com/api/docs/guides/latency-optimization
- Anthropic - Prompt caching: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- BentoML - Prefill–decode disaggregation: https://bentoml.com/llm/inference-optimization/prefill-decode-disaggregation