Otimização de Custos para Sistemas de LLM: Para Onde o Dinheiro Realmente Vai

Gaste tokens onde realmente importam.

Conteúdo da página

Os custos dos LLMs (Modelos de Linguagem de Grande Escala) escalam linearmente com o uso. Um sistema que processa 10.000 solicitações por dia a US$ 0,01 por solicitação custa US$ 100 diariamente — US$ 365 por ano. Em escala empresarial, isso ultrapassa US$ 10.000.

A otimização de custos não se trata de cortar caminho. Trata-se de gastar tokens onde eles realmente importam.

Cada token que você desperdiça é um token que poderia ter sido gasto em uma resposta melhor.

Estratégias de otimização de custos de LLM

Orçamento de tokens

A maneira mais simples de controlar os custos é definir limites. Por sessão, por tarefa ou por dia.

Estratégia 1: Orçamentos por Sessão

Os orçamentos por sessão são diretos:

class SessionBudget:
    def __init__(self, budget_tokens: int = 10000):
        self.budget = budget_tokens
        self.used = 0

    def allocate(self, tokens: int) -> bool:
        if self.used + tokens <= self.budget:
            self.used += tokens
            return True
        return False

    def remaining(self) -> int:
        return self.budget - self.used

Estratégia 2: Orçamentos por Tarefa

Os orçamentos por tarefa são mais úteis. Diferentes tarefas precisam de quantidades diferentes de contexto:

task_budgets:
  classify:
    max_tokens: 100
    model: qwen2.5-1.5b
  summarize:
    max_tokens: 500
    model: qwen2.5-7b
  code_review:
    max_tokens: 2000
    model: qwen2.5-coder-7b
  reason:
    max_tokens: 4000
    model: qwen2.5-32b

Estratégia 3: Orçamentos Adaptativos

Os orçamentos adaptativos ajustam-se com base no que realmente acontece. Se as tarefas de classificação consistentemente usam 80 tokens, pare de alocar 100:

class AdaptiveBudget:
    def __init__(self):
        self.task_history = {}

    def allocate(self, task_type: str) -> int:
        if task_type in self.task_history:
            return int(self.task_history[task_type] * 1.5)
        return 1000

    def record(self, task_type: str, tokens_used: int):
        if task_type not in self.task_history:
            self.task_history[task_type] = tokens_used
        else:
            self.task_history[task_type] = (
                0.9 * self.task_history[task_type] + 0.1 * tokens_used
            )

A média móvel exponencial (peso de 0,9) significa que o uso recente importa mais do que o histórico. Ajuste o peso com base na volatilidade das suas cargas de trabalho.

Inferência via API vs. local

A inferência local é mais barata em escala. O ponto de equilíbrio depende do seu hardware e das tarifas da API.

Modelo API (US$/M tokens) Custo local/hora Ponto de equilíbrio
GPT-4o US$ 2,50 / US$ 10,00 N/A
Claude Sonnet 4 US$ 3,00 / US$ 15,00 N/A
Qwen2.5-72B US$ 0,50 / US$ 2,00 ~US$ 0,50 ~4 horas/dia
Qwen2.5-32B US$ 0,30 / US$ 1,20 ~US$ 0,20 ~2 horas/dia
Qwen2.5-7B US$ 0,10 / US$ 0,40 ~US$ 0,05 ~1 hora/dia

A matemática do hardware:

Hardware Investimento inicial Eletricidade mensal Ponto de equilíbrio vs API
RTX 3090 (usada) US$ 600 US$ 15 ~4 meses
RTX 4090 US$ 1.500 US$ 20 ~6 meses
RTX 5080 US$ 1.000 US$ 18 ~5 meses
DGX Spark US$ 2.000 US$ 30 ~8 meses

Com um uso moderado — uma hora ou mais por dia — a inferência local se paga sozinha. Com alto uso, as economias são dramáticas. O problema é o capital inicial. Uma RTX 5080 custa US$ 1.000. Uma conta de API você pode pausar. Hardware, não.

Estratégias de fallback (recuo)

Quando seu modelo preferido é muito caro ou muito lento, recua para algo mais barato. A chave é saber quando a qualidade é “boa o suficiente”.

Estratégia 1: Fallback Baseado em Qualidade

O fallback baseado em qualidade tenta modelos até que a saída atinja um limite:

class QualityFallback:
    def __init__(self, quality_threshold: float = 0.8):
        self.threshold = quality_threshold
        self.models = [
            {"model": "claude-sonnet-4", "cost": 0.015},
            {"model": "qwen2.5-72b", "cost": 0.002},
            {"model": "qwen2.5-32b", "cost": 0.001},
            {"model": "qwen2.5-7b", "cost": 0.0004},
        ]

    def route(self, prompt: str) -> str:
        for model_config in self.models:
            result = self.call_model(model_config["model"], prompt)
            if self.evaluate_quality(result) >= self.threshold:
                return result
        return self.call_model(self.models[0]["model"], prompt)

O problema é a própria avaliação. Como você mede a qualidade sem chamar outro modelo? Alguns sistemas usam um classificador pequeno. Outros usam verificações heurísticas — comprimento, estrutura, presença de palavras-chave. Nenhum desses é perfeito.

Estratégia 2: Fallback Baseado em Latência

O fallback baseado em latência é mais simples. Encaminhe para o modelo mais rápido que atenda ao seu orçamento de tempo:

class LatencyFallback:
    def __init__(self, max_latency: float = 5.0):
        self.max_latency = max_latency
        self.models = [
            {"model": "qwen2.5-1.5b", "latency": 0.5},
            {"model": "qwen2.5-7b", "latency": 2.0},
            {"model": "qwen2.5-32b", "latency": 10.0},
            {"model": "claude-sonnet-4", "latency": 5.0},
        ]

    def route(self, prompt: str) -> str:
        for model_config in sorted(self.models, key=lambda x: x["latency"]):
            if model_config["latency"] <= self.max_latency:
                return self.call_model(model_config["model"], prompt)
        return self.call_model(self.models[0]["model"], prompt)

Cache (Armazenamento em Cache)

O cache é a otimização de custos mais subestimada. Prompts idênticos acontecem mais frequentemente do que você pensa — solicitações de classificação, consultas estilo FAQ, chamadas de ferramentas repetidas.

Estratégia 1: Cache de Prompts

O cache de prompts exatos é simples:

import hashlib

class PromptCache:
    def __init__(self, max_size: int = 1000):
        self.cache = {}
        self.max_size = max_size

    def get(self, prompt: str) -> str | None:
        key = hashlib.sha256(prompt.encode()).hexdigest()
        return self.cache.get(key)

    def set(self, prompt: str, response: str):
        key = hashlib.sha256(prompt.encode()).hexdigest()
        if len(self.cache) >= self.max_size:
            self.cache.pop(next(iter(self.cache)))
        self.cache[key] = response

Estratégia 2: Cache Semântico

O cache semântico é mais útil. Ele captura prompts que são diferentes, mas significam a mesma coisa:

from sentence_transformers import SentenceTransformer

class SemanticCache:
    def __init__(self, similarity_threshold: float = 0.95):
        self.model = SentenceTransformer('all-MiniLM-L6-v2')
        self.cache = {}
        self.threshold = similarity_threshold

    def get(self, prompt: str) -> str | None:
        prompt_embedding = self.model.encode([prompt])[0]
        for cached_prompt, cached_response in self.cache.items():
            cached_embedding = self.model.encode([cached_prompt])[0]
            similarity = self.cosine_similarity(
                prompt_embedding, cached_embedding
            )
            if similarity >= self.threshold:
                return cached_response
        return None

    def set(self, prompt: str, response: str):
        self.cache[prompt] = response

O limite é importante. 0,95 é agressivo — apenas prompts muito semelhantes são correspondidos. 0,85 é mais tolerante, mas corre o risco de retornar respostas erradas. Meça sua taxa de erro (miss rate) e ajuste.

Armazenar em cache as respostas para consultas comuns também vale a pena. Se os usuários perguntarem repetidamente “qual é o clima” ou “que horas são”, armazene o padrão em cache, não apenas o prompt exato:

class ResponseCache:
    def __init__(self):
        self.common_queries = {
            "what is the weather": "Check weather API",
            "what is the time": "Check system time",
            "who is the president": "Check current president",
        }

    def get(self, query: str) -> str | None:
        query_lower = query.lower()
        for common_query, response in self.common_queries.items():
            if common_query in query_lower:
                return response
        return None

Isso não é sofisticado, mas funciona. Consultas comuns são comuns por um motivo.

Quando a otimização ajuda

A otimização importa quando você está processando grandes volumes, executando cargas de trabalho mistas ou pagando custos de API que se somam.

Não importa quando você está criando protótipos, usando um único modelo ou processando baixos volumes. A complexidade do orçamento, fallback e cache não vale a pena para um sistema que faz 100 solicitações por dia.

Primeiro, faça o fluxo básico funcionar. Adicione otimização quando a conta chegar.

Compromissos (Trade-offs)

Estratégia Custo Qualidade Complexidade
Sem otimização Mais alto Consistente Mais baixo
Orçamento de tokens Moderado Variável Média
Modelos de fallback Baixo-Médio Variável Média
Cache Mais baixo Alta (para acertos de cache) Média
Híbrido Otimizado Otimizado Mais alto

Sistemas de produção geralmente operam de forma híbrida. Orçamento por sessão, fallback baseado em qualidade ou latência e cache do que for possível. A complexidade é real, mas as economias também.

Relacionados

Assinar

Receba novos artigos sobre sistemas, infraestrutura e engenharia de IA.