Custo/desempenho
Visão geral
“Custo/desempenho” em sistemas de IA é a disciplina de maximizar trabalho útil do modelo por dólar enquanto atende a restrições do produto como latência, qualidade e confiabilidade. Na prática, isso se resume a algumas quantidades mensuráveis:
- Utilização: quanto da capacidade de computação e da largura de banda de memória da sua GPU/TPU você está realmente usando.
- Vazão (throughput): quanto trabalho você conclui por unidade de tempo (por exemplo, tokens/s, exemplos/s, imagens/s).
- Custo unitário: dólares por unidade de trabalho (por exemplo, $/1M tokens servidos, $/época, $/parâmetro treinado).
O objetivo raramente é “minimizar custo” de forma isolada. Em geral, é:
atingir um SLO e um nível de qualidade pelo menor custo sustentável (veja SLOs para Funcionalidades de IA).
Este artigo foca nos principais direcionadores de custo e alavancas de desempenho para treinamento e serving, com uma visão centrada em GPU/TPU.
Conceitos centrais e métricas
Vazão vs latência vs utilização
- Latência: tempo por requisição (p50/p95/p99). Inferência online voltada ao produto frequentemente tem um requisito rígido de latência de cauda.
- Vazão: requisições/s, tokens/s, exemplos/s. Jobs em lote priorizam vazão.
- Utilização: quão “ocupado” o hardware está. Alta utilização frequentemente se correlaciona com boa eficiência de custo, mas pode prejudicar a latência de cauda se as filas ficarem longas.
Uma tensão comum:
- Maximizar a vazão (via lotes grandes, alta concorrência) aumenta a utilização, mas pode aumentar a latência.
- Minimizar a latência (lotes pequenos, pouco enfileiramento) pode reduzir a utilização e aumentar $/unidade de trabalho.
Custo como “economia unitária”
Custos unitários úteis incluem:
- Treinamento
- $/passo, $/época, $/token treinado
- “Tempo até qualidade” (quão rápido você atinge a perda/acurácia-alvo)
- Serving
- $/requisição
- $/1K tokens (APIs de LLM frequentemente cotam isso)
- $/imagem, $/minuto de áudio, etc.
Uma forma simples de raciocinar sobre o custo unitário de serving:
[ \text{custo por token} \approx \frac{\text{$ por hora por acelerador}}{\text{tokens por segundo} \times 3600} ]
Isso ignora CPU, RAM, rede e overhead, mas é uma boa aproximação de primeira ordem.
Limitado por computação vs limitado por memória
Muitas cargas de trabalho de IA são limitadas por um destes:
- Limitado por computação (compute-bound): você não consegue alimentar matemática rápido o suficiente (FLOPs). A utilização é dominada por atividade de SM/ALU.
- Limitado por memória (memory-bound): você não consegue mover dados rápido o suficiente (largura de banda de HBM) ou fica sem memória (capacidade de HBM).
Transformers (Arquitetura Transformer) frequentemente se comportam assim:
- Treinamento: frequentemente pesado em computação, mas a memória de ativações e o estado do otimizador podem dominar a capacidade.
- Serving: frequentemente limitado por largura de banda de memória e capacidade de memória devido ao cache KV (KV cache).
Entender qual é o gargalo diz quais otimizações importam.
Métricas de utilização de hardware que você deve conhecer
Você verá estas em profilers e dashboards:
- Utilização de SM da GPU: percentual do tempo em que as unidades de computação estão ativas.
- Utilização de largura de banda de HBM: percentual do pico de largura de banda de memória atingido.
- Utilização de Tensor Core / MMA (NVIDIA): se você está usando unidades rápidas de matriz (frequentemente ligado ao uso de FP16/BF16/FP8).
- MFU (Utilização de FLOPs do Modelo, Model FLOPs Utilization): FLOPs atingidos / pico teórico de FLOPs. Popular em treinamento de LLM para quantificar eficiência.
- Utilização de TPU: frequentemente reportada como utilização de MXU mais estatísticas do pipeline de entrada; a eficiência de compilação do XLA é um fator.
Ressalva de interpretação: 90% de “utilização de GPU” em uma métrica genérica ainda pode significar baixa eficiência se os kernels forem ineficientes ou limitados por memória.
Utilização de GPU/TPU: o que realmente impede 100%
Causas comuns de subutilização
Gargalos no pipeline de entrada
- Carregamento de dados, tokenização, aumento de dados, leituras de armazenamento remoto.
- Sintomas: lacunas de ociosidade da GPU entre passos; CPU saturada.
- Correções: prefetch, cache, workers paralelos, datasets empacotados, formatos binários.
Ineficiência de kernel / fusão ruim
- Muitos kernels pequenos, lançamentos excessivos, operações não fundidas.
- Particularmente doloroso para tamanhos de lote pequenos ou grafos de modelo complexos.
Overhead de comunicação em treinamento distribuído
- All-reduce, all-gather, bolhas de pipeline.
- O gargalo muda de computação para rede (NVLink/InfiniBand/Ethernet).
Pressão de memória
- OOM força lotes menores → menor intensidade aritmética → pior utilização.
- Paginação/offloading podem introduzir travamentos.
Fragmentação em tempo de serving
- Muitas requisições pequenas e irregulares reduzem a eficiência de batching.
- Crescimento do cache KV reduz a capacidade efetiva e força menor concorrência.
“Primeiras checagens” práticas
- O acelerador está esperando por dados?
- Você está usando precisão mista (mixed precision) (BF16/FP16/FP8 onde for seguro)?
- O job está gastando muito tempo em comunicação?
- A memória está limitando lote/concorrência?
Essas perguntas orientam se você deve otimizar CPU/dados, kernels, estratégia distribuída ou memória.
Direcionadores de custo de treinamento
O custo de treinamento é tipicamente dominado por horas de acelerador, mas vários fatores de segunda ordem importam porque mudam quantas horas de acelerador você precisa para atingir uma qualidade-alvo.
1) Computação total necessária (FLOPs)
Para redes profundas, a computação de treinamento escala aproximadamente com:
- tamanho do modelo (parâmetros)
- número de tokens/exemplos treinados
- detalhes de arquitetura
Para Transformers, uma estimativa “de guardanapo” é que os FLOPs de treinamento escalam linearmente com tokens e parâmetros. A constante exata depende da implementação e da variante de atenção, mas o principal ponto é:
- Mais tokens e modelos maiores aumentam o custo linearmente.
- Chegar a uma determinada qualidade frequentemente depende do orçamento de computação, consistente com comportamento empírico de escalamento.
Por isso, curadoria de dataset e critérios de parada podem ser alavancas de custo, não apenas escolhas de ML (veja também Avaliação em Produção para ciclos de melhoria contínua).
2) Tamanho de lote efetivo e tempo de passo
A vazão de treinamento é frequentemente medida como:
- tokens/s
- sequências/s
- exemplos/s
E o tempo de passo é afetado por:
- tamanho do lote (micro-lote e lote global)
- comprimento de sequência (custos quadráticos de atenção, a menos que use atenção eficiente)
- otimizador (AdamW é mais pesado que SGD; o estado do otimizador pode dominar a memória)
Lotes maiores geralmente melhoram a vazão até você atingir limites de memória ou retornos decrescentes, mas podem mudar a dinâmica de otimização (veja Descida do Gradiente).
3) Overhead de treinamento distribuído
Estratégias distribuídas incluem:
- Paralelismo de dados (data parallelism): replicar o modelo, dividir o lote.
- Paralelismo de tensor/modelo (tensor/model parallelism): dividir camadas/pesos entre dispositivos.
- Paralelismo de pipeline (pipeline parallelism): dividir camadas em estágios.
Problemas de custo/desempenho:
- Tempo de all-reduce cresce com o tamanho do modelo e a contagem de dispositivos, limitado pelo interconector.
- Bolhas de pipeline reduzem a utilização quando estágios esperam.
- Desbalanceamento (um estágio mais lento) estrangula o sistema inteiro.
Regra prática: escalar para mais dispositivos só ajuda se você mantiver alta utilização e não transformar o job em um benchmark de comunicação.
4) Precisão e numérica
Usar BF16/FP16 frequentemente:
- aumenta o uso de Tensor Cores
- reduz o footprint de memória
- aumenta a vazão
FP8 pode aumentar ainda mais a vazão em GPUs recentes, mas pode exigir ajuste cuidadoso.
No entanto, instabilidade ou picos de loss podem aumentar o custo total ao forçar reexecuções ou taxas de aprendizado menores. A execução mais barata é a que converge de forma confiável.
5) Checkpointing e tolerância a falhas
Checkpointing afeta o custo via:
- overhead de I/O (escrita de checkpoints grandes)
- eficiência de restart (com que frequência você perde progresso)
- custos de armazenamento
Em clusters grandes, falhas são comuns o suficiente para que checkpointing robusto seja uma funcionalidade de custo, não um detalhe posterior. Isso se conecta com Treinamento Reprodutível (Configs, Artefatos) e Rastreamento de Experimentos.
Exemplo de treinamento: estimando custo por hora de GPU
Suponha:
- 8 GPUs a $3,00/GPU-hora (ordem de grandeza aproximada de cloud on-demand)
- treinamento leva 48 horas de relógio
Então:
Total GPU-hours = 8 * 48 = 384 GPU-hours
Accelerator cost ≈ 384 * $3.00 = $1,152
Se você melhorar a vazão em 25% (mesmo número de passos para convergência, passos mais rápidos), o tempo de relógio cai para 38,4 horas:
New GPU-hours = 8 * 38.4 = 307.2
New cost ≈ 307.2 * $3.00 = $921.60
Savings ≈ $230.40 (20%)
Um ganho de 25% em vazão gera ~20% de redução de custo aqui porque a contagem de dispositivos é fixa e você está reduzindo tempo.
Direcionadores de custo de serving (especialmente para LLMs)
Serving tem uma economia diferente porque:
- o tráfego varia (ciclos diurnos, picos)
- latência de cauda importa
- muitos custos são impulsionados por capacidade de memória e concorrência, não apenas FLOPs
Veja Serviço de Modelos e Serving de LLM para contexto em nível de sistema.
1) Tamanho do modelo e footprint de memória
Serving requer:
- pesos (weights) (frequentemente dominante para contextos menores)
- cache KV para decodificação autorregressiva (frequentemente dominante em contextos mais longos e maior concorrência)
O cache KV cresce com:
- número de camadas
- tamanho oculto
- comprimento de contexto (prompt + tokens gerados)
- concorrência (número de sequências em andamento)
- precisão (FP16 vs FP8 vs INT8 no KV)
Isso significa que duas requisições com contagens de tokens idênticas podem ter custos diferentes dependendo de concorrência e contexto.
2) Vazão em tokens/s vs “goodput”
A decodificação de LLM é iterativa: um forward pass por token gerado (a menos que use métodos especulativos). Tokens/s depende de:
- tamanho do lote (quantas sequências você decodifica simultaneamente)
- comprimento médio de saída
- implementação de atenção
- largura de banda do cache KV
- escalonamento (batching contínuo)
Uma métrica-chave de serving é goodput (vazão efetiva): tokens entregues com sucesso dentro dos alvos de latência. Você pode ter alta média de tokens/s mas p99 ruim se as filas não forem gerenciadas.
3) Batching e escalonamento
Batching melhora a utilização ao transformar muitas requisições pequenas em menos multiplicações de matriz grandes.
- Batching estático (static batching): agrupa requisições em intervalos fixos (pode adicionar latência).
- Batching dinâmico/contínuo (dynamic/continuous batching) (comum em servidores de LLM): mescla continuamente passos de decodificação entre requisições para maximizar a ocupação da GPU.
Trade-off:
- Mais batching → melhor vazão e custo
- Mais batching → potenciais problemas de latência e justiça (fairness)
Servidores modernos de LLM (por exemplo, arquiteturas do tipo vLLM) também otimizam o gerenciamento do cache KV; veja Serving de LLM.
4) Padrões de tráfego e autoscaling
Você paga por capacidade provisionada, não pela demanda média.
Direcionadores de custo:
- superprovisionamento para tráfego de pico
- cold starts / tempos de carregamento de modelo (modelos grandes podem levar minutos para carregar)
- redundância multi-região para disponibilidade
Abordagem prática:
- definir SLOs (latência p95, taxa de erro)
- dimensionar capacidade para o pico ou aplicar modelagem de requisições e fallbacks (veja Cache & Rate Limiting e Padrões de Design de Sistemas com LLM).
5) Overhead de CPU, rede e orquestração
Para serving online, GPUs são caras, mas não são o único custo:
- CPU para tokenização, roteamento, filtros de segurança, chamadas de ferramentas
- egress de rede (especialmente para streaming de tokens)
- overhead de observabilidade (registrar prompts/respostas pode ser caro — veja Privacidade em Logging)
- overhead do Kubernetes, fragmentação e headroom desperdiçado
Exemplo de serving: custo por 1M tokens
Assuma que uma GPU custa $2,50/hora e você atinge 300 tokens/s em regime permanente (incluindo seu batching e concorrência típicos).
Tokens per hour = 300 * 3600 = 1,080,000 tokens
Cost per 1M tokens ≈ $2.50 / 1.08 ≈ $2.31 per 1M tokens
Se restrições de latência obrigarem você a operar com lotes menores e você só conseguir 180 tokens/s:
Tokens per hour = 648,000
Cost per 1M tokens ≈ $2.50 / 0.648 ≈ $3.86 per 1M tokens
Por isso, “requisitos de latência” são diretamente “requisitos de custo unitário”.
Alavancas práticas de otimização
Treinamento: alavancas de maior ROI
Corrija o pipeline de entrada
- Pré-tokenize e armazene em formatos eficientes
- Aumente workers do dataloader, prefetch, pinned memory
- Faça cache local de shards quentes
Use precisão mista de forma efetiva
- BF16 é um padrão estável comum em hardware moderno
- Garanta que operações críticas mantenham precisão suficiente (loss scaling se necessário)
Aumente a intensidade aritmética
- Lotes maiores (se a otimização permitir)
- Empacotamento de sequências para dados de comprimento variável (reduz desperdício de padding)
Reduza gargalos de comunicação
- Use interconectores mais rápidos onde isso importa
- Sobreponha comunicação com computação
- Reavalie a estratégia de paralelismo (dados vs tensor vs pipeline)
Meça MFU e o detalhamento do passo
- Faça profiling antes e depois das mudanças
- Otimize o maior contribuinte, não a métrica mais “barulhenta”
Serving: alavancas de maior ROI
Batching + escalonamento inteligente
- Batching contínuo geralmente traz ganhos grandes
- Controle de admissão para evitar colapso de latência de cauda
Dimensione corretamente contexto e saídas
- Limite o máximo de tokens de saída quando o produto permitir
- Use recuperação (retrieval) para evitar encher prompts enormes (com avaliação cuidadosa)
Quantização e modelos menores
- Quantização de pesos INT8/FP8 pode aumentar vazão e reduzir memória
- Destilação ou variantes menores reduzem latência e custo
(veja Otimização de Inferência)
Cache
- Faça cache de prompts repetidos, embeddings ou resultados intermediários
- Use caches semânticos onde fizer sentido
(veja Cache & Rate Limiting)
Padrões de design de sistema
- Cascatas/roteadores: modelo barato primeiro, modelo caro como fallback
- Decodificação especulativa (speculative decoding): modelo rascunho propõe tokens; alvo verifica (quando suportado)
- Modos degradados (degraded modes) sob carga (saídas mais curtas, modelo de menor qualidade)
(veja Padrões de Design de Sistemas com LLM)
Planejamento de capacidade e dashboards que importam
Principais dashboards de treinamento
Acompanhe:
- detalhamento do tempo de passo (forward/backward/otimizador/comunicação/dados)
- tokens/s, TFLOPs/s atingidos, MFU
- utilização de memória da GPU e frequência de OOM
- latência do dataloader e utilização de CPU do host
- tempo de checkpoint e taxas de falha/restart
Principais dashboards de serving
Acompanhe:
- tokens/s por réplica e por GPU
- concorrência de requisições, profundidade de fila e rejeições por admissão
- latência p50/p95/p99 (tempo até o primeiro token e tempo por token de saída para LLMs)
- uso de memória da GPU (especialmente cache KV)
- taxas de erro e timeouts
- custo por requisição bem-sucedida / custo por 1K tokens
Isso se conecta naturalmente com Monitoramento e Observabilidade para Apps de LLM.
Armadilhas comuns e anti-padrões
- Otimizar utilização sem proteger a latência de cauda: alta utilização média pode esconder colapso de enfileiramento no p99.
- Ignorar desperdício de padding: lotes com comprimento variável podem reduzir silenciosamente a vazão efetiva pela metade.
- Perseguir FLOPs de pico: muitas cargas são limitadas por memória; kernels melhores não ajudam se a largura de banda do cache KV for o limite.
- Subestimar “tempo até qualidade”: um passo mais rápido que piora a convergência pode aumentar o custo total.
- Sem economia unitária: se você não consegue calcular $/1M tokens ou $/época, não consegue priorizar trabalho.
- Logar tudo: armazenar prompts/respostas completos em escala pode se tornar um custo grande (e um risco de privacidade).
Juntando tudo: um fluxo de decisão
Quando custo/desempenho está ruim, um fluxo prático é:
- Defina a unidade que você se importa (tokens/s, $/1M tokens, $/época, latência p95 em QPS).
- Meça para onde vão tempo e memória (profilers + métricas de sistema).
- Classifique o gargalo: computação, largura de banda de memória, capacidade de memória, comunicação, pipeline de dados ou enfileiramento.
- Aplique a correção de maior alavancagem:
- Treinamento: pipeline de entrada, precisão mista, batching/packing, estratégia distribuída
- Serving: batching/escalonamento, quantização, limites de contexto/saída, cache, cascatas
- Valide qualidade e SLOs (não “economize dinheiro” quebrando o produto).
Custo/desempenho é uma competência contínua de MLOps: à medida que modelos, tráfego e hardware evoluem, o ponto ótimo muda. As melhores equipes tratam isso como um loop de engenharia mensurável e iterativo — fortemente integrado com avaliação, arquitetura de serving e práticas de confiabilidade.