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

  1. 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.
  2. 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.
  3. 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).
  4. Pressão de memória

    • OOM força lotes menores → menor intensidade aritmética → pior utilização.
    • Paginação/offloading podem introduzir travamentos.
  5. 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:

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

  1. 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
  2. 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)
  3. 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)
  4. 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)
  5. 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

  1. Batching + escalonamento inteligente

    • Batching contínuo geralmente traz ganhos grandes
    • Controle de admissão para evitar colapso de latência de cauda
  2. 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)
  3. 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)
  4. Cache

    • Faça cache de prompts repetidos, embeddings ou resultados intermediários
    • Use caches semânticos onde fizer sentido
      (veja Cache & Rate Limiting)
  5. 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 é:

  1. Defina a unidade que você se importa (tokens/s, $/1M tokens, $/época, latência p95 em QPS).
  2. Meça para onde vão tempo e memória (profilers + métricas de sistema).
  3. Classifique o gargalo: computação, largura de banda de memória, capacidade de memória, comunicação, pipeline de dados ou enfileiramento.
  4. 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
  5. 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.