Publicado em 28 de abril de 2026
Uso assistentes de IA todos os dias para programar — Claude Code, GPT, Gemini API. Ao longo do primeiro semestre de 2026, fui acumulando três frustrações com APIs em nuvem.
Pressão de tokens. Os planos pagos foram apertando os limites disponíveis a cada renovação. Cada projeto sério em algum momento batia no teto e exigia esperar o reset ou comprar pacotes adicionais. Custo crescente, controle decrescente.
Modelos somem. Em uma aplicação de
auditoria de transformadores elétricos, o gemini-1.5-pro foi
descontinuado da noite para o dia. O gemini-2.0-flash virou
bloqueado para novos usuários. Sobrou migrar para
gemini-2.5-flash enquanto durar. Quando o pipeline depende de
um modelo na nuvem, ele tem prazo de validade.
Dados sensíveis fora de casa. Cada foto de placa de transformador enviada à API do Google é dado de cliente saindo da rede.
A resposta, em três projetos construídos no primeiro semestre de 2026, foi montar o stack em casa: um servidor LLM dedicado, um cliente que apontasse para ele em vez da API da Anthropic, e uma aplicação real que provasse que isso funciona em produção.
Existe um trade-off honesto: contexto custa memória. O combo modelo+contexto precisa caber na VRAM disponível, e essa equação restringe escolhas. Mas o ganho — controle do que roda, do que sai da máquina, e quanto custa por token (zero) — compensa.
Os três projetos: ancalagon-llm (setup do servidor com RTX 4070 Ti SUPER e llama.cpp tunado para MoE), local-claude (wrapper que faz Claude Code usar modelos locais) e ocr-pipeline-local (pipeline de visão substituindo a API do Gemini).
Qwen3.6-27B local, 100% GPU
Speedup com speculative decoding (RTX 4070 Ti SUPER)
Qwen3-Coder 30B (MoE tunado)
Modelo cabendo em 16 GB de VRAM (TQ3_4S)
Testes automatizados no pipeline OCR
Acurácia em campo (alimentador real)
Backends de inferência suportados
Chamadas a APIs externas em produção
A primeira peça do stack é o servidor. O hardware é um PC
dual-boot (Windows + Ubuntu Server 24.04) com Ryzen 7600X,
32 GB de RAM e RTX 4070 Ti SUPER 16 GB. O Mac acessa via
Tailscale em 100.64.0.10.
O LM Studio era prático mas estava deixando metade dos tokens na mesa. Durante inferência, a GPU ficava em 30–34% de utilização, consumindo aproximadamente 70 W de um TGP de 285 W. Dois gargalos foram identificados:
-n-cpu-moe do llama.cpp faz
exatamente isso. O LM Studio não a expõe.turbo-tan/llama.cpp-tq3) cai para 13 GB e cabe
100% em GPU.| Configuração | GPU util | Power | tok/s gen |
|---|---|---|---|
| LM Studio — qwen3-coder Q4_K_M | 30% | 67 W | 64.5 |
llama.cpp upstream — coder -ncmoe 10 |
36% | 101 W | 81.5 |
| LM Studio — Qwen3.6-27B Q4_K_M | 34% | 94 W | 13.7 |
| llama.cpp TQ3 fork — Qwen3.6-27B-TQ3_4S | 96% | 292 W | 36.8 |
O servidor expõe três modelos via systemd, cada um em
um service com Conflicts= declarado — apenas um sobe por
vez, e o systemd para o anterior automaticamente:
--n-cpu-moe 16, ctx 96K)--n-cpu-moe 8, ctx 96K)Todos na mesma porta 1234 (idêntica à
do LM Studio) — clientes existentes não precisam mudar URL. Um wrapper
lmswitch alterna entre services com health-poll. Aliases SSH no
Mac (llcoder, llq36, llgemma4,
lloff) tornam o controle remoto trivial.
Glaurung (Mac) Ancalagon (Ubuntu)
100.64.0.10 / :1234
aliases: systemd --user:
llcoder ──ssh──> llama-coder.service ──┐
llq36 ──ssh──> llama-qwen36.service ─┤ Conflicts=
llgemma4 ──ssh──> llama-gemma4.service ─┘ (apenas um up)
lloff ──ssh──> │
▼
:1234 (OpenAI-compat API)
▲
curl http://100.64.0.10:1234 ────────────┘
Claude Code é um agente de codificação excelente, mas
só fala com a API da Anthropic. Eu queria o agente, não o vendor lock-in.
local-claude é um wrapper bash que injeta variáveis de
ambiente (ANTHROPIC_BASE_URL,
CLAUDE_CONFIG_DIR=~/.claude-local para isolar config) e roteia
o tráfego para um servidor OpenAI-compatível local ou remoto. O
claude original fica intocado.
llama-server automaticamenteremote — conecta a um servidor já
em pé (ex.: o service do Ancalagon)Quando há um modelo menor da mesma família no diretório, o script ativa speculative decoding: um “draft” pequeno gera tokens candidatos que o modelo grande verifica em batch. Tokens aceitos são gratuitos; rejeitados são regerados normalmente.
| Plataforma (Qwen2.5-7B Q8_0 + 0.5B Q8_0 draft) | Sem draft | Com draft | Speedup |
|---|---|---|---|
| Apple M4 Pro (24 GB) | 29 t/s | 57 t/s | ~2× |
| RTX 4070 Ti SUPER (16 GB) | 29 t/s | 177 t/s | ~6× |
Insight: o menor draft vence. O draft de 3B é mais lento que o de 1.5B apesar de ter taxa de aceitação maior — o overhead de verificação domina.
A janela de contexto da Apple Intelligence é de
4096 tokens. O system prompt + ferramentas do Claude Code
sozinhos somam aproximadamente 27K tokens — sete vezes mais do que
cabe. O backend roda em chat-only mode
(--bare --tools ""): dá para conversar, mas o agente não pode
usar ferramentas (editar arquivos, rodar comandos). É um lembrete físico de
que contexto = memória.
No Mac, o alias srl-coder conecta direto
ao service que está no ar (specstory run claude -c "local-claude
--backend remote --port 1234"). Zero gerenciamento de processo
no cliente: o service do Ancalagon é a infraestrutura, o local-claude é só
a ponte.
As duas primeiras peças são ferramentas de desenvolvimento. A terceira é uma aplicação concreta: um pipeline de visão que substitui a API do Google Gemini em um sistema de auditoria de transformadores elétricos. Fotos de campo de placas de equipamento entram, JSON estruturado com 11 campos sai (potência, fabricante, série, data de fabricação, elo fusível, tensões primária e secundária, fases, autoproteção, tombamento, matrícula).
Servidor dedicado: outro PC com RTX 5070 12 GB, Ubuntu 24.04, CUDA 12.8. O pipeline tem dois estágios principais e dois auxiliares:
foto de placa ─▶ YOLO Stage 0 ─▶ Stage 1 (visão) ─▶ Stage 2 (raciocínio) ─▶ JSON
(SMB ou crop Qwen3.5-VLM qwen2.5:14b ▲
upload da placa 9B (llama.cpp) (Ollama) │
base64) │ :8090 :11434 │
│ │ │
▼ ▼ │
YOLO Stage 3 ◀─────── auditoria ─────────┘ │
(revalida classes detectadas) │
│
cruzamento OCR × cadastro ────────────┘
placa_transformador, faz crop com 10% de padding e passa só
a região da placa para o VLM em alta resolução. Fallback:
redimensionar para 1600 px se YOLO falhar.placa_identificacao, marca _needs_review=True
para revisão humana.Testei sete modelos de visão (glm-ocr, qwen2.5vl, Qianfan-OCR, Gemma 4 E4B, entre outros). O Qwen3.5-VLM lê bem, mas não é confiável para emitir JSON estruturado direto — alucinou campos, inverteu valores, perdeu números. Separar OCR puro do raciocínio estruturado deu 11/11 campos corretos no conjunto de validação inicial (placas Romagnole 112.5 kVA).
O pipeline saiu do zero (v1.0) ao v3.1 com 11/11 campos em uma única sessão (06 de abril). Em 18 dias, evoluiu por v3.2 → v3.7 → v4.0 → v4.1-dev, terminando com 176 testes automatizados e ≥85.3% de acurácia em campo em um alimentador de rede elétrica (34 postes comparados com cadastro).
_classify_text e
_filter_ocr_for_stage2 separam texto de placa, de medição,
de gravação no poste e de telecom antes do Stage 2 ver.cls4=placa_identificacao vs
cls6=placa_transformador) propaga um
pole_id_context que controla filtros downstream. Detectar é
fácil; usar a detecção para mudar comportamento do pipeline é o que gera
ganho.threading.Semaphore(1) no FastAPI, serializando estágios
dentro de uma mesma requisição.placa_transformador no
banco. Se ≥ 2 → banco confirmado, e a comparação vira
OCR_pot × N vs cadastro (±5%).A API REST sobe em FastAPI na porta
8091 com envelope estilo Gemini
(contents[].parts[].inline_data) para ser drop-in para o C# que
antes chamava o Google. A aplicação dispara via dois tipos novos de agenda
no scheduler existente:
Há ainda um endpoint OCR-only
POST /api/v41/ocr/poste/{id}/texto para casos onde só o texto
bruto interessa — usado para extração de dados de poste
(separados dos dados do trafo, com filtros próprios).
Na nuvem, contexto parece grátis (Claude tem 1 M, Gemini tem 2 M). Localmente, contexto é VRAM. A equação é simples:
VRAM = pesos do modelo + KV cache
O KV cache cresce linearmente com contexto e batch. Cada combinação modelo × ctx × quantização × KV quant ou cabe ou não cabe na placa. Na prática, foi assim nos três projetos:
| Servidor | VRAM | Modelo | Quant | Ctx | KV | Observação |
|---|---|---|---|---|---|---|
| Ancalagon | 16 GB | Qwen3-Coder 30B (MoE) | Q4_K_M | 96K | q4_0 | experts na CPU (-ncmoe 16) |
| Ancalagon | 16 GB | Qwen3.6-27B | TQ3_4S | 40K | q8_0 | 100% GPU, 96% util |
| Ancalagon | 16 GB | Gemma 4 26B (MoE) | Q4_K_M | 96K | q4_0 | -ncmoe 8 |
| Servidor OCR | 12 GB | Qwen3.5-VLM + qwen2.5 | Q4 / Q5 | n/a | — | serializado por semáforo |
| Mac (apfel) | unified | Apple Intelligence | — | 4096 | — | chat-only, sem ferramentas |
Para Claude Code (system prompt ~27K + ferramentas), 4096 tokens não funciona. Mas 96K com modelo MoE de 30B em 16 GB de VRAM consumer-grade funciona — e era impensável dois anos atrás.
{checked: true, value: "q4_0"} o campo é
silenciosamente ignorado e o cache fica em f16. Descoberto medindo VRAM
antes e depois — nada na UI sinaliza.K=q8 / V=q4 → fallback CUDA catastrófico no Qwen3-Coder
(3 tok/s). Iguale ou não use.pcie_aspm=off, semáforo no FastAPI,
GpuHangError propagado para o cliente, e drop-in para parar
processos concorrentes antes de subir o serviço principal.gemini-1.5-pro 404 do dia para a noite.
Sem aviso. Migração emergencial para gemini-2.5-flash em
produção. Lembrete físico de por que vale a pena ter alternativa
local.File.Exists() silencioso em C#. Para path
SMB inacessível, retorna false sem exceção. Resultado: zero
imagens enviadas ao Gemini sem nenhuma mensagem de erro. Verifique acesso
explicitamente em paths de rede.[JsonPropertyName] (System.Text.Json) é ignorado pelo
Newtonsoft.Json. Resultado: campos todos “NI” sem erro.
Confira sempre qual serializador faz a desserialização de fato.O ganho principal não é economia (embora seja real — zero custo por token marginal). É iterar sem medo.
Quando cada chamada custa, você poda experimentos
antes deles começarem. Localmente, dá para rodar 4 configurações A/B no
mesmo dataset (25 postes × 4 = 100 inferências) numa tarde sem pensar
duas vezes — foi exatamente o que fiz para validar que o baseline
Qwen3.5-VLM + qwen2.5:14b vence as alternativas (Qwen3-VL-8B, qwen3:14b
com /no_think, qwen3:8b).
E quando o modelo é seu, ele não some. O Qwen3-Coder Q4_K_M de hoje vai estar rodando igualzinho em 2030, se eu quiser. Isso muda a escala de tempo dos projetos: dependências de modelo deixam de ser risco operacional.
O trade-off é real: contexto é finito. Para tarefas que exigem mais de 100K tokens (refator grande, base de código gigante), Claude na nuvem ainda ganha. Mas para 90% do que faço no dia a dia — feature focada, debug iterativo, OCR de placa, validação estruturada — o stack local resolve. E resolve sem que a próxima renovação de plano apertando os limites volte a ser uma fricção.