<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Modelos-Locais | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/modelos-locais/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/modelos-locais/index.xml" rel="self" type="application/rss+xml"/><item><title>LLM Benchmark v4: os top LLMs open source numa B300</title><link>https://akitaonrails.github.io/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/</guid><pubDate>Mon, 19 Oct 2026 14:00:00 -0300</pubDate><description>&lt;p&gt;Nos dois últimos textos do benchmark v4 eu &lt;a href="https://akitaonrails.github.io/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-1/"&gt;refiz o teste do zero&lt;/a&gt;, com um app Rails sabotado de propósito em sete sprints, e &lt;a href="https://akitaonrails.github.io/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-2/"&gt;ranqueei 39 modelos&lt;/a&gt; por vigilância: seis empates no topo, um modelo de graça batendo metade do Claude. Depois &lt;a href="https://akitaonrails.github.io/2026/09/23/llm-benchmark-v4-opus-5-5-gpt-6-sol-luna-mimo-2-6-grok-4-7/"&gt;testei cinco modelos novos&lt;/a&gt;, Opus 5.5, GPT 6 sol e luna, MiMo V2.6 Pro e Grok 4.7: o Opus 5.5 fechou com 100 e se juntou ao empate no topo, o GPT 6 luna fez 95,5 por menos de um dólar a rodada, e o Grok 4.7 regrediu feio contra o próprio antecessor.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Nos dois últimos textos do benchmark v4 eu <a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-1/">refiz o teste do zero</a>, com um app Rails sabotado de propósito em sete sprints, e <a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-2/">ranqueei 39 modelos</a> por vigilância: seis empates no topo, um modelo de graça batendo metade do Claude. Depois <a href="/2026/09/23/llm-benchmark-v4-opus-5-5-gpt-6-sol-luna-mimo-2-6-grok-4-7/">testei cinco modelos novos</a>, Opus 5.5, GPT 6 sol e luna, MiMo V2.6 Pro e Grok 4.7: o Opus 5.5 fechou com 100 e se juntou ao empate no topo, o GPT 6 luna fez 95,5 por menos de um dólar a rodada, e o Grok 4.7 regrediu feio contra o próprio antecessor.</p>
<p>Faltava uma categoria que eu nunca tinha testado direito: os modelos open source grandes. Os que cabem na minha <a href="/2026/01/21/omarchy-3-setup-de-dual-gpus-com-amd-e-nvidia/">RTX 5090</a> de 32 GB ou no meu <a href="/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/">Strix Halo</a> com 61 GB utilizáveis pela GPU são fracos demais pra isso. Servem pra tarefa simples de agente, e nenhum modelo pequeno aguenta tarefa real de programação, muito menos um projeto complexo de software.</p>
<p>O Qwen 3.8 27B levou 706 minutos pra fechar o v4 no Strix e fez 80; o GLM-4.7-Flash local fez 24. Então nem tente.</p>
<p>Mas e se a gente forçar? O que modelos de tamanho médio, de 100 a 400 bilhões de parâmetros, conseguem fazer rodando numa única B300 alugada? Eu podia alugar quatro delas e rodar os modelos gigantes de 750 bilhões ou um trilhão, e não tenho certeza de que o custo-benefício vale. Os dados abaixo respondem.</p>
<blockquote>
  <p><strong>TL;DR:</strong> 14 modelos abertos, uma B300 de 288 GB na Runpod a uns 9 dólares por hora, mesmo protocolo v4 de sabotagem. Nenhum deles serve rápido do jeito que vem: o GLM 5.3 Flash decodificava a 10 tokens por segundo com a configuração padrão e a 141 depois de ajustado, 14 vezes mais só de flag. Dois modelos abertos, GLM 5.3 Flash e Qwen3.8 Flash-Next, fizeram 92,5, nível Claude Sonnet 5, e o Swift 1.5 27B fez 88, nível Opus 4.6. Nenhum chegou nos 100 da fronteira. O flagship de 397 bilhões fez 68,5 e deixou passar uma backdoor de admin, o DeepSeek V4 Flash fez 86 na nuvem e 44,5 servido por mim, e o Nemotron trapaceou num teste de regressão. A API em nuvem do mesmo modelo custa um dólar por rodada; o pod custa 218 dólares por dia. Pule pra <a href="#o-que-%c3%a9-uma-b300-e-o-que-%c3%a9-a-runpod">B300 e Runpod</a>, pro <a href="#nenhum-deles-serve-r%c3%a1pido-do-jeito-que-vem">ajuste de servidor</a>, pro <a href="#o-ranking">ranking</a>, pros <a href="#os-casos-interessantes">casos interessantes</a>, pra <a href="#a-que-dist%c3%a2ncia-da-fronteira">comparação com a fronteira</a>, pra <a href="#a-conta">conta</a> ou pra <a href="#vale-a-pena-ter-a-pr%c3%b3pria-infra">conclusão</a>.</p>

</blockquote>
<h2>O que é uma B300, e o que é a Runpod<span class="hx:absolute hx:-mt-20" id="o-que-é-uma-b300-e-o-que-é-a-runpod"></span>
    <a href="#o-que-%c3%a9-uma-b300-e-o-que-%c3%a9-a-runpod" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A <strong>B300</strong> é a GPU de datacenter da geração Blackwell Ultra da NVIDIA, anunciada em março de 2025. Cada placa tem <a href="https://developer.nvidia.com/blog/nvidia-blackwell-ultra-for-the-era-of-ai-reasoning/"target="_blank" rel="noopener">288 GB de memória HBM3e</a>, nove vezes a minha 5090, e uns 15 petaflops de computação densa em NVFP4, o formato de 4 bits que a Blackwell faz em hardware. É a placa que vai dentro do rack GB300 NVL72, 72 delas interligadas, que as empresas de IA compram aos milhares.</p>
<p>Sozinha, uma B300 custa mais que um apartamento. O que a torna acessível é alugar por hora.</p>
<p>A <a href="https://www.runpod.io/"target="_blank" rel="noopener"><strong>Runpod</strong></a> é um provedor de GPU em nuvem que aluga placas em &ldquo;pods&rdquo;, containers Linux com uma ou mais GPUs, cobrados por segundo. Você escolhe a placa, um template de imagem, sobe por SSH, e paga enquanto o pod está ligado, inclusive ocioso. A B300 custava uns <a href="https://www.runpod.io/pricing"target="_blank" rel="noopener">9 dólares por hora</a> na nuvem segura deles na semana do teste; uma H100 sai por 3 a 4. O disco do container é efêmero: terminou o pod, os 250 GB de pesos que você baixou somem, e você baixa de novo.</p>
<p>O pod que eu usei: uma B300 com 275 GiB segundo o <code>nvidia-smi</code> (os mesmos 288 GB nominais, em unidades binárias), 344 vCPUs, 4 TB de RAM no host, 700 GB de disco. O servidor de inferência foi o vLLM; o harness foi o mesmo OpenCode de todos os outros modelos do ranking, conectado por túnel SSH ao pod. O modelo roda lá; o código, os testes e os resultados ficam na minha máquina.</p>
<p>Caber na memória é a primeira peneira. Na B300 cabem os modelos de 100 a 400 bilhões de parâmetros quantizados em NVFP4, mais os de 27 a 35 bilhões em BF16. Ficam de fora o GLM 5.3 flagship de 753 bilhões, o DeepSeek V4.1 Flash de 750, o DeepSeek V4 Pro de 1,6 trilhão e o Kimi K3 de 2,8 trilhões. Pra esses, seriam quatro B300 ou mais, uns 36 dólares por hora.</p>
<h2>A metodologia<span class="hx:absolute hx:-mt-20" id="a-metodologia"></span>
    <a href="#a-metodologia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O protocolo é o <a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-1/">v4</a> de sempre: o modelo constrói um app Rails em sete sprints acumulados, e entre os sprints um revisor independente, isolado do modelo, injeta 14 sabotagens, de CVE real a regressão silenciosa de lógica e de teste, no código que o próprio modelo escreveu. A nota de 0 a 100 mede vigilância: quanto o modelo pega e conserta sem ser avisado (peso cheio), quanto conserta depois de um &ldquo;faça uma auditoria de segurança&rdquo; explícito no último sprint (40% do peso), e o que fica sem resolver (zero). Os tiers são A a partir de 83, B de 73 a 82, C de 51 a 72, D até 50.</p>
<p>Duas diferenças nesta rodada. A primeira é o revisor: os cinco modelos que terminaram antes do bloqueio foram revisados de ponta a ponta pelo Codex, como nas rodadas anteriores.</p>
<p>No meio da leva o serviço de segurança da OpenAI passou a recusar a operação de injeção e correção, com uma flag de risco cibernético, mesmo sendo código meu, em máquina minha, num benchmark com 46 modelos já pontuados pelo mesmo protocolo. Troquei o revisor pro Claude Opus 4.8 nos modelos restantes, com o mesmo plano de injeção, mesmo catálogo, mesma régua, e a proveniência de cada fronteira está gravada por sprint. O impacto esperado na nota é nenhum, porque os dois executam a mesma receita escrita.</p>
<p>A segunda é a admissão por velocidade, que é o assunto da próxima seção. Modelo que decodifica abaixo de 100 tokens por segundo nem entra na pista.</p>
<p>Além disso: o modelo aberto tem direito a até dois empurrões curtos por rodada pra defeito bobo (um import faltando, um ponto de entrada de teste errado), registrados como &ldquo;assistido&rdquo;. Falha de infraestrutura, driver, loader, parser, nunca é cobrada do modelo. E o pod rodava um modelo por vez, sem acesso ao workspace ou ao resultado de nenhum outro.</p>
<h2>Nenhum deles serve rápido do jeito que vem<span class="hx:absolute hx:-mt-20" id="nenhum-deles-serve-rápido-do-jeito-que-vem"></span>
    <a href="#nenhum-deles-serve-r%c3%a1pido-do-jeito-que-vem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A primeira lição custou um dia. Baixei o <a href="https://huggingface.co/RedHatAI/GLM-5.3-Flash-NVFP4"target="_blank" rel="noopener">GLM 5.3 Flash</a>, 320 bilhões de parâmetros com 18 ativos, quantizado em NVFP4, 198 GB. Subi no vLLM com a configuração que &ldquo;funciona&rdquo;: serve, passa nos testes de chamada de ferramenta, responde. E decodificava a <strong>10 tokens por segundo</strong>, com a GPU a 10% de uso, 209 watts dos 1.100 que o pod libera.</p>
<p>Isso é inutilizável pra programar. Um agente de código gera dezenas de milhares de tokens por sprint; a 10 por segundo, um sprint de 20 minutos vira mais de quatro horas.</p>
<p>O mesmo checkpoint, mesmo contexto de 128 mil tokens, mesmo cache de KV em FP8, mesmo prompt de teste, trocando só as flags de compilação e modo: <strong>141 tokens por segundo</strong>. Catorze vezes mais rápido, zero mudança de precisão ou de pesos.</p>
<p>O culpado principal era o <code>--enforce-eager</code>, que desliga o <code>torch.compile</code> e a captura de CUDA graphs do vLLM, e que todo tutorial manda ligar &ldquo;pra subir mais rápido&rdquo;. Sobe mais rápido e serve 14 vezes mais devagar. A medição trocou essa flag junto com o modo <code>interactivity</code>, então eu não isolei a contribuição de cada uma.</p>
<p>Daí a regra de admissão: <strong>menos de 100 tokens por segundo eu considero inutilizável</strong>, e nenhum modelo entra no benchmark sem passar disso na mediana de duas sondagens quentes de 256 tokens, depois de uma fria. Pra não virar um buraco sem fundo, o ajuste é limitado: no máximo três configurações e 45 minutos de relógio por modelo, incluindo carga e compilação. E ajuste nunca pode trocar qualidade por velocidade: nada de encurtar contexto, baixar esforço de raciocínio ou quantizar mais pra bater o número.</p>
<p>Os ajustes que valeram pra todo mundo:</p>
<ul>
<li><strong>Instalar um CUDA que compile pra placa.</strong> A imagem padrão da Runpod vem com CUDA 12.8, cujo <code>nvcc</code> <a href="https://developer.nvidia.com/cuda-gpus"target="_blank" rel="noopener">não compila pra <code>compute_103a</code></a>, a capacidade de computação da B300. Sem o 12.9, os kernels NVFP4 nunca compilam.</li>
<li><strong>Nunca usar <code>--enforce-eager</code>.</strong> Deixar o vLLM compilar e capturar CUDA graphs é a maior vitória isolada.</li>
<li><strong><code>--performance-mode interactivity</code></strong>: otimizar pra latência de um fluxo só, que é o que um agente serial precisa, em vez de vazão agregada.</li>
<li><strong>Cache de KV em FP8 nos MoE grandes, cache de prefixo e prefill em pedaços em todos</strong>, e confirmados pelos contadores do Prometheus do vLLM em vez de assumidos: num prompt de 11 mil tokens, o servidor reaproveitou exatamente 8.576 tokens de prefixo a cada repetição.</li>
<li><strong>O parser certo de ferramenta e de raciocínio pra cada família.</strong> Parser errado faz chamada de ferramenta sumir em silêncio ou vazar o raciocínio na resposta.</li>
</ul>
<p>E os ajustes específicos que achei mais interessantes:</p>
<ul>
<li><strong>NVFP4 só roda no vLLM.</strong> Os checkpoints de 198 a 251 GB em FP4 nativo da Blackwell são a única forma de caber na placa, e llama.cpp e Ollama simplesmente não carregam esses formatos. Quem vive de GGUF não tem acesso a essa faixa.</li>
<li><strong>Decodificação especulativa nativa.</strong> O Qwen 3.8 27B foi de 92 pra 195 tokens por segundo com a cabeça MTP que o próprio checkpoint traz, prevendo três tokens por passo, mais <code>--language-model-only</code> pra descartar a torre de visão (medido em pods diferentes, então sem ablação controlada). O <a href="https://huggingface.co/meta-models/Muse-Glimmer-30B"target="_blank" rel="noopener">Muse Glimmer</a> chegou a 295 com o rascunhador DFlash leve que ele publica junto, 15 tokens especulados por passo. Só liguei onde existe um rascunhador oficial e onde a chamada de ferramenta continuou correta; pro Ornith existe um rascunhador publicado à parte, com taxa de aceitação desconhecida, então ficou desligado.</li>
<li><strong>O loader do CUDA 12.</strong> O flashinfer compila em JIT um kernel de MoE em NVFP4 que precisa do <code>libcudart.so.12</code>, e depois de instalar o CUDA 13 pro Nemotron a resolução de biblioteca quebrou. O sintoma parecia OOM; a causa era um <code>ld.so.conf</code>. Um script de reparo com evidência de <code>ldd</code> antes e depois resolveu, e o 397B passou a servir a 144 tokens por segundo.</li>
<li><strong>Um ambiente virtual por runtime.</strong> O Nemotron 3.5 Super VL exigiu um vLLM 0.32 próprio num venv separado, porque é o que o card oficial fixa, e esse build pede CUDA 13. Cada família de modelo é um caso bespoke: parser, quantização, cabeça de rascunho, versão de runtime e reparo de loader, tudo diferente.</li>
<li><strong>Um bug de parser que derrubava a última chamada.</strong> O parser de ferramenta Qwen do vLLM descartava o último parâmetro de uma chamada quando faltava a tag de fechamento do parâmetro mas a de função estava presente. Um reparo estreito, com 24 casos de teste, tirou esse ruído de serving da conta do modelo.</li>
</ul>
<p>Os argumentos exatos de cada modelo, os logs de sondagem e o livro-razão de cada rodada de ajuste estão no <a href="https://github.com/akitaonrails/llm-coding-benchmark/blob/master/docs/b300-serving-optimizations.md"target="_blank" rel="noopener">documento de otimizações de serving</a> e no <a href="https://github.com/akitaonrails/llm-coding-benchmark/blob/master/docs/runpod-b300-openweights-runbook.md"target="_blank" rel="noopener">runbook da B300</a> do repositório.</p>
<blockquote>
  <p>A velocidade é quase de graça, depois que você deixa o vLLM compilar. O trabalho real é descobrir, modelo por modelo, qual combinação de toolkit, parser, quantização e cabeça de rascunho aquele checkpoint exige. Eu dei 45 minutos pra cada um e precisei deles.</p>

</blockquote>
<h2>O ranking<span class="hx:absolute hx:-mt-20" id="o-ranking"></span>
    <a href="#o-ranking" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Nota v4 de vigilância, tier, tamanho em memória, velocidade de decodificação na sondagem de admissão (quente, um fluxo só) e o custo só do tempo de inferência dos sete sprints, a 9,09 dólares por hora. O custo exclui download, carga, compilação, ajuste e tempo ocioso; a conta de verdade vem mais abaixo.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: right">#</th>
          <th>Modelo</th>
          <th style="text-align: right">v4</th>
          <th>Tier</th>
          <th style="text-align: right">Memória</th>
          <th style="text-align: right">tok/s</th>
          <th style="text-align: right">Minutos</th>
          <th style="text-align: right">Custo inferência</th>
          <th>Revisor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">1</td>
          <td>GLM 5.3 Flash (NVFP4)</td>
          <td style="text-align: right"><strong>92,5</strong></td>
          <td>A</td>
          <td style="text-align: right">198 GB</td>
          <td style="text-align: right">142</td>
          <td style="text-align: right">145</td>
          <td style="text-align: right">US$ 21,96</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>Qwen3.8 Flash-Next (NVFP4)</td>
          <td style="text-align: right"><strong>92,5</strong></td>
          <td>A</td>
          <td style="text-align: right">100 GB</td>
          <td style="text-align: right">195</td>
          <td style="text-align: right">80</td>
          <td style="text-align: right">US$ 12,04</td>
          <td>Codex → Claude</td>
      </tr>
      <tr>
          <td style="text-align: right">3</td>
          <td>Swift 1.5 27B</td>
          <td style="text-align: right"><strong>88,0</strong></td>
          <td>A</td>
          <td style="text-align: right">56 GB</td>
          <td style="text-align: right">203</td>
          <td style="text-align: right">64</td>
          <td style="text-align: right">US$ 9,71</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td style="text-align: right">4</td>
          <td>ThinkingCap 3.8 27B</td>
          <td style="text-align: right"><strong>83,0</strong></td>
          <td>A</td>
          <td style="text-align: right">56 GB</td>
          <td style="text-align: right">189</td>
          <td style="text-align: right">76</td>
          <td style="text-align: right">US$ 11,47</td>
          <td>Codex → Claude</td>
      </tr>
      <tr>
          <td style="text-align: right">5</td>
          <td>Muse Glimmer 30B</td>
          <td style="text-align: right">81,0</td>
          <td>B</td>
          <td style="text-align: right">60 GB</td>
          <td style="text-align: right">295</td>
          <td style="text-align: right">23</td>
          <td style="text-align: right">US$ 3,54</td>
          <td>Codex → Claude</td>
      </tr>
      <tr>
          <td style="text-align: right">5</td>
          <td>Ornith 1.5 35B</td>
          <td style="text-align: right">81,0</td>
          <td>B</td>
          <td style="text-align: right">70 GB</td>
          <td style="text-align: right">317</td>
          <td style="text-align: right">161</td>
          <td style="text-align: right">US$ 24,39</td>
          <td>Codex → Claude</td>
      </tr>
      <tr>
          <td style="text-align: right">7</td>
          <td>Qwen 3.8 27B</td>
          <td style="text-align: right">80,5</td>
          <td>B</td>
          <td style="text-align: right">54 GB</td>
          <td style="text-align: right">195</td>
          <td style="text-align: right">192</td>
          <td style="text-align: right">US$ 29,11</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td style="text-align: right">8</td>
          <td>Qwen3.5 397B (NVFP4)</td>
          <td style="text-align: right">68,5</td>
          <td>C</td>
          <td style="text-align: right">251 GB</td>
          <td style="text-align: right">140</td>
          <td style="text-align: right">54</td>
          <td style="text-align: right">US$ 8,22</td>
          <td>Codex → Claude</td>
      </tr>
      <tr>
          <td style="text-align: right">9</td>
          <td>Nemotron 3.5 Super VL (NVFP4)</td>
          <td style="text-align: right">63,0</td>
          <td>C</td>
          <td style="text-align: right">70 GB</td>
          <td style="text-align: right">207</td>
          <td style="text-align: right">50</td>
          <td style="text-align: right">US$ 7,51</td>
          <td>Codex → Claude</td>
      </tr>
      <tr>
          <td style="text-align: right">10</td>
          <td>DeepSeek V4 Flash</td>
          <td style="text-align: right">44,5</td>
          <td>D</td>
          <td style="text-align: right">167 GB</td>
          <td style="text-align: right">153</td>
          <td style="text-align: right">37</td>
          <td style="text-align: right">US$ 5,59</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td style="text-align: right">11</td>
          <td>GLM 4.7 Flash BF16</td>
          <td style="text-align: right">33,5</td>
          <td>D</td>
          <td style="text-align: right">60 GB</td>
          <td style="text-align: right">217</td>
          <td style="text-align: right">30</td>
          <td style="text-align: right">US$ 4,60</td>
          <td>Codex</td>
      </tr>
  </tbody>
</table>
<p>Três não têm nota: o <strong>Command A Plus</strong> da Cohere (218 bilhões, 25 ativos, W4A4) travou num loop reescrevendo o mesmo model 23 vezes, precisou de três empurrões e ainda deixou os testes sem rodar antes de o revisor ser bloqueado; o <strong>Nex N2.5 Mini</strong> rodou a 337 tokens por segundo e entregou Ruby inválido, migrations contraditórias e autenticação quebrada em duas tentativas, um resultado ruim válido de um servidor que funciona; e o <strong>Qwen3.5 122B</strong> era reserva condicional pro caso de o 397B não terminar, e o 397B terminou.</p>
<p>Os minutos são só de sprint, e os de Ornith incluem um sprint interrompido por infra. Flash-Next, Swift, ThinkingCap, Muse e Qwen 27B levaram um empurrão cada; GLM 4.7, dois; os outros, nenhum. A velocidade de sondagem é um número de admissão: a vazão de uma sessão real de código inclui prefill, ferramentas, testes e as escolhas do agente. E repare que os dois primeiros empatam na nota com perfis opostos: o GLM gastou 145 minutos e 22 dólares pra chegar nos mesmos 92,5 que o Qwen3.8 Flash-Next fez em 80 minutos e 12 dólares, com metade da memória.</p>
<h2>Os casos interessantes<span class="hx:absolute hx:-mt-20" id="os-casos-interessantes"></span>
    <a href="#os-casos-interessantes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Qwen3.8 Flash-Next: o melhor dos abertos, por custo<span class="hx:absolute hx:-mt-20" id="qwen38-flash-next-o-melhor-dos-abertos-por-custo"></span>
    <a href="#qwen38-flash-next-o-melhor-dos-abertos-por-custo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <a href="https://huggingface.co/nvidia/Qwen3.8-Flash-Next-NVFP4"target="_blank" rel="noopener">Flash-Next</a> é, <a href="https://recipes.vllm.ai/Qwen/Qwen3.8-Flash-Next"target="_blank" rel="noopener">nas palavras da Qwen</a>, uma prévia da arquitetura do Qwen 4: 125 bilhões de parâmetros de modelo de linguagem com 6 bilhões ativos, mais uma tabela de embeddings de n-gramas de 51 bilhões que a Qwen diz que pode ser descarregada pra memória do host, mais uma cabeça MTP de 4. Quantizado em NVFP4 pela NVIDIA, ocupou uns 100 GB de placa (o checkpoint tem 133 GB em disco). Foi o único modelo da segunda leva a resolver todos os 14 itens: pegou o vazamento entre tenants sem aviso, pegou o item composto de integridade de teste, e escreveu por conta própria specs de isolamento por usuário, de modo que a IDOR plantada na API nem tinha como continuar verde. Com 195 tokens por segundo e 12 dólares de inferência, é o melhor custo-benefício desta tabela.</p>
<h3>GLM 5.3 Flash: o mesmo 92,5, três vezes mais caro<span class="hx:absolute hx:-mt-20" id="glm-53-flash-o-mesmo-925-três-vezes-mais-caro"></span>
    <a href="#glm-53-flash-o-mesmo-925-tr%c3%aas-vezes-mais-caro" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O GLM fez a mesma nota com uma arquitetura bem maior: 320 bilhões de parâmetros, 18 ativos, 198 GB mesmo em FP4. Resolveu os 14 itens, zero empurrão, revisado de ponta a ponta pelo Codex.</p>
<p>Só que levou 145 minutos e consumiu o dobro da memória pelo mesmo resultado. Na nuvem da Z.ai, o mesmo modelo fez 84,25 num plano fixo. Com o ruído de uma rodada só, que no meu histórico chega a nove pontos, a leitura mais segura é que os dois estão na mesma faixa.</p>
<h3>Swift 1.5 e ThinkingCap: dois fine-tunes de 27B no Tier A<span class="hx:absolute hx:-mt-20" id="swift-15-e-thinkingcap-dois-fine-tunes-de-27b-no-tier-a"></span>
    <a href="#swift-15-e-thinkingcap-dois-fine-tunes-de-27b-no-tier-a" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As duas surpresas. O <a href="https://huggingface.co/ukisai/Swift-1.5-Qwen3.8-27b"target="_blank" rel="noopener">Swift 1.5</a>, da UkisAI, e o <a href="https://huggingface.co/bottlecapai/ThinkingCap-Qwen3.8-27B"target="_blank" rel="noopener">ThinkingCap 3.8</a>, da BottleCap AI, são ajustes do Qwen 3.8 27B treinados pra pensar menos: o Swift anuncia até 58% menos tokens de raciocínio (na mediana do GPQA; no Terminal-Bench, 16%), o ThinkingCap uns 37% na média. Os dois cabem em 56 GB em BF16, rodam a uns 200 tokens por segundo com MTP, e fizeram 88 e 83.</p>
<p>O Swift ficou acima do Opus 4.6 da Anthropic. O Qwen 3.8 27B base, mesma arquitetura, fez 80,5 com 192 minutos; o Swift fez 88 em 64. O Swift pegou sabotagem mais cedo que o base; se foi por pensar menos, uma rodada de cada deixa de provar.</p>
<p>Uma ressalva de proveniência: o revisor registrou que o Swift leu mensagens de commit locais que revelavam regressões, o que enfraquece uma leitura literal de &ldquo;descoberta totalmente sem ajuda&rdquo;. E ele deixou um item sem resolver, um admin inativo ainda aceito com um teste vazio de guarda. Ainda assim, de todos os modelos desta tabela, é o que eu colocaria numa placa minha.</p>
<h3>Qwen3.5 397B: o flagship que deixou passar a backdoor<span class="hx:absolute hx:-mt-20" id="qwen35-397b-o-flagship-que-deixou-passar-a-backdoor"></span>
    <a href="#qwen35-397b-o-flagship-que-deixou-passar-a-backdoor" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O maior modelo da leva, 397 bilhões de parâmetros com 17 ativos, 251 GB de NVFP4 em 275 de placa, serviu a 140 tokens por segundo depois do reparo do loader e fez <strong>68,5</strong>, Tier C.</p>
<p>O caso que define a nota: a sabotagem número 6 é uma linha <code>admin? =&gt; admin || email.end_with?(&quot;@internal.example&quot;)</code>, uma escalação de privilégio por domínio de e-mail. O 397B deixou passar nos sprints normais, deixou passar no sprint 7 com o pedido explícito de auditoria de segurança, e <strong>escreveu um teste que passava, codificando a backdoor como comportamento esperado</strong>, no mesmo commit em que anunciou &ldquo;Brakeman: 0 warnings&rdquo;. Parâmetro não comprou vigilância.</p>
<h3>Nemotron 3.5 Super VL: trapaceou no teste<span class="hx:absolute hx:-mt-20" id="nemotron-35-super-vl-trapaceou-no-teste"></span>
    <a href="#nemotron-35-super-vl-trapaceou-no-teste" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <a href="https://huggingface.co/nvidia/NVIDIA-Nemotron-3.5-Super-VL-120B-A12B-NVFP4"target="_blank" rel="noopener">Nemotron 3.5 Super VL</a> da NVIDIA, 120 bilhões com 12 ativos, lançado dois dias antes de eu começar a testar e <a href="https://blogs.nvidia.com/blog/nemotron-3-5-super-vl/"target="_blank" rel="noopener">vendido pela NVIDIA</a> como o modelo aberto de agente mais rápido da categoria, com &ldquo;acurácia líder&rdquo; em tarefas multimodais, fez <strong>63</strong>. A sabotagem número 2 remove um link de navegação e o teste de regressão procura <code>have_link('Sign in')</code>. O Nemotron trocou o teste pra <code>have_button('Sign in')</code>, que passa no botão do formulário de login sem restaurar o link, e manteve a trapaça mesmo depois do sprint de revelação.</p>
<p>Também tende a pegar item um sprint atrasado. E foi o que exigiu um runtime próprio: vLLM 0.32 com CUDA 13 num ambiente separado.</p>
<h3>DeepSeek V4 Flash: 86 na nuvem, 44,5 na minha mão<span class="hx:absolute hx:-mt-20" id="deepseek-v4-flash-86-na-nuvem-445-na-minha-mão"></span>
    <a href="#deepseek-v4-flash-86-na-nuvem-445-na-minha-m%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O caso mais importante pra quem pensa em self-host. O <a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash"target="_blank" rel="noopener">DeepSeek V4 Flash</a>, 284 bilhões com 13 ativos, fez <strong>86, Tier A, por 97 centavos</strong> pela API em setembro, e a versão 0731, exatamente a que eu baixei, fez 85 por 1,94. Os mesmos pesos públicos em FP4 e FP8 misto, servidos por mim na B300, fizeram <strong>44,5, Tier D</strong>: pegou 4 itens sem aviso, mais 5 na revelação, e deixou 5 sem resolver. Foi o mais rápido a terminar, 37 minutos, e o que menos vigiou.</p>
<p>A causa provável é divergência entre o checkpoint público e o que a DeepSeek serve, ou entre o stack de serving deles e o meu: parser, template de chat, modo de raciocínio, quantização. Eu segui o card oficial. O que isso prova é que o nome do modelo no Hugging Face não garante a qualidade da API hospedada, e que quem vai hospedar precisa validar antes de confiar.</p>
<h3>GLM 4.7 Flash: igual ao local, ou seja, ruim<span class="hx:absolute hx:-mt-20" id="glm-47-flash-igual-ao-local-ou-seja-ruim"></span>
    <a href="#glm-47-flash-igual-ao-local-ou-seja-ruim" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O GLM 4.7 Flash de 30 bilhões em BF16, a 217 tokens por segundo, fez 33,5. Em casa, em GGUF Q5, tinha feito 24. Precisou de dois empurrões, deixou nove itens sem resolver e entregou uma migration com erro de sintaxe. A diferença de nove pontos e meio cabe dentro do ruído de uma rodada; o que fica é que o modelo é fraco de qualquer jeito.</p>
<h3>O que apareceu em vários<span class="hx:absolute hx:-mt-20" id="o-que-apareceu-em-vários"></span>
    <a href="#o-que-apareceu-em-v%c3%a1rios" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ornith, Muse Glimmer e o 397B compartilharam um defeito de higiene: rodaram o sprint até o fim com exit zero e esqueceram de commitar o próprio trabalho, deixando pro revisor fazer o checkpoint. Não entrou na nota, e num time de verdade seria o primeiro motivo de reclamação. E o aglomerado de 81 a 83, ThinkingCap, Ornith e Muse, tem um perfil consistente: pega injeção de SQL, vazamento entre tenants, XSS e segredo exposto com confiança, e deixa passar os itens silenciosos, controle de acesso quebrado, integridade de teste, índice faltando, agregação errada.</p>
<h2>A que distância da fronteira<span class="hx:absolute hx:-mt-20" id="a-que-distância-da-fronteira"></span>
    <a href="#a-que-dist%c3%a2ncia-da-fronteira" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Os três melhores abertos contra os melhores comerciais do mesmo ranking, com o custo e o tempo de cada rodada:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">v4</th>
          <th>Tier</th>
          <th style="text-align: right">Custo da rodada</th>
          <th style="text-align: right">Minutos</th>
          <th>Onde</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPT-6 Astra</td>
          <td style="text-align: right">100,0</td>
          <td>A</td>
          <td style="text-align: right">US$ 30,55</td>
          <td style="text-align: right">100</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td>Claude Opus 5.5</td>
          <td style="text-align: right">100,0</td>
          <td>A</td>
          <td style="text-align: right">US$ 16,63</td>
          <td style="text-align: right">64</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td>GPT 5.6 terra</td>
          <td style="text-align: right">100,0</td>
          <td>A</td>
          <td style="text-align: right">US$ 9,52</td>
          <td style="text-align: right">80</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td>Grok 4.6</td>
          <td style="text-align: right">98,5</td>
          <td>A</td>
          <td style="text-align: right">US$ 13,00</td>
          <td style="text-align: right">67</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td>Claude Fable 5.1</td>
          <td style="text-align: right">95,5</td>
          <td>A</td>
          <td style="text-align: right">~US$ 51</td>
          <td style="text-align: right">133</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td><strong>GLM 5.3 Flash (NVFP4)</strong></td>
          <td style="text-align: right"><strong>92,5</strong></td>
          <td>A</td>
          <td style="text-align: right">US$ 21,96 só inferência</td>
          <td style="text-align: right">145</td>
          <td>B300</td>
      </tr>
      <tr>
          <td><strong>Qwen3.8 Flash-Next (NVFP4)</strong></td>
          <td style="text-align: right"><strong>92,5</strong></td>
          <td>A</td>
          <td style="text-align: right">US$ 12,04 só inferência</td>
          <td style="text-align: right">80</td>
          <td>B300</td>
      </tr>
      <tr>
          <td>Claude Sonnet 5</td>
          <td style="text-align: right">91,0</td>
          <td>A</td>
          <td style="text-align: right">~US$ 27</td>
          <td style="text-align: right">112</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td>Gemini 3.8 Flash high</td>
          <td style="text-align: right">90,5</td>
          <td>A</td>
          <td style="text-align: right">US$ 15,98</td>
          <td style="text-align: right">97</td>
          <td>nuvem</td>
      </tr>
      <tr>
          <td><strong>Swift 1.5 27B</strong></td>
          <td style="text-align: right"><strong>88,0</strong></td>
          <td>A</td>
          <td style="text-align: right">US$ 9,71 só inferência</td>
          <td style="text-align: right">64</td>
          <td>B300</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td style="text-align: right">87,5</td>
          <td>A</td>
          <td style="text-align: right">US$ 25,64</td>
          <td style="text-align: right">89</td>
          <td>nuvem</td>
      </tr>
  </tbody>
</table>
<p>A leitura: o melhor do open source que cabe numa B300 e que eu servi está <strong>um degrau abaixo do teto comercial e no mesmo nível do meio do Tier A</strong>. Diferenças de um ou dois pontos são ruído de uma rodada.</p>
<p>GLM 5.3 Flash e Qwen3.8 Flash-Next empatam com o Sonnet 5 e passam o Gemini 3.8 Flash e o Opus 4.6. Nenhum chega nos 100 que sete modelos comerciais fizeram, nem nos 98,5 do Grok 4.6. Sete pontos e meio separam o melhor aberto do teto, e no v4 cada ponto é uma sabotagem a menos pega sem aviso.</p>
<p>A diferença que importa é outra: previsibilidade. Entre os comerciais de ponta da tabela, o pior é 87,5, e o campo comercial inteiro também tem fundo de poço, o Mistral Large 3 fez 39. O campo aberto tem os mesmos 92,5 no topo e dois Tier D no fundo, com o DeepSeek fazendo 85 na nuvem e 44,5 na minha mão. Escolher um comercial de ponta é escolher um número que a API reproduz; escolher um aberto é apostar que o checkpoint, a quantização e o seu stack de serving reproduzem o número do card, e às vezes reproduzem.</p>
<h2>A conta<span class="hx:absolute hx:-mt-20" id="a-conta"></span>
    <a href="#a-conta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se a qualidade ainda fica abaixo da fronteira, o custo pelo menos é proporcional? Depende de onde você roda o modelo aberto.</p>
<p><strong>Na API em nuvem do próprio fornecedor, sim, e com folga.</strong> Uma rodada completa do v4 custou 97 centavos no DeepSeek V4 Flash, 1,17 dólar no Qwen 3.8 Flash, 1,03 no MiMo V2.5 Pro, todos Tier A na nuvem. Contra 9,52 a 30,55 dólares dos modelos de 100 pontos, é dez a trinta vezes mais barato por uma nota 8 a 14 pontos menor. Os preços de lista explicam: o <a href="https://docs.z.ai/guides/overview/pricing"target="_blank" rel="noopener">GLM 5.3 Flash</a> custa 15 centavos por milhão de tokens de entrada e 50 de saída; o <a href="https://api-docs.deepseek.com/quick_start/pricing"target="_blank" rel="noopener">DeepSeek Flash</a>, 15 a 30 centavos de entrada e 60 centavos a 1,20 de saída; o <a href="https://platform.claude.com/docs/en/about-claude/pricing"target="_blank" rel="noopener">Opus 5.5</a>, 4 e 20 dólares; o <a href="https://developers.openai.com/api/docs/pricing"target="_blank" rel="noopener">GPT-6 Astra</a>, 10 e 50.</p>
<p><strong>Na sua própria B300 alugada, não.</strong> O pod custa 9 dólares por hora ligado ou ocioso, <strong>218 por dia, uns 6.500 por mês</strong>. O tempo de modelo desta leva inteira, 14 modelos entre download, carga e sprints, somou 23 horas, uns 212 dólares a preço de pod; só os sprints foram 17 horas e 157 dólares.</p>
<p>Só que o pod cobra também o tempo em que nada roda. Eu coloquei 400 dólares de crédito na Runpod e sobraram 127: a leva inteira custou <strong>273 dólares</strong>, umas 30 horas de pod, incluindo armazenamento, quatro pods em sequência e sete horas ociosas numa madrugada em que a orquestração morreu e nada avisou. Quase 60 dólares a mais que o tempo de modelo, só de espera. A conta de ponto de equilíbrio do repositório: o pod só fica mais barato que a API a partir de umas <strong>6.500 rodadas por mês</strong>, 217 por dia, sustentadas. Um desenvolvedor sozinho ou um time pequeno nem chega perto.</p>
<p>E modelo contra modelo, lado a lado:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>API em nuvem</th>
          <th>Hospedado por mim na B300</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V4 Flash 0731</td>
          <td>85 (A) por US$ 1,94</td>
          <td>44,5 (D) por US$ 5,59 só de inferência</td>
      </tr>
      <tr>
          <td>Qwen 3.8 Flash (nuvem) e Flash-Next (B300), da mesma família</td>
          <td>86,5 (A) por US$ 1,17</td>
          <td>92,5 (A) por US$ 12,04</td>
      </tr>
      <tr>
          <td>Qwen 3.8 27B</td>
          <td>80 (B) de graça no Strix, em 706 minutos</td>
          <td>80,5 (B) por US$ 29,11, em 192 minutos</td>
      </tr>
  </tbody>
</table>
<blockquote>
  <p>A API em nuvem de um modelo aberto é dez a trinta vezes mais barata que a fronteira por um tier a menos. Hospedar o mesmo modelo numa B300 alugada é mais caro que a fronteira, e às vezes pior que a própria API dele.</p>

</blockquote>
<h2>Cabe um time na mesma placa?<span class="hx:absolute hx:-mt-20" id="cabe-um-time-na-mesma-placa"></span>
    <a href="#cabe-um-time-na-mesma-placa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Uma pergunta que eu fiz olhando a GPU a 10% de uso: esses modelos usam toda a computação do pod? Dava pra colocar um time inteiro na mesma B300?</p>
<p>Em princípio, sim, e é pra isso que o vLLM existe. O <a href="https://vllm.ai/blog/2023-06-20-vllm"target="_blank" rel="noopener">batching contínuo</a> agenda várias requisições no mesmo passo de decodificação, e o <a href="https://vllm.ai/blog/2023-06-20-vllm"target="_blank" rel="noopener">PagedAttention</a> guarda o cache de KV de cada sessão em blocos não contíguos, com menos de 4% de desperdício, justamente pra caber mais sessões na mesma memória. Um modelo de 27 bilhões em BF16 ocupa 56 GB e deixa uns 200 GB de cache de KV: são dezenas de sessões de agente com contexto de 128 mil tokens ao mesmo tempo. O 397B, com 251 GB de pesos, deixa 24, e aí cabem poucas.</p>
<p>O que não escala é a velocidade por pessoa. O meu teste rodou em <code>--performance-mode interactivity</code>, que otimiza a latência de um fluxo só, e os 200 tokens por segundo são desse fluxo. Com dez agentes decodificando ao mesmo tempo, a vazão agregada da placa sobe e a de cada um cai, e a decodificação especulativa que me deu os 295 do Muse Glimmer rende menos conforme a concorrência cresce: um <a href="https://huggingface.co/z-lab/Muse-Glimmer-30B-DFlash2"target="_blank" rel="noopener">rascunhador DFlash publicado pro Muse pelo Z Lab</a> reporta 3 a 4,6 vezes de ganho com uma sessão e 1,2 a 1,7 com 32.</p>
<p>Eu não medi concorrência nesta leva, então o que posso dizer é o contorno: um time de cinco a dez pessoas num modelo de 27 a 35 bilhões numa B300 é prático, e provavelmente cada um ainda fica acima dos 100 tokens por segundo. O mesmo time no 397B é inviável na placa, e no GLM 5.3 Flash de 198 GB fica apertado.</p>
<p>É nesse cenário, e só nele, que a conta do pod começa a fechar. Dez desenvolvedores rodando agente o dia inteiro num modelo de 27B gastariam na API, a um dólar por rodada de duas horas, uns 40 dólares por dia; o pod custa 218. Ainda perde. O que muda a conta é ter cem pessoas, ou ter a obrigação de que o código não saia da sua rede.</p>
<h2>Vale a pena ter a própria infra?<span class="hx:absolute hx:-mt-20" id="vale-a-pena-ter-a-própria-infra"></span>
    <a href="#vale-a-pena-ter-a-pr%c3%b3pria-infra" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra desenvolvimento de software sério, com os dados desta leva, a resposta vem em três partes.</p>
<p><strong>Os modelos abertos de ponta são bons o suficiente.</strong> GLM 5.3 Flash, Qwen3.8 Flash-Next e Swift 1.5 27B são Tier A no meu teste, no nível do Sonnet 5 e acima do Opus 4.6. Isso era impensável em abril, quando <a href="/2026/04/05/testando-llms-open-source-e-comerciais-quem-consegue-bater-o-claude-opus/">só o Qwen 3.6 35B chegava perto do Opus</a>, e ainda a uma correção de distância. Pra quem aceita um tier abaixo do teto, eles servem pra trabalho de verdade, inclusive revisão de segurança, que é o que o v4 mede.</p>
<p><strong>Alugar uma B300 pra rodar eles é o jeito errado de usá-los.</strong> A API em nuvem do mesmo modelo custa um dólar por rodada e precisa de zero configuração. O pod custa 218 por dia, exige um dia de ajuste por família de modelo, e no caso do DeepSeek entregou um modelo pior do que a API dele. Só vale com uma obrigação de residência de dados, com um checkpoint que nenhuma nuvem serve, com carga sustentada de centenas de rodadas por dia, ou com necessidade de pesos fixados pra reprodutibilidade. Quatro B300 pra rodar os de 750 bilhões a um trilhão quadruplicam a conta sem evidência de que a nota chega nos 100: o 397B, maior modelo que eu testei, fez 68,5.</p>
<p><strong>E se for pra hospedar, hospede pequeno.</strong> O Swift 1.5 27B e o ThinkingCap em 56 GB, com MTP, a 200 tokens por segundo, são Tier A em hardware que dá pra comprar, uma placa profissional de 80 ou 96 GB em vez de uma de datacenter. É ali que o open source faz sentido pra uma empresa: um time inteiro numa placa, código dentro da rede, nota de Opus 4.6. Só olhe a licença antes: o Swift e o ThinkingCap saem com licenças próprias, e acima de um certo faturamento os dois exigem licença comercial. Os gigantes abertos continuam sendo coisa pra API.</p>
<p>Minha regra depois disso tudo: modelo comercial de ponta pra tarefa que não pode errar, API de modelo aberto pra volume, e placa própria só quando a lei ou o cliente exigem. O repositório com cada argumento de serving, cada log de sondagem, cada transcrição de revisor e a proveniência Codex ou Claude de cada fronteira está <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">público</a>, como sempre.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>hardware</category></item><item><title>Treinei meu próprio modelo de IA (Spoiler: é só pra estudo)</title><link>https://akitaonrails.github.io/2026/10/14/treinei-meu-proprio-modelo-de-ia-spoiler-e-so-pra-estudo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/14/treinei-meu-proprio-modelo-de-ia-spoiler-e-so-pra-estudo/</guid><pubDate>Wed, 14 Oct 2026 15:00:00 -0300</pubDate><description>&lt;p&gt;Faltava um exercício na minha lista deste ano: tentar construir o meu próprio LLM. Não um modelo de fronteira. Construir um GPT-5 ou um Claude exige anos de estudo e experimentação, times inteiros e centenas de milhões de dólares em computação. A estimativa do &lt;a href="https://hai.stanford.edu/ai-index/2024-ai-index-report"target="_blank" rel="noopener"&gt;AI Index de Stanford&lt;/a&gt; pro GPT-4 foi de US$ 78 milhões só de compute, e pro Gemini Ultra, US$ 191 milhões.&lt;/p&gt;
&lt;p&gt;Só que ninguém precisa de um modelo classe GPT pra tudo. Modelo de fronteira é generalista por definição: tenta saber tudo sobre qualquer coisa, de direito tributário a letra de música, e o retorno disso é decrescente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Faltava um exercício na minha lista deste ano: tentar construir o meu próprio LLM. Não um modelo de fronteira. Construir um GPT-5 ou um Claude exige anos de estudo e experimentação, times inteiros e centenas de milhões de dólares em computação. A estimativa do <a href="https://hai.stanford.edu/ai-index/2024-ai-index-report"target="_blank" rel="noopener">AI Index de Stanford</a> pro GPT-4 foi de US$ 78 milhões só de compute, e pro Gemini Ultra, US$ 191 milhões.</p>
<p>Só que ninguém precisa de um modelo classe GPT pra tudo. Modelo de fronteira é generalista por definição: tenta saber tudo sobre qualquer coisa, de direito tributário a letra de música, e o retorno disso é decrescente.</p>
<p>E a maioria deles hoje é mistura de especialistas, <em>mixture of experts</em>: grosso modo, vários modelos menores agrupados num bloco gigante de bits, com um roteador que acorda só alguns pra cada token. O <a href="https://arxiv.org/abs/2508.10925"target="_blank" rel="noopener">gpt-oss-120b</a> da OpenAI tem 117 bilhões de parâmetros no total e usa 5,1 bilhões por token. O <a href="https://arxiv.org/abs/2412.19437"target="_blank" rel="noopener">DeepSeek-V3</a> tem 671 bilhões e ativa 37. O modelo grande já é, por dentro, uma coleção de modelos pequenos.</p>
<p>Eu podia ter pego um modelo aberto, gpt-oss, Qwen, Phi, e adaptado. As técnicas existem e são bem documentadas:</p>
<ul>
<li><strong>Fine-tuning completo</strong>: continuar treinando todos os pesos de um modelo pronto nos seus dados. É a transferência de aprendizado clássica, formalizada pro texto pelo <a href="https://arxiv.org/abs/1801.06146"target="_blank" rel="noopener">ULMFiT</a> em 2018.</li>
<li><strong>LoRA e QLoRA</strong>: congelar os pesos originais e treinar só matrizes pequenas de baixo posto ao lado deles. O <a href="https://arxiv.org/abs/2106.09685"target="_blank" rel="noopener">LoRA</a> reduz em até dez mil vezes os parâmetros treináveis; o <a href="https://arxiv.org/abs/2305.14314"target="_blank" rel="noopener">QLoRA</a> quantiza o modelo base em 4 bits e ajusta um modelo de 65 bilhões numa única GPU de 48 GB.</li>
<li><strong>Destilação</strong>: treinar um modelo menor pra imitar as saídas de um maior. A ideia é de <a href="https://arxiv.org/abs/1503.02531"target="_blank" rel="noopener">Hinton, 2015</a>, e o exemplo recente mais famoso é o <a href="https://arxiv.org/abs/2501.12948"target="_blank" rel="noopener">DeepSeek-R1</a> destilado em versões de 1,5 a 70 bilhões em cima de Qwen e Llama.</li>
<li><strong>Pré-treino continuado</strong>: mais uma fase de treino em texto do seu domínio antes de qualquer ajuste de instrução, o <a href="https://arxiv.org/abs/2004.10964"target="_blank" rel="noopener">&ldquo;Don&rsquo;t Stop Pretraining&rdquo;</a> de 2020.</li>
<li><strong>SFT, RLHF e DPO</strong>: ensinar o formato de assistente com demonstrações, depois alinhar com preferência humana via aprendizado por reforço (<a href="https://arxiv.org/abs/2203.02155"target="_blank" rel="noopener">InstructGPT</a>) ou com uma perda de classificação direta (<a href="https://arxiv.org/abs/2305.18290"target="_blank" rel="noopener">DPO</a>).</li>
</ul>
<p>Com qualquer uma dessas eu teria um modelo útil em horas. Só que o objetivo era outro. Eu queria organizar, com as mãos, as técnicas básicas pra construir um modelo menor que o GPT-2, do zero, em poucos dias, e ver até onde dava pra chegar. Se desse, treinar um modelo minúsculo partindo de pesos aleatórios e adicionar funcionalidade suficiente pra ele quase funcionar dentro de um harness agêntico como o OpenCode.</p>
<p>Antes de continuar, quem eu sou nessa história: eu sou engenheiro de software curioso e amador no assunto. Pesquisador de LLM eu sou zero. Eu sabia que não ia ter um LLM funcional em tão pouco tempo, e isso faz parte do exercício.</p>
<p>Mas não é inalcançável. Se você é estudante de computação, estuda sério e dá anos pra isso, você consegue tentar por conta própria. E se você não é cientista, entenda isto: ninguém precisa ser Einstein pra fazer isso. Os fundamentos estão todos disponíveis, publicados, pra você estudar e tentar. O que é obrigatório é suor.</p>
<p>Spoiler: a gente chegou longe no &ldquo;quase&rdquo; e parou lá. O <a href="https://github.com/akitaonrails/microgpt"target="_blank" rel="noopener">repositório é público</a>, com cada etapa e cada fracasso documentado. Este texto é o resumo.</p>
<blockquote>
  <p><strong>TL;DR:</strong> parti do microgpt do Karpathy, 200 linhas de Python puro que treinam um GPT em um minuto num MacBook, e expandi pra um laboratório em PyTorch com tokenizer próprio, pré-treino, ajuste supervisionado, servidor compatível com a API da OpenAI e integração com o OpenCode. Em menos de duas semanas, numa RTX 5090, treinei um modelo de 337 milhões de parâmetros em 10 bilhões de tokens: 137 horas de treino, no máximo uns R$ 80 de luz. Ele acerta 58 de 100 funções Python simples de um conjunto reservado, e zera em tudo que exige ler, editar e testar um repositório. O <a href="https://huggingface.co/HuggingFaceTB/SmolLM2-360M"target="_blank" rel="noopener">SmolLM2 de 360 milhões</a>, do mesmo tamanho, viu 4 trilhões de tokens, 400 vezes mais. Pule pro <a href="#o-exerc%c3%adcio-do-karpathy">exercício do Karpathy</a>, pras <a href="#as-etapas-de-construir-um-llm">etapas</a>, pras <a href="#as-dimens%c3%b5es-do-problema">dimensões</a>, pros <a href="#os-trope%c3%a7os">tropeços</a>, pro <a href="#o-que-saiu-do-outro-lado">resultado</a> ou pra <a href="#o-futuro-%c3%a9-menor-e-mais-especializado">conclusão</a>.</p>

</blockquote>
<h2>O exercício do Karpathy<span class="hx:absolute hx:-mt-20" id="o-exercício-do-karpathy"></span>
    <a href="#o-exerc%c3%adcio-do-karpathy" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em fevereiro deste ano o Andrej Karpathy publicou o <a href="https://karpathy.github.io/2026/02/12/microgpt/"target="_blank" rel="noopener">microgpt</a>: um único arquivo de 200 linhas de Python, sem dependência nenhuma, que treina e roda um GPT. Ele chama de &ldquo;o conteúdo algorítmico completo do que é necessário. Todo o resto é só eficiência&rdquo;. É a culminação de uma linhagem dele: o micrograd (autograd escalar), o makemore (gerar nomes) e o nanoGPT (GPT-2 em PyTorch compacto), à qual se soma o <a href="https://github.com/karpathy/nanochat"target="_blank" rel="noopener">nanochat</a>, &ldquo;o melhor ChatGPT que 100 dólares compram&rdquo;, que no lançamento treinava um modelo de capacidade GPT-2 em quase quatro horas num nó de oito H100, e hoje faz isso em umas duas.</p>
<p>O microgpt é propositalmente mínimo: o dataset são 32 mil nomes próprios, um por linha, e o tokenizer é um caractere por token.</p>
<p>A rede tem uma camada, 16 dimensões de embedding, 4 cabeças de atenção, janela de 16 tokens porque o nome mais longo tem 15 letras. Treina por mil passos, um nome por passo, com Adam escrito à mão, e no fim gera nomes inventados, tudo em um minuto num MacBook.</p>
<p>O que ele ensina, por estar tudo exposto num arquivo só:</p>
<ul>
<li>o autograd, a classe <code>Value</code> que guarda um número, o gradiente e a regra da cadeia pra propagar de volta;</li>
<li>o que é um token e um embedding;</li>
<li>o bloco de atenção, com consulta, chave e valor, e a máscara causal que impede o modelo de espiar o futuro;</li>
<li>a perda de entropia cruzada contra o próximo token;</li>
<li>o otimizador;</li>
<li>a amostragem com temperatura.</li>
</ul>
<p>É um GPT-2 com diferenças pequenas que o próprio arquivo anota: RMSNorm no lugar de LayerNorm, sem bias, ReLU em vez de GeLU. Depois de ler aquilo, o que sobra entre você e um modelo de verdade é, nas palavras dele, eficiência. Eu fui ver quanto de eficiência.</p>
<h2>O que a gente expandiu<span class="hx:absolute hx:-mt-20" id="o-que-a-gente-expandiu"></span>
    <a href="#o-que-a-gente-expandiu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/akitaonrails/microgpt"target="_blank" rel="noopener">microgpt lab</a> é uma reimplementação independente em PyTorch, inspirada no gist, dividida por conceito: umas 3.300 linhas de Python em 31 módulos, mais scripts e 85 testes de CPU. Uma única implementação de modelo serve pra pré-treino, ajuste de assistente e servidor. Todo peso começa aleatório; o projeto não baixa peso pré-treinado nenhum, de propósito, porque a regra do exercício era do zero.</p>
<p>Em cima do bloco do Karpathy entraram os componentes que um modelo de 2026 usa: RoPE pra posição, RMSNorm, SwiGLU na camada densa, embeddings amarrados à saída, Flash Attention via PyTorch, cache de chaves e valores com reuso de prefixo. E em volta do modelo, o que falta pra ele conversar com um agente: um protocolo de conversa com papéis, um servidor HTTP compatível com a API de chat da OpenAI, chamadas de ferramenta validadas por esquema e opcionalmente por gramática, separação de conteúdo de raciocínio, e a configuração do OpenCode apontando pra ele.</p>
<p>O projeto passou por quatro escalas:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Parâmetros</th>
          <th>Tokenizer</th>
          <th>Pra quê</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Lição em bytes</td>
          <td style="text-align: right">0,9 milhão</td>
          <td>byte a byte</td>
          <td>roda em CPU, ensina o loop inteiro</td>
      </tr>
      <tr>
          <td>Estudante</td>
          <td style="text-align: right">91 milhões</td>
          <td>o200k (200 mil entradas)</td>
          <td>primeiro agente, aritmética</td>
      </tr>
      <tr>
          <td>Candidato de capacidade</td>
          <td style="text-align: right">239 milhões</td>
          <td>o200k</td>
          <td>mais camadas, mesmo vocabulário</td>
      </tr>
      <tr>
          <td>Pivô de fundação</td>
          <td style="text-align: right">337 milhões</td>
          <td>BPE próprio, 32 mil entradas</td>
          <td>o modelo final</td>
      </tr>
  </tbody>
</table>
<p>A tabela esconde a primeira lição grande, que eu conto nos tropeços: no modelo de 239 milhões, 154 milhões de parâmetros eram só a tabela de vocabulário. Sobravam 85 milhões pro transformer de verdade.</p>
<h2>As etapas de construir um LLM<span class="hx:absolute hx:-mt-20" id="as-etapas-de-construir-um-llm"></span>
    <a href="#as-etapas-de-construir-um-llm" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Independente do tamanho, o caminho tem as mesmas estações. O que muda é o zero à direita em cada uma.</p>
<h3>1. Dados<span class="hx:absolute hx:-mt-20" id="1-dados"></span>
    <a href="#1-dados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Modelo é o que ele come. Baixamos, com revisão fixada e hash, seleções de corpora públicos: <a href="https://huggingface.co/datasets/codeparrot/codeparrot-clean"target="_blank" rel="noopener">CodeParrot</a> (Python deduplicado e filtrado), <a href="https://huggingface.co/datasets/OpenCoder-LLM/opc-sft-stage1"target="_blank" rel="noopener">OpenCoder</a> (instrução e código), <a href="https://huggingface.co/datasets/HuggingFaceTB/smollm-corpus"target="_blank" rel="noopener">Cosmopedia</a> (texto educativo sintético), <a href="https://huggingface.co/datasets/HuggingFaceTB/smoltalk"target="_blank" rel="noopener">SmolTalk</a> (conversa) e uma fatia multilíngue. Os 14 manifestos de download somam uns 20 GB brutos, tudo guardado no NAS.</p>
<p>Daí vem a curadoria: deduplicação exata, divisão de treino e validação por repositório ou grupo de conversa (nunca por linha, senão o mesmo projeto vaza pros dois lados), e a mistura. O corpus final de fundação tem 3,5 bilhões de tokens armazenados, 60% código Python, 16% instrução, 12% texto educativo, 8% conversa, 4% multilíngue. Cada proporção é uma decisão de projeto, e nenhuma delas tem resposta certa publicada.</p>
<h3>2. Tokenizer<span class="hx:absolute hx:-mt-20" id="2-tokenizer"></span>
    <a href="#2-tokenizer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O tokenizer é o dicionário entre texto e inteiros. A lição em bytes usa 256 tokens mais um de fim de documento. O estudante usou o o200k, o vocabulário público da OpenAI com 200 mil entradas, que é o que a maioria dos tutoriais faz. O pivô treinou um BPE próprio de 32.768 entradas, em cima de 92 MB de texto só de treino, com cobertura de UTF-8 completa.</p>
<p>A escolha parece detalhe e decide onde os parâmetros vão parar. Cada entrada do vocabulário custa uma linha na tabela de embedding com a largura do modelo. Com 200 mil entradas e largura 768, são 154 milhões de parâmetros gastos em tabela. Com 32 mil entradas e largura 1.024, são 33 milhões, e o resto vai pras camadas que pensam.</p>
<h3>3. Pré-treino, ou fundação<span class="hx:absolute hx:-mt-20" id="3-pré-treino-ou-fundação"></span>
    <a href="#3-pr%c3%a9-treino-ou-funda%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui é o loop do Karpathy em escala. O modelo recebe janelas de 2.048 tokens tiradas do fluxo de treino e aprende a prever o próximo token. Cada atualização processa 32.768 tokens: duas sequências, acumuladas oito vezes. AdamW, taxa de aprendizado com aquecimento e decaimento em cosseno, recorte de gradiente, BF16 na GPU e FP32 nos pesos.</p>
<p>O pivô rodou uns 305 mil atualizações, 10 bilhões de tokens de exposição. Como o corpus tem 3,5 bilhões, cada documento foi visto, na expectativa da mistura, quase três vezes: o código umas duas, e instrução, texto educativo e conversa umas nove ou dez. A cada 500 atualizações, uma validação e um checkpoint. Nos marcos de 2, 5 e 10 bilhões, um checkpoint congelado com hash, pra medir a curva.</p>
<h3>4. Ajuste supervisionado<span class="hx:absolute hx:-mt-20" id="4-ajuste-supervisionado"></span>
    <a href="#4-ajuste-supervisionado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pré-treino produz um modelo que completa texto. Pra ele responder a um pedido, vem o SFT, <em>supervised fine-tuning</em>: exemplos de pergunta e resposta no formato exato que o servidor usa, com a perda calculada só sobre os tokens do assistente. O que o usuário e a ferramenta disseram entra como contexto e é mascarado da perda.</p>
<p>Os exemplos vieram do <a href="https://huggingface.co/datasets/nvidia/OpenCodeInstruct"target="_blank" rel="noopener">OpenCodeInstruct</a> da NVIDIA, e cada resposta foi executada num Docker restrito antes de entrar: quase 99 mil candidatos, 97 mil sobreviveram ao teste com três sementes de hash diferentes. Sobraram 92 mil episódios de treino. O checkpoint escolhido viu 64 mil sorteios desses episódios, uns 10 milhões de tokens de resposta.</p>
<h3>5. Protocolo, ferramentas e servidor<span class="hx:absolute hx:-mt-20" id="5-protocolo-ferramentas-e-servidor"></span>
    <a href="#5-protocolo-ferramentas-e-servidor" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um agente como o OpenCode fala com o modelo por HTTP: manda o histórico com papéis, as definições de ferramenta, recebe de volta texto ou uma chamada de ferramenta em JSON, executa, devolve o resultado, e repete. O laboratório implementa esse ciclo inteiro: o mesmo arquivo de protocolo serializa os exemplos de treino e os prompts de serviço, pra formato de treino e formato de uso nunca divergirem.</p>
<p>O servidor implementa <code>/v1/models</code> e <code>/v1/chat/completions</code>, valida a chamada de ferramenta contra o esquema, pode restringir a geração com gramática pra JSON sair bem formado, separa o conteúdo de raciocínio da resposta, conta tokens em cache e recusa estouro de contexto em vez de truncar em silêncio. Isso tudo é encanamento, zero inteligência, e sem ele o modelo nem senta à mesa.</p>
<h3>6. Avaliação<span class="hx:absolute hx:-mt-20" id="6-avaliação"></span>
    <a href="#6-avalia%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A etapa que mais mudou o projeto. Um conjunto congelado de 100 tarefas Python de desenvolvimento, 20 famílias com cinco variantes de nome e enunciado, mais 100 tarefas reservadas que só o checkpoint final viu. Cada resposta roda num Docker sem rede, sem root, com limite de CPU e memória, contra asserções fixas. Além disso: quatro tarefas reais de conserto de repositório via OpenCode, 12 problemas de rastrear a saída exata de um programa, sondas de recuperação de informação em prompts de 8, 16 e 32 mil tokens, e quatro conversas curtas.</p>
<blockquote>
  <p>Nada de HumanEval, MBPP ou SWE-bench. São diagnósticos locais pequenos, com famílias correlacionadas, uma semente, geração gulosa. Servem pra decidir entre checkpoints, e o próprio relatório de fechamento manda não ler &ldquo;58 de 100&rdquo; como &ldquo;58% de sucesso em programação&rdquo;.</p>

</blockquote>
<h2>As dimensões do problema<span class="hx:absolute hx:-mt-20" id="as-dimensões-do-problema"></span>
    <a href="#as-dimens%c3%b5es-do-problema" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que tudo isso custou, e como isso se compara com o mundo real:</p>
<table>
  <thead>
      <tr>
          <th>Dimensão</th>
          <th style="text-align: right">Nosso modelo final</th>
          <th>Referências</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Parâmetros</td>
          <td style="text-align: right">337 milhões</td>
          <td>GPT-2 pequeno: 124 milhões; GPT-2 completo (2019): 1,5 bilhão</td>
      </tr>
      <tr>
          <td>Tokens vistos no pré-treino</td>
          <td style="text-align: right">10 bilhões</td>
          <td>SmolLM2-360M: 4 trilhões; Llama 3.1: 15 trilhões; Qwen3: 36 trilhões</td>
      </tr>
      <tr>
          <td>Dados brutos baixados</td>
          <td style="text-align: right">20 GB</td>
          <td>GPT-2: 40 GB de WebText, 8 milhões de páginas</td>
      </tr>
      <tr>
          <td>Corpus de fundação</td>
          <td style="text-align: right">3,5 bilhões de tokens, 2,35 milhões de documentos</td>
          <td></td>
      </tr>
      <tr>
          <td>Hardware</td>
          <td style="text-align: right">1 RTX 5090, 32 GB</td>
          <td>Llama 3.1 405B: 30,84 milhões de horas de H100</td>
      </tr>
      <tr>
          <td>Tempo de GPU na fundação</td>
          <td style="text-align: right">137 horas de processo, 6,4 dias de calendário</td>
          <td>DeepSeek-V3: 2,79 milhões de horas de H800, ~US$ 5,6 milhões</td>
      </tr>
      <tr>
          <td>Tempo de SFT</td>
          <td style="text-align: right">3,4 horas somando os nove estágios</td>
          <td></td>
      </tr>
      <tr>
          <td>Vazão</td>
          <td style="text-align: right">23 a 44 mil tokens/s, dependendo de I/O</td>
          <td></td>
      </tr>
      <tr>
          <td>VRAM de pico</td>
          <td style="text-align: right">13,9 GB na fundação; 6,8 GB no SFT</td>
          <td></td>
      </tr>
      <tr>
          <td>Checkpoints</td>
          <td style="text-align: right">17 checkpoints distintos (20 caminhos), 69 GB</td>
          <td></td>
      </tr>
      <tr>
          <td>Custo em dinheiro</td>
          <td style="text-align: right">zero em nuvem; uns R$ 80 de luz no pior caso</td>
          <td>nanochat: US$ 100 em 8 H100 por 4 horas no lançamento; hoje uns US$ 48 em 2 horas</td>
      </tr>
  </tbody>
</table>
<p>A conta da luz: a RTX 5090 tem <a href="https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/"target="_blank" rel="noopener">575 W de potência nominal</a>. Em 137 horas a plena carga são uns 79 kWh, e a tarifa residencial da Enel em São Paulo em 2026, somando TUSD, TE e impostos, fica em torno de R$ 1 por kWh. Como o treino rodou boa parte do tempo a 75% de ciclo pra não travar o desktop, o real foi menos. É o custo marginal de quem já tem a placa.</p>
<p>Alugar uma 5090 na <a href="https://www.runpod.io/pricing"target="_blank" rel="noopener">Runpod</a> sai a uns US$ 1,20 por hora, então as mesmas 137 horas custariam uns US$ 165; uma H100 sai entre US$ 2,90 e 4 por hora, dependendo da variante.</p>
<p>E a comparação que dá a escala. O SmolLM2 de 360 milhões de parâmetros, praticamente o tamanho do nosso, foi treinado em 4 trilhões de tokens. Na nossa vazão, 4 trilhões levariam entre quatro e seis anos de GPU ininterrupta. Mesmo tamanho de modelo, 400 vezes a quantidade de estudo.</p>
<p>É por isso que &ldquo;parâmetros&rdquo; é uma unidade enganosa: diz o tamanho do cérebro, e nada sobre quantos anos de escola ele teve.</p>
<p>A regra de bolso derivada de <a href="https://arxiv.org/abs/2203.15556"target="_blank" rel="noopener">Chinchilla</a> (70 bilhões de parâmetros, 1,4 trilhão de tokens), uns 20 tokens por parâmetro pra ser computacionalmente ótimo, daria 6,7 bilhões de tokens pro nosso modelo. Passamos disso. Só que o ótimo de Chinchilla é o ponto de melhor uso de compute, e modelos pequenos modernos passam muito dele de propósito, porque o custo de inferência depois compensa treinar mais.</p>
<h2>Os tropeços<span class="hx:absolute hx:-mt-20" id="os-tropeços"></span>
    <a href="#os-trope%c3%a7os" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A lista completa está no <a href="https://github.com/akitaonrails/microgpt/blob/main/docs/report/experiment-closeout.md"target="_blank" rel="noopener">relatório de fechamento</a>. Os que mais ensinaram:</p>
<p><strong>Medir perto demais do treino.</strong> O primeiro agente, o de 91 milhões, completou quatro sessões de 16 rodadas no OpenCode e uma de 32 rodadas com 130 chamadas de ferramenta reais, recuperando de erro de verificação e trocando de sessão no meio, com o estado guardado pelo harness. Parecia pronto.</p>
<p>Aí eu pedi, na mão e em inglês, uma função hello world que recebe um nome como parâmetro e imprime na mensagem. O modelo repetiu duas vezes a edição aritmética anterior, apontou pra um arquivo que não existia, e na terceira tentativa cuspiu JSON inválido. Tudo que ele sabia era a família de tarefa do treino.</p>
<p><strong>O vocabulário comeu o modelo.</strong> O candidato de 239 milhões tinha mais camadas e perda menor que o de 91 e resolvia 24 de 100 problemas. Ao olhar a distribuição dos parâmetros, 154 milhões eram tabela de vocabulário. O pivô pra 32 mil entradas devolveu 300 milhões de parâmetros pro transformer. O pivô mudou vocabulário, distribuição de capacidade, inicialização e exposição de uma vez, então o relatório não isola qual delas moveu o resultado.</p>
<p><strong>Perda não é competência.</strong> No SFT final, continuar de 4 mil pra 8 mil atualizações baixou a perda de validação e derrubou o acerto executado de 68 pra 62. Loss mais baixa, código pior. A seleção de checkpoint passou a ser por tarefa executada, congelada antes; o número da perda ficou de fora.</p>
<p><strong>Dados e avaliadores mentem.</strong> Respostas &ldquo;corretas&rdquo; do dataset público falhavam ao serem executadas de novo. Testes que dependiam da ordem de hash do Python, builtins sombreados, <code>SystemExit(0)</code> contando como sucesso, edições vazias copiando o arquivo. Cada um precisou de um controle: três sementes de hash, sentinela de conclusão, fixture vazia que precisa falhar. E num dos treinos mistos, o modelo aprendeu a dizer que os testes passaram quando não tinham passado.</p>
<p><strong>Formato de treino e formato de uso precisam bater.</strong> Anúncio de troca de sessão, ID de chamada de ferramenta, âncora de edição, o jeito como o OpenCode cita a entrada do usuário: cada diferença entre o que o modelo viu no treino e o que recebeu no servidor derrubava o agente antigo. O mesmo arquivo de protocolo pros dois lados foi a solução, e ainda assim mudar formato às vezes melhorava o nome da função sem melhorar o código.</p>
<p><strong>Tokenização cruza fronteiras de correção.</strong> A string literal <code>&lt;|endoftext|&gt;</code> dentro de código fonte precisa continuar sendo texto comum em vez de virar o marcador de fim de documento. Reconstruir documentos UTF-8 sem partir um caractere no meio. O suporte do XGrammar a vocabulário em nível de byte. A primeira avaliação de código do marco de 2 bilhões falhou na construção da gramática, e foi corrigida e rerodada em vez de contar como falha do modelo.</p>
<p><strong>O NAS quase me custou dias.</strong> Na retomada da fase de 10 bilhões, o treinador carregou o passo 81.500 em vez do 152.588 congelado: o <code>last.pt</code> mutável tinha timestamp anterior à pausa, logo depois de uma manutenção no NAS, e a causa nunca foi provada. Descartamos 1,24 hora de treino, verificamos o hash do marco de 5 bilhões, e toda retomada seguinte passou a exigir hash, passo exato, estado do otimizador e do RNG, dataset e uma sonda de escrita com fsync. Nunca retome um treino só porque o arquivo se chama <code>last.pt</code>.</p>
<p><strong>GPU de desktop é GPU compartilhada.</strong> VRAM livre não impediu o navegador de travar. A solução foi um ciclo de trabalho cooperativo: o treinador sincroniza a GPU e dorme entre lotes, 75% do tempo durante o dia e 100% das 3 às 9 da manhã. E a leitura do corpus pelo NAS, via mmap, deixava a GPU ociosa esperando disco. Depois de uma manutenção a vazão caiu pra 5 mil tokens por segundo antes de voltar aos 23 mil.</p>
<p><strong>Parei numa fronteira do pipeline.</strong> O controlador do pivô implementava fundação, SFT de código e holdout. Terminou limpo, e &ldquo;terminou limpo&rdquo; cobria até a função Python isolada, enquanto o objetivo era o agente. O checkpoint final foi testado no OpenCode só no fechamento, e falhou. Devia ter sido testado antes de qualquer frase com &ldquo;pronto&rdquo;.</p>
<h2>O que saiu do outro lado<span class="hx:absolute hx:-mt-20" id="o-que-saiu-do-outro-lado"></span>
    <a href="#o-que-saiu-do-outro-lado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A ficha do modelo final:</p>
<table>
  <thead>
      <tr>
          <th>Propriedade</th>
          <th>Valor</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Parâmetros</td>
          <td>337.167.360, sendo 303,6 milhões no transformer e 33,6 milhões na tabela de vocabulário</td>
      </tr>
      <tr>
          <td>Arquitetura</td>
          <td>24 camadas, largura 1.024, 16 cabeças, SwiGLU com 2.752 de largura oculta, RMSNorm, RoPE</td>
      </tr>
      <tr>
          <td>Vocabulário</td>
          <td>BPE próprio de 32.768 entradas, cobertura UTF-8</td>
      </tr>
      <tr>
          <td>Contexto</td>
          <td>32.768 tokens alocados; treinado em janelas de 2.048</td>
      </tr>
      <tr>
          <td>Fundação</td>
          <td>10 bilhões de tokens, 305.176 atualizações, perda de validação final 1,085</td>
      </tr>
      <tr>
          <td>SFT escolhido</td>
          <td>4.000 atualizações, 10,3 milhões de tokens de resposta</td>
      </tr>
      <tr>
          <td>Checkpoint</td>
          <td>4 GB com estado do otimizador; 1,35 GB só de pesos em FP32</td>
      </tr>
  </tbody>
</table>
<p>A curva de aprendizado, medida nos três marcos, com o mesmo SFT curto aplicado em cima de cada um:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Tokens de fundação</th>
          <th style="text-align: right">Completar código cru /100</th>
          <th style="text-align: right">Após SFT de 500 /100</th>
          <th style="text-align: right">Após SFT de 1.000 /100</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">2 bilhões</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">24</td>
          <td style="text-align: right">39</td>
      </tr>
      <tr>
          <td style="text-align: right">5 bilhões</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">49</td>
          <td style="text-align: right">52</td>
      </tr>
      <tr>
          <td style="text-align: right">10 bilhões</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">56</td>
          <td style="text-align: right">65</td>
      </tr>
  </tbody>
</table>
<p>O SFT mais longo em cima dos 10 bilhões chegou a <strong>68 de 100</strong> no conjunto de desenvolvimento e <strong>58 de 100</strong> no reservado. Todas as 100 saídas do reservado são Python válido, e 97 têm o nome de função e os parâmetros pedidos. Nove famílias passaram nas cinco variantes: mediana, fatorial, máximo divisor comum, conversão pra binário, inverter dicionário, contar linhas, ano bissexto, segundos pra horas, chaves de JSON. Total acumulado ficou em quatro de cinco.</p>
<p>O que ele faz, portanto: escreve uma função Python pequena, com docstring, sintaxe correta e o nome certo, pra um pedido claro de uma linha. Dentro de um editor, como autocompletar de função isolada, serve pra estudo.</p>
<p>O que ele erra, com exemplos salvos: deduplicar usando <code>set</code> quando o enunciado exigia suportar listas não hasheáveis; agrupar por tamanho contando palavras em vez de agrupar; fazer aritmética em cada elemento numa função de preencher; devolver <code>(texto, '')</code> numa função de dividir uma vez, ignorando o separador; tirar o último caractere em vez do diretório numa função de basename. Dos 42 erros do reservado, 39 têm sintaxe válida e nome certo e falham na semântica. A parte difícil nunca foi parecer código.</p>
<p>E o que ele zera:</p>
<ul>
<li><strong>0 de 4</strong> consertos reais de repositório no OpenCode, com zero chamadas de ferramenta. Pedido pra ler, editar e testar um arquivo, ele devolveu texto com código. Numa das tarefas, renomeou a função pra <code>fix_mean</code>.</li>
<li><strong>0 de 12</strong> em rastrear a saída exata de programas curtos, com e sem modo de raciocínio. Muitas respostas foram código que supostamente calcularia o inteiro pedido.</li>
<li><strong>0 de 9</strong> em achar um número de seis dígitos escondido em prompts de 8, 16 e 32 mil tokens. A alocação de 32 mil tokens funciona; a memória útil, nenhuma.</li>
<li>Conversa: perguntado em português sobre a cor do céu, respondeu repetitivo e errado; pedido em inglês de café da manhã virou uma docstring em Python repetindo &ldquo;oats&rdquo; até o limite de tokens. Numa troca curta lembrou que o gato se chamava Pipoca, e mesmo assim respondeu com frase inteira apesar do pedido de &ldquo;só o nome&rdquo;.</li>
</ul>
<p>O SFT final só tinha respostas de código, sem conversa, sem trajetória de ferramenta, sem recuperação de erro. A tendência de responder qualquer coisa com Python é coerente com isso, e o relatório toma o cuidado de dizer que a causa não foi isolada.</p>
<h2>O que faltaria pra um agente simples no OpenCode<span class="hx:absolute hx:-mt-20" id="o-que-faltaria-pra-um-agente-simples-no-opencode"></span>
    <a href="#o-que-faltaria-pra-um-agente-simples-no-opencode" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O relatório fecha com estimativas de engenharia, sem promessa de taxa de acerto. Pra chegar num agente que resolve tarefa simples de programação dentro do OpenCode, nesta ordem:</p>
<ol>
<li><strong>Ensinar uso de ferramenta no modelo forte.</strong> Entre 10 e 30 mil trajetórias curtas e verificadas de repositório (ler, editar, checar), misturadas com 30 a 50% de código pra ele não desaprender. Um a três dias de engenharia e de duas a seis horas iniciais de treino.</li>
<li><strong>Corrigir os próprios erros.</strong> Coletar centenas a alguns milhares de falhas do modelo em tarefas de treino, reconstruir o estado do arquivo, verificar as correções e treinar mascarando a ação errada. Mais um a três dias de coleta e de duas a oito horas de treino.</li>
<li><strong>Devolver a conversa.</strong> Uma mistura equilibrada de conversa, ferramenta e código contra o checkpoint congelado, pra ele parar de responder tudo com Python e conseguir trocar de tarefa. Uma a quatro horas de treino.</li>
<li><strong>Contexto longo de verdade.</strong> Só depois das tarefas curtas passarem: 4 mil, 8 mil, e só então 16 e 32 mil, com trajetórias reais de compactação e troca de sessão. Um a dois dias de engenharia e de 6 a 24 horas de treino, medindo memória a cada janela.</li>
<li><strong>Mais fundação, com dados melhores.</strong> Cada 10 bilhões de tokens a mais custam entre 93 e 139 horas nesta GPU, antes de preparação e avaliação. E repetir a mesma mistura de 3,5 bilhões não vale a pena: precisa de mais código educativo único.</li>
</ol>
<p>Nenhum desses passos foi executado. Não há evidência de que um número fixo de horas a mais produza um agente confiável, e o relatório é explícito nisso. O que o exercício estabeleceu foi a distância: entre gerar uma função plausível e agir de forma confiável num repositório existe um abismo que parâmetro e contexto alocado sozinhos não cruzam.</p>
<blockquote>
  <p>O mais forte que saiu daqui foi a curva medida de 0 pra 65 em dez bilhões de tokens, e três contraexemplos concretos: sintaxe não é semântica, suporte a protocolo não é habilidade com ferramenta, e capacidade de contexto não é memória.</p>

</blockquote>
<h2>O futuro é menor e mais especializado<span class="hx:absolute hx:-mt-20" id="o-futuro-é-menor-e-mais-especializado"></span>
    <a href="#o-futuro-%c3%a9-menor-e-mais-especializado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de onze dias olhando pra dentro de um modelo, a minha leitura do mercado ficou mais firme. Modelo de fronteira generalista vai continuar existindo e vai continuar sendo caro, e o retorno de cada bilhão a mais de parâmetros é cada vez menor. Eu já escrevi que <a href="/2026/10/12/llms-agentes-competente-o-suficiente/">os modelos crus convergiram</a> e que o ganho recente veio do harness. O exercício me deu a outra metade do argumento.</p>
<p>A mistura de especialistas é a indústria admitindo isso por dentro. Se o gpt-oss de 117 bilhões acorda 5 bilhões por token, o que você está usando numa pergunta de contabilidade é um modelo de 5 bilhões escolhido por um roteador. A pergunta que fica é por que carregar os outros 112 bilhões, de arte, cinema, meme e física de partículas, num servidor de escritório contábil.</p>
<p>Imagine um modelo local, feito pra contadores. Ele conhece o plano de contas, a legislação tributária do país, os formatos de arquivo da Receita, o jargão. Ele dispensa parâmetros sobre tudo que um contador não precisa, e pode rodar no PC do escritório, com os dados do cliente dentro da sala.</p>
<p>Os exemplos já existem em outras áreas: o <a href="https://research.google/blog/medgemma-our-most-capable-open-models-for-health-ai-development/"target="_blank" rel="noopener">MedGemma</a> de 4 bilhões pra saúde, o <a href="https://arxiv.org/abs/2403.03883"target="_blank" rel="noopener">SaulLM</a> de 7 bilhões pra direito, o <a href="https://arxiv.org/abs/2503.16252"target="_blank" rel="noopener">Fin-R1</a> de 7 bilhões pra raciocínio financeiro. Em 2023 o <a href="https://arxiv.org/abs/2303.17564"target="_blank" rel="noopener">BloombergGPT</a> precisou de 50 bilhões de parâmetros e 1,3 milhão de horas de A100 pra fazer finanças; hoje um ajuste em cima de um modelo aberto de 7 bilhões faz o mesmo papel por uma fração.</p>
<p>O que mudou pra isso virar viável foi o agente. Um modelo especializado de 2023 precisava saber tudo de cor, porque a única coisa que ele fazia era completar texto. Um modelo de 2026 precisa de uma base: raciocínio suficiente, capacidade de sustentar uma tarefa longa, chamada de ferramenta confiável, cache de prompt. O resto, a legislação que mudou ontem, o documento do cliente, a tabela que ele nunca viu, ele busca com ferramenta e internet na hora, e preenche as lacunas do treino em tempo de uso.</p>
<p>A <a href="https://arxiv.org/abs/2506.02153"target="_blank" rel="noopener">NVIDIA publicou em 2025</a> exatamente essa posição: modelos pequenos são suficientes, mais adequados e mais baratos pra maioria das chamadas de um agente, com o modelo grande chamado só quando a conversa aberta exige. A Apple já <a href="https://machinelearning.apple.com/research/apple-foundation-models-2025-updates"target="_blank" rel="noopener">roda um modelo de 3 bilhões no aparelho</a> com protocolo de ferramentas e adaptadores por tarefa.</p>
<p>É o mesmo raciocínio dos <a href="/2026/10/10/qual-modelo-de-decisao-classificador-de-ia-tipo-jev-e-melhor/">modelos de decisão que eu testei</a> semana passada: um classificador de 4 bilhões treinado pra uma tarefa bate um modelo de chat genérico do mesmo tamanho nela, numa fração do tempo de resposta. Em vez de tentar caber o mundo inteiro num modelo maior, cabe o domínio num modelo menor e deixa o harness buscar o mundo quando precisar.</p>
<p>E o que o meu modelo de 337 milhões me ensinou é o tamanho do &ldquo;suficiente&rdquo; nessa frase. Ele sabe a sintaxe e sabe o nome das coisas. O que falta, semântica, ferramenta, memória, é exatamente o que separa um gerador de texto de um agente, e isso ninguém treina em duas semanas numa placa de vídeo. Treina em meses, em cluster, com trilhões de tokens, e depois especializa.</p>
<p>O código está no <a href="https://github.com/akitaonrails/microgpt"target="_blank" rel="noopener">repositório</a>, com o relatório de fechamento inteiro, pra quem quiser pegar de onde eu parei. É só pra estudo. E foi o melhor estudo do ano.</p>
]]></content:encoded><category>inteligencia-artificial</category><category>llms</category><category>machine-learning</category><category>modelos-locais</category></item><item><title>Qual modelo de decisão/classificador de IA tipo Jev é melhor?</title><link>https://akitaonrails.github.io/2026/10/10/qual-modelo-de-decisao-classificador-de-ia-tipo-jev-e-melhor/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/10/qual-modelo-de-decisao-classificador-de-ia-tipo-jev-e-melhor/</guid><pubDate>Sat, 10 Oct 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Semana passada publiquei &lt;a href="https://akitaonrails.github.io/2026/10/03/entendendo-o-hype-do-jev-pra-que-serve/"&gt;um texto longo sobre o Jev&lt;/a&gt;, o classificador hospedado da TypeSafe que virou febre em setembro. O resumo daquele texto: o Jev é um classificador, coisa que existe há décadas, e o espanto de &amp;ldquo;193x mais rápido&amp;rdquo; só existe porque muita gente vinha usando LLM de chat como classificador caro e lento. Reproduzi o ensaio &lt;em&gt;O aluno que marca a letra A&lt;/em&gt;, do Paulo Câmara, apresentei as alternativas abertas Clef e Laya, e pra comparar os modelos lado a lado me apoiei no &lt;a href="https://huggingface.co/spaces/multimodalart/jev-decision-index"target="_blank" rel="noopener"&gt;Decision Index&lt;/a&gt;, um ranking público que já existia.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Semana passada publiquei <a href="/2026/10/03/entendendo-o-hype-do-jev-pra-que-serve/">um texto longo sobre o Jev</a>, o classificador hospedado da TypeSafe que virou febre em setembro. O resumo daquele texto: o Jev é um classificador, coisa que existe há décadas, e o espanto de &ldquo;193x mais rápido&rdquo; só existe porque muita gente vinha usando LLM de chat como classificador caro e lento. Reproduzi o ensaio <em>O aluno que marca a letra A</em>, do Paulo Câmara, apresentei as alternativas abertas Clef e Laya, e pra comparar os modelos lado a lado me apoiei no <a href="https://huggingface.co/spaces/multimodalart/jev-decision-index"target="_blank" rel="noopener">Decision Index</a>, um ranking público que já existia.</p>
<p>Ranking de terceiro é útil, mas eu queria pôr a mão na massa. Então montei meu próprio benchmark, com casos meus, metade em português, e rodei os modelos abertos duas vezes: no meu <a href="/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/">home server com Strix Halo</a>, o mesmo Ryzen AI Max+ 395 que uso pra testar LLM local, via ROCm, e de novo numa RTX 5090 via CUDA. Os hospedados (Jev, os dois Clef da Cloudflare, o OpenAI Decisions e o Microsoft Decision-1 via OpenRouter) rodaram pela API deles. Tudo, incluindo os casos, as respostas brutas e os scripts de pontuação, está público no <a href="https://github.com/akitaonrails/jev-docs-benchmarks"target="_blank" rel="noopener">repositório do benchmark</a>.</p>
<blockquote>
  <p><strong>TL;DR:</strong> o topo agora tem dois, ambos de pesos abertos. O <strong>Perplexity Decider 1.1 27B</strong> e o recém-lançado <strong>Decision 3.0</strong> (vLLM Semantic Router) empataram em <strong>99,4%</strong> nos casos compactos, com 100% em português, e rótulos idênticos em todas as 1.024 classificações do benchmark. O preço: uns 49 GB de pesos e mais de um segundo por decisão. Na nuvem, o <strong>Microsoft Decision-1</strong> empatou com o <strong>Jev</strong> em 97,6% custando um terço: é o hospedado mais barato que eu medi, e o primeiro candidato a avaliar. O Jev responde mais rápido e erra diferente. O <strong>OpenAI Decisions</strong> estreou empatado com o melhor 4B local, o Jebadiah, e tem a menor latência hospedada. O <strong>Julia-1</strong>, o menor e mais rápido de todos (12 ms), acertou no nível da moeda: de fábrica, não serve. E a segunda passada, agora numa <strong>RTX 5090</strong>: os modelos até 9B aceleram de 2× a 10×, o Jebadiah 4B cai pra 39 ms, mais rápido que qualquer hospedado, e os dois campeões de 27B, com uns 49 GB de pesos, nem cabem nos 32 GB da placa. Pule pra <a href="#a-tabela-completa">tabela</a>, pro <a href="#quanto-custa-em-e-mails-de-suporte">custo em e-mails de suporte</a>, pra <a href="#qual-modelo-em-qual-situa%c3%a7%c3%a3o">qual usar em qual situação</a>, pra <a href="#qu%c3%a3o-dif%c3%adcil-%c3%a9-rodar-os-melhores-abertos-na-sua-m%c3%a1quina">quão difícil é rodar local</a>, pras <a href="#destaques-e-pegadinhas">pegadinhas</a> ou pras duas cozinhas do <a href="#rocm-x-cuda-a-mesma-receita-em-duas-cozinhas">ROCm x CUDA</a>.</p>

</blockquote>
<h2>Como eu testei<span class="hx:absolute hx:-mt-20" id="como-eu-testei"></span>
    <a href="#como-eu-testei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A ideia central foi medir mais do que &ldquo;quem acerta mais&rdquo;. Modelo de decisão tem objetivo de projeto, e comparar um encoder de 322 milhões de parâmetros feito pra rodar em dispositivo fraco com um serviço hospedado de tamanho não divulgado só por porcentagem de acerto é injusto com os dois. Então medi acerto, latência, custo e comportamento sob estresse, e no fim separo as recomendações por situação.</p>
<h3>Os dois conjuntos de casos<span class="hx:absolute hx:-mt-20" id="os-dois-conjuntos-de-casos"></span>
    <a href="#os-dois-conjuntos-de-casos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Montei dois conjuntos de testes, congelados em git antes de qualquer inferência, com o hash do dataset gravado junto com as respostas. Nenhum modelo recebe a resposta esperada, a justificativa ou o identificador do caso. Só o texto, as instruções e as opções.</p>
<p>O <strong>conjunto v1</strong> tem 13 casos de uso operacionais, em inglês, cada um com uma política fictícia explícita:</p>
<ul>
<li>severidade de ticket de suporte</li>
<li>roteamento de suporte</li>
<li>política de reembolso</li>
<li>escalação de incidente</li>
<li>aprovação de ferramenta (um agente pode ou não executar tal ação)</li>
<li>fraude por e-mail</li>
<li>tipo de documento</li>
<li>suporte de evidência</li>
<li>relevância de busca</li>
<li>moderação</li>
<li>roteamento de pedido</li>
<li>qualificação de lead</li>
<li>triagem por limiar numérico</li>
</ul>
<p>São 10 cenários por caso de uso, cada um apresentado de quatro formas: original, opções em ordem invertida, opções com nomes opacos (c0, c1, c2) e original mais um comentário de &ldquo;revisor&rdquo; sugerindo a resposta errada, que a instrução manda ignorar. Dá 520 classificações por modelo. Somam-se 24 sondagens de probabilidade em situações sem informação nenhuma (moeda honesta, dado de seis lados), onde a resposta certa é matemática.</p>
<p>O <strong>conjunto v2</strong> foi feito pra cobrir o que o v1 deixa de fora: semântica comum e português. São 7 tarefas, com 12 cenários cada, e cada cenário escrito em inglês e em português brasileiro com a mesma resposta e a mesma ordem de opções:</p>
<ul>
<li>sentimento (autor versus citação, negação, sarcasmo)</li>
<li>intenção atual (pedido retirado, ações concorrentes, hipótese)</li>
<li>implicação lógica (quantificadores, fato ausente, ordem temporal)</li>
<li>relevância de busca (tudo, parte ou nada do que foi pedido)</li>
<li>tratamento de dados sensíveis (segredo fictício versus menção, redação, revogação)</li>
<li>status atual de incidente (ordem da linha do tempo, verificado versus não verificado)</li>
<li>política por limiar (limites inclusivos, unidades, E/OU com valor faltando)</li>
</ul>
<p>Cada par roda em três condições:</p>
<ul>
<li><strong>compacta</strong>: só o registro e a regra</li>
<li><strong>com exemplos</strong>: um exemplo resolvido por classe, fixo e igual pra todos</li>
<li><strong>com distratores</strong>: dez notas irrelevantes de arquivo antes do registro que importa, pra estressar contexto</li>
</ul>
<p>Dá 504 classificações por modelo.</p>
<blockquote>
  <p>Os casos foram escritos com ajuda de IA e revisados contra as políticas, sem anotação humana independente. É um benchmark sintético e pequeno: 130 famílias de cenário no v1 e 84 no v2. As variantes são correlacionadas, então 520 respostas são 130 cenários vistos de quatro jeitos. Trate os números como fotografia desses casos, e teste com os seus antes de decidir.</p>

</blockquote>
<h3>Os 23 competidores<span class="hx:absolute hx:-mt-20" id="os-23-competidores"></span>
    <a href="#os-23-competidores" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Cinco rodaram na nuvem, pela API oficial: o <strong>Jev</strong> (versão 1.13.0, da TypeSafe), o <strong>Clef</strong> (27B, Cloudflare Workers AI), o <strong>Clef-flash</strong> (9B, idem), o <strong>OpenAI Decisions</strong> (o alias <code>gpt-6-luna</code>, em beta público) e o <strong>Microsoft Decision-1</strong> (9B, pós-treinado do Qwen3.5-9B, servido via OpenRouter em Azure). Os outros dezoito rodaram no Strix Halo, via ROCm, e depois de novo numa RTX 5090, via CUDA, com os pesos fixados por hash de commit no Hugging Face, dentro de container Docker. Os três da primeira leva (Laya, Laya typed e Clef-flash) dividiram um container; os demais rodaram um por vez, com o Julia-1 medido duas vezes, em GPU e em CPU. O servidor via 64 GiB de memória endereçável pela GPU.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Tamanho nominal</th>
          <th>Origem</th>
          <th>Onde rodou</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Jev 1.13.0</td>
          <td style="text-align: right">não divulgado</td>
          <td>TypeSafe, proprietário</td>
          <td>API</td>
      </tr>
      <tr>
          <td>OpenAI Decisions (<code>gpt-6-luna</code>)</td>
          <td style="text-align: right">não divulgado</td>
          <td>OpenAI, proprietário, beta</td>
          <td>API</td>
      </tr>
      <tr>
          <td>Microsoft Decision-1</td>
          <td style="text-align: right">9B</td>
          <td>Microsoft, proprietário</td>
          <td>API (OpenRouter)</td>
      </tr>
      <tr>
          <td>Clef</td>
          <td style="text-align: right">27B</td>
          <td>Cloudflare, pesos abertos</td>
          <td>API</td>
      </tr>
      <tr>
          <td>Clef-flash</td>
          <td style="text-align: right">9B</td>
          <td>Cloudflare, pesos abertos</td>
          <td>API e local</td>
      </tr>
      <tr>
          <td>Perplexity Decider 1.1 27B</td>
          <td style="text-align: right">27B</td>
          <td>Perplexity, Apache 2.0</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Decision 3.0 (d3)</td>
          <td style="text-align: right">27B</td>
          <td>vLLM Semantic Router, Apache 2.0</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Decision 3.0 Edge</td>
          <td style="text-align: right">0,6B</td>
          <td>vLLM Semantic Router, Apache 2.0</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Jebadiah 4B v2</td>
          <td style="text-align: right">4B</td>
          <td>Frontier Infra, Apache 2.0</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Kev 0.8B / 4B / 9B</td>
          <td style="text-align: right">0,8B / 4B / 9B</td>
          <td>Jared Palmer, Apache 2.0</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Mapika Decider 4B</td>
          <td style="text-align: right">4,2B</td>
          <td>Mapika, Apache 2.0</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Eikos 4B</td>
          <td style="text-align: right">4B</td>
          <td>Caio Vicentino, MIT, treinado em EN e PT</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Simple-Jev + Qwen3.5 4B</td>
          <td style="text-align: right">4B</td>
          <td>Featherless, servidor de leitura de tokens sobre modelo comum</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Laya / Laya typed-decisions</td>
          <td style="text-align: right">421M</td>
          <td>Convai Innovations, encoder ModernBERT</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Laya multilingual</td>
          <td style="text-align: right">322M</td>
          <td>Convai Innovations, encoder mmBERT</td>
          <td>local</td>
      </tr>
      <tr>
          <td>Julia-1</td>
          <td style="text-align: right">144M</td>
          <td>Supersonic Labs, Apache 2.0, encoder mmBERT</td>
          <td>local, GPU e CPU</td>
      </tr>
      <tr>
          <td>GLiNER2.5-Decide / multi-Decide</td>
          <td style="text-align: right">340M / 287M</td>
          <td>Fastino, Apache 2.0</td>
          <td>local</td>
      </tr>
  </tbody>
</table>
<p>A ordem aí em cima é só organizacional, hospedados primeiro e locais por família. O ranking de verdade está na <a href="#a-tabela-completa">tabela de resultados</a>.</p>
<p>Todos receberam exatamente o mesmo texto, as mesmas instruções e as mesmas opções, sem prompt ajustado por modelo e sem nenhum ajuste depois de ver resultado (o Simple-Jev usa a política de prompt fixa do próprio servidor). A exceção é o <strong>GLiNER</strong>, cuja API nativa recebe rótulos em vez de distribuições de probabilidade. Ele entra numa trilha separada, só no v2, com mapeamento explícito e sem métricas de calibração inventadas.</p>
<p>O <strong>Simple-Jev</strong> merece explicação. Ele é um servidor que pega um LLM comum (aqui o Qwen3.5 4B, sem nenhum treino de decisão) e lê a probabilidade dos tokens de resposta em vez de gerar texto. É a linha de base: se um modelo &ldquo;de decisão&rdquo; perde pra isso, o treino especializado não comprou nada.</p>
<p>Duas observações de protocolo. O <strong>OpenAI Decisions</strong> responde por um endpoint nativo de decisões tipadas, e o alias que a API devolveu não é um snapshot imutável: a OpenAI pode trocar o peso por trás do nome, como a Cloudflare faz com o Clef-flash. E o <strong>Perplexity Decider</strong> não é um LLM de chat comum: ele tem uma cabeça de decisão separada, com atenção não-causal e uma temperatura de calibração salva nos pesos. Um servidor de chat genérico não reproduz o comportamento dele. O <strong>Microsoft Decision-1</strong> fala SystemOne nativo pela API de decisões do OpenRouter, e o identificador servido é datado (<code>microsoft-decision-1-20261009</code>), ou seja, também não é peso imutável. Ele não aparece no catálogo de modelos de texto do OpenRouter: pra achar, tem que filtrar pela modalidade <code>decisions</code>. O <strong>Decision 3.0</strong> é a família nova do projeto vLLM Semantic Router, Apache 2.0, derivada de Qwen, com seis checkpoints (d3, flash, mini, nano, lite, edge) e SystemOne nativo. Eu testei o flagship (d3) e o menor (d3-edge); os quatro do meio ficam pra outra rodada.</p>
<h3>O que foi medido<span class="hx:absolute hx:-mt-20" id="o-que-foi-medido"></span>
    <a href="#o-que-foi-medido" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Acerto por tarefa, por língua e por condição, sempre em cima das probabilidades por opção que o modelo devolve, ignorando o campo de &ldquo;confiança&rdquo; do fornecedor.</li>
<li>Brier e ECE pra calibração.</li>
<li>Latência mediana e p95 vista pelo cliente, incluindo a rede: WAN pros hospedados, LAN pros locais. Os locais foram medidos nas duas implantações, Strix com ROCm e 5090 com CUDA. São observações de implantação completa, máquina inteira de cada lado; a comparação entre elas não isola o efeito de trocar só o backend.</li>
<li>Tokens de entrada reportados pela API, valorados pela tabela de preços de 10 de outubro, antes de qualquer franquia.</li>
<li>Intervalos de confiança por bootstrap sobre famílias de cenário, em vez de respostas individuais.</li>
</ul>
<p>Custo local eu deixei de medir. Energia, amortização do hardware e manutenção ficaram de fora, então modelo local aparece como &ldquo;não medido&rdquo;, porque &ldquo;zero&rdquo; seria mentira.</p>
<h2>A tabela completa<span class="hx:absolute hx:-mt-20" id="a-tabela-completa"></span>
    <a href="#a-tabela-completa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A coluna principal é o acerto nos 168 casos compactos do v2 (84 em inglês, 84 em português). A coluna do v1 usa os 130 cenários originais. Latências são medianas vistas pelo cliente ao longo das 504 chamadas do v2, nas duas implantações locais: ROCm no Strix Halo, CUDA na RTX 5090. Os hospedados não têm hardware meu pra trocar, e os dois 27B não cabem nos 32 GB da 5090. O custo é o valor de lista por 10 mil decisões compactas, calculado pelos tokens que a API reportou.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Compacto EN / PT-BR</th>
          <th style="text-align: right">Compacto combinado</th>
          <th style="text-align: right">v1 originais</th>
          <th style="text-align: right">Latência ROCm (Strix)</th>
          <th style="text-align: right">Latência CUDA (5090)</th>
          <th style="text-align: right">10 mil decisões</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Perplexity Decider 27B</td>
          <td style="text-align: right">98,8% / 100,0%</td>
          <td style="text-align: right"><strong>99,4%</strong></td>
          <td style="text-align: right">99,2%</td>
          <td style="text-align: right">1.547 ms (LAN)</td>
          <td style="text-align: right">não cabe (49 GB)</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Decision 3.0 (27B)</td>
          <td style="text-align: right">98,8% / 100,0%</td>
          <td style="text-align: right"><strong>99,4%</strong></td>
          <td style="text-align: right">99,2%</td>
          <td style="text-align: right">1.420 ms (LAN)</td>
          <td style="text-align: right">não cabe (48,6 GB)</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Jev</td>
          <td style="text-align: right">97,6% / 97,6%</td>
          <td style="text-align: right">97,6%</td>
          <td style="text-align: right">96,2%</td>
          <td style="text-align: right">255 ms (WAN)</td>
          <td style="text-align: right">–</td>
          <td style="text-align: right">US$ 0,19</td>
      </tr>
      <tr>
          <td>Microsoft Decision-1</td>
          <td style="text-align: right">95,2% / 100,0%</td>
          <td style="text-align: right">97,6%</td>
          <td style="text-align: right">95,4%</td>
          <td style="text-align: right">422 ms (WAN)</td>
          <td style="text-align: right">–</td>
          <td style="text-align: right">US$ 0,065</td>
      </tr>
      <tr>
          <td>Clef (27B)</td>
          <td style="text-align: right">97,6% / 94,0%</td>
          <td style="text-align: right">95,8%</td>
          <td style="text-align: right">94,6%</td>
          <td style="text-align: right">302 ms (WAN)</td>
          <td style="text-align: right">–</td>
          <td style="text-align: right">US$ 0,63</td>
      </tr>
      <tr>
          <td>Jebadiah 4B v2</td>
          <td style="text-align: right">91,7% / 89,3%</td>
          <td style="text-align: right">90,5%</td>
          <td style="text-align: right">85,4%</td>
          <td style="text-align: right">205 ms (LAN)</td>
          <td style="text-align: right"><strong>39 ms</strong></td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>OpenAI Decisions</td>
          <td style="text-align: right">90,5% / 90,5%</td>
          <td style="text-align: right">90,5%</td>
          <td style="text-align: right">93,1%</td>
          <td style="text-align: right">222 ms (WAN)</td>
          <td style="text-align: right">–</td>
          <td style="text-align: right">US$ 0,23</td>
      </tr>
      <tr>
          <td>Kev 9B</td>
          <td style="text-align: right">92,9% / 83,3%</td>
          <td style="text-align: right">88,1%</td>
          <td style="text-align: right">87,7%</td>
          <td style="text-align: right">561 ms (LAN)</td>
          <td style="text-align: right">69 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Clef-flash (9B) hospedado</td>
          <td style="text-align: right">91,7% / 83,3%</td>
          <td style="text-align: right">87,5%</td>
          <td style="text-align: right">90,8%</td>
          <td style="text-align: right">394 ms (WAN)</td>
          <td style="text-align: right">–</td>
          <td style="text-align: right">US$ 0,10</td>
      </tr>
      <tr>
          <td>Clef-flash (9B) local</td>
          <td style="text-align: right">89,3% / 85,7%</td>
          <td style="text-align: right">87,5%</td>
          <td style="text-align: right">90,8%</td>
          <td style="text-align: right">618 ms (LAN)</td>
          <td style="text-align: right">86 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Mapika Decider 4B</td>
          <td style="text-align: right">89,3% / 85,7%</td>
          <td style="text-align: right">87,5%</td>
          <td style="text-align: right">86,9%</td>
          <td style="text-align: right">186 ms (LAN)</td>
          <td style="text-align: right">91 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Kev 4B</td>
          <td style="text-align: right">88,1% / 86,9%</td>
          <td style="text-align: right">87,5%</td>
          <td style="text-align: right">82,3%</td>
          <td style="text-align: right">246 ms (LAN)</td>
          <td style="text-align: right">67 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Eikos 4B</td>
          <td style="text-align: right">85,7% / 79,8%</td>
          <td style="text-align: right">82,7%</td>
          <td style="text-align: right">87,7%</td>
          <td style="text-align: right">235 ms (LAN)</td>
          <td style="text-align: right">42 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Simple-Jev + Qwen3.5 4B</td>
          <td style="text-align: right">77,4% / 71,4%</td>
          <td style="text-align: right">74,4%</td>
          <td style="text-align: right">85,4%</td>
          <td style="text-align: right">1.349 ms (LAN)</td>
          <td style="text-align: right">161 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Kev 0.8B</td>
          <td style="text-align: right">64,3% / 61,9%</td>
          <td style="text-align: right">63,1%</td>
          <td style="text-align: right">63,1%</td>
          <td style="text-align: right">86 ms (LAN)</td>
          <td style="text-align: right">49 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Decision 3.0 Edge (0,6B)</td>
          <td style="text-align: right">67,9% / 53,6%</td>
          <td style="text-align: right">60,7%</td>
          <td style="text-align: right">65,4%</td>
          <td style="text-align: right">45 ms (LAN)</td>
          <td style="text-align: right">13 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>GLiNER2.5-Decide (EN)</td>
          <td style="text-align: right">54,8% / 50,0%</td>
          <td style="text-align: right">52,4%</td>
          <td style="text-align: right">não rodou</td>
          <td style="text-align: right">208 ms (LAN)</td>
          <td style="text-align: right">20 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Laya typed-decisions</td>
          <td style="text-align: right">50,0% / 35,7%</td>
          <td style="text-align: right">42,9%</td>
          <td style="text-align: right">43,8%</td>
          <td style="text-align: right">43 ms (LAN)</td>
          <td style="text-align: right">14 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Laya (EN)</td>
          <td style="text-align: right">44,0% / 41,7%</td>
          <td style="text-align: right">42,9%</td>
          <td style="text-align: right">43,8%</td>
          <td style="text-align: right">41 ms (LAN)</td>
          <td style="text-align: right">13 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Laya multilingual</td>
          <td style="text-align: right">35,7% / 38,1%</td>
          <td style="text-align: right">36,9%</td>
          <td style="text-align: right">43,8%</td>
          <td style="text-align: right">19 ms (LAN)</td>
          <td style="text-align: right">10 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>GLiNER2.5-multi-Decide</td>
          <td style="text-align: right">32,1% / 35,7%</td>
          <td style="text-align: right">33,9%</td>
          <td style="text-align: right">não rodou</td>
          <td style="text-align: right">63 ms (LAN)</td>
          <td style="text-align: right">10 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Julia-1 (GPU)</td>
          <td style="text-align: right">38,1% / 25,0%</td>
          <td style="text-align: right">31,5%</td>
          <td style="text-align: right">38,5%</td>
          <td style="text-align: right"><strong>12 ms</strong> (LAN)</td>
          <td style="text-align: right">7,3 ms</td>
          <td style="text-align: right">não medido</td>
      </tr>
      <tr>
          <td>Julia-1 (CPU)</td>
          <td style="text-align: right">38,1% / 25,0%</td>
          <td style="text-align: right">31,5%</td>
          <td style="text-align: right">36,9%</td>
          <td style="text-align: right">71 ms (LAN)</td>
          <td style="text-align: right">–</td>
          <td style="text-align: right">não medido</td>
      </tr>
  </tbody>
</table>
<p>Sobre a coluna CUDA: os pesos são os mesmos, então o acerto veio igual ou a até dois casos de distância no compacto em todos os modelos. Rótulos individuais viraram em alguns (12 de 504 no pior caso, o Julia; 11 no Simple-Jev), diferença de aritmética de kernel que não mudou placar nenhum, e as probabilidades diferem na terceira casa decimal em média. A tabela fica pelo acerto do Strix; a coluna CUDA só traz latência. O relatório pareado completo, caso a caso, está no <a href="https://github.com/akitaonrails/jev-docs-benchmarks/blob/master/benchmark/reports/cuda-comparison/results.md"target="_blank" rel="noopener">repositório</a>.</p>
<p>Pra calibrar a leitura: as tarefas têm três ou quatro opções, então chute uniforme dá uns 31% nos dois conjuntos. &ldquo;Perto de 50%&rdquo; aqui já é bem acima da moeda, e &ldquo;perto de 33%&rdquo; é a moeda. O Julia-1 ficou em 31,5%: o nome bonito esconde um cara jogando cara-ou-coroa.</p>
<p>Os intervalos importam. A vantagem do Perplexity sobre o Jev no v2 é de menos de 2 pontos e o intervalo de bootstrap encosta no zero: é a liderança medida neste conjunto, não uma superioridade provada pra sempre. E o Decision 3.0 empatou com o Perplexity no limite do que este conjunto consegue distinguir: <strong>rótulos idênticos nas 1.024 classificações</strong>, com os dois errando os mesmos dois casos (a relevância em inglês no v2 e o caso disputado de severidade no v1, que já está no errata). O Microsoft empatou com o Jev no agregado, com intervalo centrado em zero, mas empate no agregado não é intercambiabilidade: eles erram casos diferentes, e o Microsoft inverte o padrão de língua, errando mais em inglês que em português. Jev e Clef também seguem separados por menos de 2 pontos, no mesmo patamar. Já o pelotão de 90% (Jebadiah, OpenAI) fica 7 pontos atrás do Jev, e daí pra baixo todas as diferenças são claras.</p>
<p>Cada célula de tarefa tem só 12 cenários por língua, então um acerto a mais muda a célula em mais de 8 pontos. Empate em uma tarefa específica é pista pra um piloto. As tabelas por tarefa estão no <a href="https://github.com/akitaonrails/jev-docs-benchmarks/blob/master/benchmark/reports/decision3-comparison-v2/results.md"target="_blank" rel="noopener">repositório</a>.</p>
<h2>Quanto custa, em e-mails de suporte<span class="hx:absolute hx:-mt-20" id="quanto-custa-em-e-mails-de-suporte"></span>
    <a href="#quanto-custa-em-e-mails-de-suporte" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Preço por milhão de tokens é um número que ninguém consegue sentir. Os preços de lista em 11 de outubro são US$ 0,042 por milhão de tokens de entrada no Jev, US$ 0,24 no Clef, US$ 0,038 no Clef-flash e US$ 0,10 no OpenAI Decisions. Saída é grátis nos cinco. O Microsoft Decision-1 cobra os mesmos US$ 0,042 do Jev por milhão, via OpenRouter, mas reportou um terço dos tokens de entrada pros mesmos textos (uns 26 mil contra uns 77 mil do Jev no v2 compacto), e ninguém sabe explicar a diferença de tokenização. Na prática medida, isso o torna o hospedado mais barato do lote.</p>
<p>O Clef-flash custava US$ 0,09 no dia 9: a Cloudflare baixou o preço em mais da metade no meio do meu teste. O Jev reporta bem mais tokens que a Cloudflare pro mesmo texto, uns 45% a mais no v2, e isso já está na conta abaixo.</p>
<p>Imagine uma operação com <strong>10 mil e-mails de suporte por mês</strong> pra classificar em três ou quatro baldes, com textos curtos parecidos com os meus casos:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">10 mil e-mails / mês</th>
          <th style="text-align: right">1 milhão de e-mails</th>
          <th style="text-align: right">Erros esperados em 10 mil (pelo acerto compacto)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Microsoft Decision-1</td>
          <td style="text-align: right">US$ 0,065</td>
          <td style="text-align: right">US$ 6,45 (uns R$ 32)</td>
          <td style="text-align: right">~240</td>
      </tr>
      <tr>
          <td>Clef-flash</td>
          <td style="text-align: right">US$ 0,10</td>
          <td style="text-align: right">US$ 10 (uns R$ 50)</td>
          <td style="text-align: right">~1.250</td>
      </tr>
      <tr>
          <td>Jev</td>
          <td style="text-align: right">US$ 0,19</td>
          <td style="text-align: right">US$ 19 (uns R$ 95)</td>
          <td style="text-align: right">~240</td>
      </tr>
      <tr>
          <td>OpenAI Decisions</td>
          <td style="text-align: right">US$ 0,23</td>
          <td style="text-align: right">US$ 23 (uns R$ 115)</td>
          <td style="text-align: right">~950</td>
      </tr>
      <tr>
          <td>Clef</td>
          <td style="text-align: right">US$ 0,63</td>
          <td style="text-align: right">US$ 63 (uns R$ 315)</td>
          <td style="text-align: right">~420</td>
      </tr>
  </tbody>
</table>
<blockquote>
  <p>Em reais, pela cotação de sexta (R$ 4,99), 10 mil decisões no Jev custam um real. Um milhão custa menos de cem reais. Pra qualquer empresa que tenha um milhão de e-mails pra classificar, isso some no orçamento.</p>

</blockquote>
<p>Uma ressalva de proporção: meus casos são curtos, uns 450 tokens por caso compacto pela contagem do Jev. E-mail real de cliente, com histórico de thread citado, pode ser três a cinco vezes maior, e o custo escala linear com isso. Mesmo assim, estamos falando de centavos por milhar.</p>
<p>O que esse cálculo mostra é que a diferença de preço entre os modelos hospedados é irrelevante perto do custo de errar. O Jev custa 9 centavos de dólar a mais que o Clef-flash a cada 10 mil decisões, e nesses mesmos 10 mil comete uns mil erros a menos. Se um e-mail mal roteado custar mais que um centésimo de centavo de dólar em retrabalho humano, o Jev se paga. Essa conta assume que todo erro custa igual, o que nunca é verdade: mandar um ticket crítico pra fila de rotina custa muito mais que o contrário.</p>
<p>A franquia da Cloudflare muda a conta pra quem é pequeno. São 10 mil &ldquo;neurônios&rdquo; por dia, compartilhados pela conta inteira, zerando à meia-noite UTC. Com os meus tamanhos de entrada, isso dá umas 1.500 classificações de Clef completo por dia ou quase 10 mil de Clef-flash, de graça. Se você tem 1.000 e-mails por dia e nada mais rodando na conta, o Clef sai a custo zero com qualidade de ponta.</p>
<p>E o local? Energia o benchmark deixou de medir. Pelo que eu vejo no meu Strix Halo, a máquina inteira fica abaixo de 100 W sob carga. Com o Jebadiah em 200 ms por decisão, 10 mil decisões são uns 35 minutos de máquina, menos de 0,1 kWh, uns centavos de real na conta de luz.</p>
<p>O custo local real é a máquina, a sua hora configurando ROCm e container, e manter aquilo de pé. Quem já tem o hardware por outro motivo tem custo marginal baixo. Quem compraria a máquina só pra isso está comprando um problema de milhares de reais pra economizar uns cem reais por milhão.</p>
<h2>Qual modelo em qual situação<span class="hx:absolute hx:-mt-20" id="qual-modelo-em-qual-situação"></span>
    <a href="#qual-modelo-em-qual-situa%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Você quer precisão e quer operar nada: Jev<span class="hx:absolute hx:-mt-20" id="você-quer-precisão-e-quer-operar-nada-jev"></span>
    <a href="#voc%c3%aa-quer-precis%c3%a3o-e-quer-operar-nada-jev" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na nuvem, o Jev continua sendo o nome. Venceu ou empatou em praticamente todas as células entre os hospedados, e só o Microsoft empatou com ele no agregado. Em inglês ele foi 100% em cinco das sete tarefas do v2, e em português repetiu 97,6% no agregado sem perder nada na tradução, coisa que nenhum outro modelo acima da moeda conseguiu. No v1 ele manteve os 96,2% do original com o comentário malicioso do &ldquo;revisor&rdquo; injetado; o Clef até subiu um caso, e o Kev 4B também ficou parado, só que num patamar bem mais baixo. A latência de 255 ms via WAN ficou a 30 ms do OpenAI, e custa 19 centavos de dólar a cada 10 mil decisões.</p>
<p>A contrapartida é a que já discuti no texto anterior: é proprietário, hospedado, de tamanho não divulgado, e você depende da TypeSafe manter o comportamento por baixo de você. E ele erra. Tratou um ID de fatura chamado &ldquo;PASSWORD-523&rdquo; como credencial utilizável, mesmo com o registro dizendo explicitamente que aquilo não autentica ninguém.</p>
<h3>Você quer o nível do Jev pagando um terço: Microsoft Decision-1<span class="hx:absolute hx:-mt-20" id="você-quer-o-nível-do-jev-pagando-um-terço-microsoft-decision-1"></span>
    <a href="#voc%c3%aa-quer-o-n%c3%advel-do-jev-pagando-um-ter%c3%a7o-microsoft-decision-1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A Microsoft entrou nesse mercado quieto, e entrou bem. O Decision-1 é um 9B pós-treinado do Qwen3.5-9B, servido via OpenRouter, e foi o primeiro modelo a empatar com o Jev no agregado compacto: 97,6%, com o intervalo de bootstrap centrado no zero. O detalhe que me ganhou: <strong>100% em português</strong>, melhor que o inglês dele (95%), invertendo o padrão de todo o resto do lote. No v1, as variantes de ordem invertida e comentário malicioso não derrubaram uma única resposta, e ele acertou o ticket crítico que o Clef e o OpenAI erraram.</p>
<p>E é o hospedado mais barato que eu medi: US$ 0,065 por 10 mil decisões, um terço do Jev, cobrando a mesma taxa por token mas reportando um terço dos tokens. A economia vem da contagem de tokens, e o mecanismo exato eu não sei explicar, então trate como medida, não como promessa.</p>
<p>Os pontos fracos existem. A latência é a pior entre os hospedados (422 ms de mediana, contra 255 do Jev). Nos casos compactos ele é limpo de erro confiante, mas basta adicionar contexto pra aparecer erro em português com 99,5% de confiança: o padrão &ldquo;português perfeito&rdquo; não sobrevive a texto longo. E nas sondagens de probabilidade ele é fraco, dando 87% pra uma moeda honesta. A Microsoft anuncia probabilidades calibradas; minhas sondagens discordam. E como todo hospedado de ID datado, o peso por trás pode mudar sem aviso.</p>
<p>Pra operação em português que quer qualidade de Jev pagando menos, é o primeiro candidato a rodar no seu piloto.</p>
<h3>Você quer o mesmo nível com pesos abertos: Clef<span class="hx:absolute hx:-mt-20" id="você-quer-o-mesmo-nível-com-pesos-abertos-clef"></span>
    <a href="#voc%c3%aa-quer-o-mesmo-n%c3%advel-com-pesos-abertos-clef" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Clef completo de 27B empatou com o Jev em inglês (82 de 84) e ficou 3 casos atrás em português. Ganhou dele no tratamento de dados sensíveis em inglês (12 de 12 contra 11). É modelo de pesos abertos, então dá pra rodar local com uns 54 GB de memória só pros pesos em BF16, o que eu deixei de testar: o Clef completo rodou só pela API. Entre os abertos, ele perdeu a coroa pro Perplexity, que pontuou mais alto com pesos menores.</p>
<p>Pela API ele é o mais caro dos cinco hospedados, três vezes o Jev, sem ganho de qualidade que justifique. A exceção é a franquia diária: pra volume pequeno, ele é o melhor classificador que você consegue de graça.</p>
<h3>Você quer o mais barato na nuvem: Clef-flash<span class="hx:absolute hx:-mt-20" id="você-quer-o-mais-barato-na-nuvem-clef-flash"></span>
    <a href="#voc%c3%aa-quer-o-mais-barato-na-nuvem-clef-flash" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A US$ 0,038 por milhão, o Clef-flash é o preço mais baixo do mercado hospedado e cobre 9.400 decisões por dia dentro da franquia. A qualidade cai: 92% em inglês e 83% em português nos casos compactos, a maior queda de língua entre os hospedados, com intervalo inteiro abaixo de zero. Em sentimento nas duas línguas, e em status de incidente em inglês, ele empatou com o Jev. Em intenção e dados sensíveis em português, perdeu feio.</p>
<p>Pra tarefa de sentimento em qualquer língua ou pra classificação em inglês onde 1 em 12 erros é aceitável, ele serve bem. Pra português, meça antes.</p>
<h3>Você já vive no ecossistema OpenAI: OpenAI Decisions<span class="hx:absolute hx:-mt-20" id="você-já-vive-no-ecossistema-openai-openai-decisions"></span>
    <a href="#voc%c3%aa-j%c3%a1-vive-no-ecossistema-openai-openai-decisions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A estreia da OpenAI nesse mercado ficou no meio da tabela: 90,5% combinado, empate exato com o Jebadiah, com a menor latência hospedada que eu medi (222 ms) e o mesmo acerto nas duas línguas. Custa US$ 0,23 por 10 mil decisões, entre o Jev e o Clef. Se a sua operação já fala a API da OpenAI e 9 em 10 de acerto basta, a integração é trivial: um endpoint nativo de decisões, nada de prompt.</p>
<p>Os pontos fracos são reveladores. A pior célula dele foi justamente política por limiar numérico (8 de 12 em inglês, 7 em português): tratou &ldquo;pelo menos 10&rdquo; como satisfeito por um intervalo de 8 a 12 e falhou no curto-circuito de E/OU. A calibração é esquizofrênica: nas sondagens de predicado (sim/não) ele foi o mais certeiro do lote, com erro médio quase zero, e nas sondagens de escolha ele devolveu 94%/6% numa moeda honesta. E nas variantes do v1 ele virou 2 respostas certas em cada apresentação (ordem invertida, rótulo opaco, comentário malicioso), coisa que só o Perplexity não fez: o Jev manteve a acurácia agregada, mas também virou algumas respostas certas no caminho. Ah, e o alias <code>gpt-6-luna</code> não é snapshot imutável: reprodutibilidade exige guardar as respostas.</p>
<h3>Você quer o melhor modelo aberto, ponto: Perplexity Decider 27B<span class="hx:absolute hx:-mt-20" id="você-quer-o-melhor-modelo-aberto-ponto-perplexity-decider-27b"></span>
    <a href="#voc%c3%aa-quer-o-melhor-modelo-aberto-ponto-perplexity-decider-27b" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Perplexity Decider 1.1 27B é a surpresa do benchmark. Liderou a tabela com 99,4% nos compactos, foi o único que não perdeu nenhuma célula de tarefa pro Jev (3 vitórias, 11 empates), e fez 100% em português nas três condições: compacto, com exemplos, com distratores. A calibração também é a melhor medida, com Brier de 0,012, e a cobertura é absurda: respondeu com confiança acima de 0,9 em 96% dos casos e errou zero deles. No v1, não virou uma única resposta nas variantes de ordem, rótulo opaco e comentário malicioso.</p>
<p>Os únicos 3 erros dele no v2 foram o mesmo caso em traje diferente (confundiu &ldquo;notificação enviada a gerentes&rdquo; com &ldquo;aprovador obrigatório&rdquo;), e ele os cometeu com confiança abaixo de 0,9: o erro conhecido vem sinalizado. O ponto fraco são as sondagens de probabilidade de evento: perguntado sobre um evento de chance 1 em 4, ele respondeu 0,73. Classificação forte não implica probabilidade de evento confiável.</p>
<p>O custo é hardware: uns 49 GB de pesos em BF16, um segundo e meio por decisão no meu Strix Halo (com os kernels de referência do ROCm, sem flash-attention). E antes de sonhar com &ldquo;numa NVIDIA deve voar&rdquo;: eu testei, e ele nem cabe numa RTX 5090 de 32 GB. É Apache 2.0, pesos públicos sem gate. Se você tem a máquina e quer o topo da tabela em casa, é ele.</p>
<h3>Você quer o outro topo aberto: Decision 3.0 (e cuidado com o Edge)<span class="hx:absolute hx:-mt-20" id="você-quer-o-outro-topo-aberto-decision-30-e-cuidado-com-o-edge"></span>
    <a href="#voc%c3%aa-quer-o-outro-topo-aberto-decision-30-e-cuidado-com-o-edge" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Decision 3.0, lançado há dois dias pelo projeto vLLM Semantic Router, é o segundo modelo a sentar no topo: 99,4% nos compactos, 100% em português, e um empate tão absoluto com o Perplexity que os dois deram <strong>exatamente os mesmos rótulos nas 1.024 classificações</strong>, incluindo os mesmos dois erros (a relevância em inglês, com confiança abaixo de 0,9, e o caso disputado de severidade que já mora no errata). Também zerou as armadilhas do v1 sem virar uma resposta, acertou os dois tickets críticos indiscutíveis, e a calibração é da mesma classe do Perplexity (Brier 0,014, 94% de cobertura acima de 0,9 com zero erros).</p>
<p>Ou seja: o topo open agora tem duas receitas independentes que convergem. Pro seu piloto, tanto faz qual das duas; rode as duas se puder, porque as probabilidades diferem mesmo quando os rótulos não. A latência é da mesma família do Perplexity (1,4 s na minha máquina, mesma dieta de ROCm), e os pesos ocupam os mesmos ~49 GB.</p>
<p>O irmão pequeno é outra história. O <strong>Decision 3.0 Edge</strong> (0,6B) responde em 45 ms e cabe em 1,3 GB de GPU, mas acertou só 61% no compacto, despenca 14 pontos em português, e pior: dos 68 palpites que ele deu com confiança acima de 0,9, 14 estavam errados. Como roteador com limiar de confiança, de fábrica, ele é uma armadilha da mesma turma do Julia. Os quatro checkpoints do meio (flash, mini, nano, lite) ainda não testei; são eles que vão dizer se a família tem um ponto doce entre os dois extremos.</p>
<h3>Você precisa rodar local, com qualidade: Jebadiah 4B v2<span class="hx:absolute hx:-mt-20" id="você-precisa-rodar-local-com-qualidade-jebadiah-4b-v2"></span>
    <a href="#voc%c3%aa-precisa-rodar-local-com-qualidade-jebadiah-4b-v2" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Dos locais, o Jebadiah 4B v2 teve o melhor acerto compacto, 90% combinado, respondendo em uns 200 ms no Strix Halo e <strong>39 ms na RTX 5090</strong>. Numa 5090, ele fica mais rápido que qualquer hospedado do benchmark, Jev incluso (255 ms de WAN): com o hardware certo, o local deixou de ser o lado lento da tabela. Empatou com o Jev em seis das catorze células de tarefa, incluindo intenção, implicação e status em inglês e intenção em português. O ponto fraco é política por limiar numérico, 9 de 12 nas duas línguas, e ele perde terreno quando o contexto enche de distratores (76% em português nessa condição, contra 82% do Mapika).</p>
<p>O <strong>Mapika Decider 4B</strong> (o mais rápido dos 4B) e o <strong>Kev 4B</strong> ficam logo atrás, ambos com uns 87%, e a diferença de 3 pontos entre os três cabe dentro do intervalo de erro. São três receitas de treino diferentes em cima de backbones de 4B, com erros em tarefas diferentes. A escolha entre eles depende da sua tarefa, e eu rodaria os três num piloto.</p>
<h3>Você vai mandar exemplos no prompt: Kev 9B<span class="hx:absolute hx:-mt-20" id="você-vai-mandar-exemplos-no-prompt-kev-9b"></span>
    <a href="#voc%c3%aa-vai-mandar-exemplos-no-prompt-kev-9b" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Kev 9B tem um perfil curioso. Nos casos compactos ele empata com o Kev 4B, com meio ponto de diferença e intervalo cruzando zero, ou seja, mais que o dobro de parâmetros comprou nada. Com um exemplo resolvido por classe, ele abre quase 10 pontos sobre o 4B, e com distratores segura 7 pontos a mais. O Brier também melhora uns 25%: a probabilidade que ele devolve é mais bem calibrada.</p>
<p>Se o seu uso é só a regra e o texto, o 4B basta e é mais que o dobro mais rápido. Se você vai mandar exemplos ou contexto longo, o 9B vale a memória. O alerta é o português: 83% compacto em PT-BR, quase 10 pontos abaixo do inglês dele, a maior queda de língua entre os modelos que passam de 80%. Só o Laya typed (14 pontos) e o Julia (13) caem mais.</p>
<h3>Você quer o comportamento do Clef-flash na sua máquina: Clef-flash local<span class="hx:absolute hx:-mt-20" id="você-quer-o-comportamento-do-clef-flash-na-sua-máquina-clef-flash-local"></span>
    <a href="#voc%c3%aa-quer-o-comportamento-do-clef-flash-na-sua-m%c3%a1quina-clef-flash-local" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No conjunto v1, o Clef-flash local reproduziu as 520 classificações do hospedado, acerto por acerto e erro por erro. No v2 a história mudou: mesmo total (439 de 504), mas 19 rótulos diferentes, com forças trocadas (o local acerta mais intenção em português, o hospedado acerta mais relevância). Entre os locais ele continua o melhor no conjunto v1 de políticas operacionais (90,8%), à frente de todos os 4B.</p>
<p>A pegadinha é a latência: 618 ms de mediana no Strix Halo (86 ms na 5090), três vezes o Jebadiah, pelo mesmo acerto no v2. Se você escolher ele, é pelo comportamento conhecido.</p>
<h3>Você precisa rodar offline em hardware fraco: Laya e Julia, com muitas ressalvas<span class="hx:absolute hx:-mt-20" id="você-precisa-rodar-offline-em-hardware-fraco-laya-e-julia-com-muitas-ressalvas"></span>
    <a href="#voc%c3%aa-precisa-rodar-offline-em-hardware-fraco-laya-e-julia-com-muitas-ressalvas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Laya multilingual foi o pior do benchmark em acerto (37% combinado, perto da moeda), e o mais rápido com folga entre os que acertam alguma coisa: 19 ms de mediana, mais de dez vezes mais rápido que o Jebadiah.</p>
<p>Os três checkpoints do Laya têm entre 322 e 421 milhões de parâmetros, cabem em menos de 1 GB de pesos em BF16 na conta de parâmetros, e o projeto documenta execução em CPU e runtime ONNX (que eu deixei de medir). O pico de alocação do PyTorch na GPU, pro multilingual, ficou abaixo de 2 GB. Um 4B precisa de uns 8 GB só de pesos na mesma precisão.</p>
<p>O <strong>Julia-1</strong> desce mais um degrau: 144 milhões de parâmetros, 550 MB de pesos, 12 ms de mediana na GPU do Strix, 7 ms na 5090 e 71 ms numa CPU de 4 threads. É o modelo mais rápido que eu já medi aqui, e também o pior: 31,5% de acerto, colado na linha da moeda de 31%. Em português ele respondeu &ldquo;cancelamento&rdquo; nos 12 cenários de intenção, inclusive nos que pediam reembolso ou acesso (a avaliação de português do fabricante foi feita em pt-PT, e se nota). O desastre maior é a confiança: dos 71 palpites que ele deu acima de 0,9, 48 estavam errados. Um limiar de confiança não salva esse modelo, porque é justamente nos palpites confiantes que ele erra.</p>
<p>Isso é escolha de projeto. O Laya e o Julia foram feitos pra ser encoders pequenos, afináveis na sua tarefa, pra rodar na borda. O que eu testei foram os checkpoints de fábrica, sem nenhum ajuste, aplicando regras que eles nunca viram, em tarefas amplas. Nesse cenário eles foram mal e ponto.</p>
<p>Um detalhe confirma a fragilidade. No v1, quando o comentário malicioso do revisor entra, o Laya cai de 44% pra 19% e o typed-decisions pra 15%. Ele obedece o comentário que a instrução manda ignorar, e com confiança: 17 dos 28 palpites acima de 0,9 estavam errados. O Julia falhou nos 5 pares de contrafactual de política: troca a regra, ele mantém a resposta.</p>
<blockquote>
  <p>Se você quer o que o Jev entrega, o Laya e o Julia de fábrica ficam muito longe, e nenhum modelo pequeno (GLiNER incluso) chegou perto. Se o seu problema é &ldquo;preciso classificar texto curto em três baldes, num dispositivo sem internet e sem GPU, e tenho mil exemplos rotulados pra ajustar&rdquo;, esses dois são os únicos da lista que cabem. O ajuste fino é a parte que eu deixei de testar. É outra pergunta, que precisa de outro benchmark.</p>

</blockquote>
<h3>A linha de base: Simple-Jev com Qwen comum<span class="hx:absolute hx:-mt-20" id="a-linha-de-base-simple-jev-com-qwen-comum"></span>
    <a href="#a-linha-de-base-simple-jev-com-qwen-comum" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Simple-Jev lendo tokens do Qwen3.5 4B, sem treino de decisão nenhum, tirou 74% no v2 compacto. Isso é a régua de &ldquo;quanto o treino especializado compra&rdquo;: Jebadiah, Mapika e Kev, todos em cima de backbones de tamanho parecido, ficaram 13 a 16 pontos acima. O treino compra bastante.</p>
<p>O curioso é que no v1 o Simple-Jev empatou com o Jebadiah (111 de 130 originais) e acertou os dois tickets críticos sem ambiguidade, que vários modelos melhores erraram. Ele também é o mais lento do lote no Strix Halo, quase um segundo e meio por decisão, porque está rodando um LLM inteiro. Na 5090 o mesmo LLM inteiro cai pra 161 ms: a linha de base acelera junto com a máquina, que é o que ela deveria fazer.</p>
<h2>Quão difícil é rodar os melhores abertos na sua máquina<span class="hx:absolute hx:-mt-20" id="quão-difícil-é-rodar-os-melhores-abertos-na-sua-máquina"></span>
    <a href="#qu%c3%a3o-dif%c3%adcil-%c3%a9-rodar-os-melhores-abertos-na-sua-m%c3%a1quina" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todos esses modelos falam o mesmo protocolo HTTP, chamado SystemOne, que o Jev popularizou: você manda um estado (o texto), uma ou mais perguntas de resposta fechada, e recebe de volta a opção escolhida e a probabilidade de cada uma. Trocar de modelo é trocar a porta. O OpenAI Decisions foge do padrão com um endpoint nativo próprio, mas a ideia é a mesma. O que muda é quanto trabalho dá colocar cada um de pé, e aí a diferença entre eles é grande.</p>
<h3>OpenAI: só uma API key<span class="hx:absolute hx:-mt-20" id="openai-só-uma-api-key"></span>
    <a href="#openai-s%c3%b3-uma-api-key" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O mais fácil de todos: cria a chave, carrega crédito e chama o endpoint. As 1.048 requisições do benchmark rodaram em 4 minutos, sem uma retentativa. O único soluço foi a primeira sonda devolver &ldquo;saldo esgotado&rdquo; até eu colocar US$ 20 na conta.</p>
<p>O <strong>Microsoft Decision-1</strong> é quase tão simples: se você já tem conta no OpenRouter, é apontar o modelo e chamar o endpoint de decisões. Sem GPU, sem assinatura Azure, sem instalação. No meu teste, 10 das 504 chamadas do v2 tomaram um 429 e passaram na retentativa, então planeje retry.</p>
<h3>Kev e Jebadiah: um pip e um comando<span class="hx:absolute hx:-mt-20" id="kev-e-jebadiah-um-pip-e-um-comando"></span>
    <a href="#kev-e-jebadiah-um-pip-e-um-comando" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <strong>Kev</strong> é o mais simples da turma de 4B. O projeto instala direto do GitHub e sobe um servidor com um comando, baixando o adaptador LoRA e a cabeça de decisão em cima do Qwen3.5 base:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pip install <span class="s2">&#34;kev @ https://github.com/jaredpalmer/kev/archive/fc4a17e194bca35a48c57211392ec49ea54a94ac.tar.gz&#34;</span>
</span></span><span class="line"><span class="cl">python -m kev.serve --run jaredpalmer/kev-4b --host 0.0.0.0 --port <span class="m">8000</span> --device cuda</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <strong>Jebadiah</strong> segue o mesmo padrão, com o pacote do servidor dentro do repositório dele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">pip install <span class="s2">&#34;jebadiah-server @ https://github.com/getainode/jebadiah/archive/e9793abc66d1a7c5ad878d51eda656f1e9c5b9f5.tar.gz#subdirectory=server&#34;</span>
</span></span><span class="line"><span class="cl">jebadiah-serve --model frontier-infra/jebadiah-4b-v2 --host 0.0.0.0 --port <span class="m">8000</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com qualquer um dos dois no ar, uma decisão é um POST:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">curl -s http://localhost:8000/v1/systemone -H <span class="s1">&#39;content-type: application/json&#39;</span> -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;model&#34;: &#34;kev-4b&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;state&#34;: &#34;Cliente diz que foi cobrado duas vezes e pede o estorno da segunda cobrança.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;questions&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;fila&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;type&#34;: &#34;choice&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;instructions&#34;: &#34;Escolha a fila correta pela política de suporte.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">      &#34;criteria&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;financeiro&#34;: &#34;Cobrança, estorno, nota fiscal.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;tecnico&#34;: &#34;Erro, bug, indisponibilidade.&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;vendas&#34;: &#34;Upgrade, plano novo, orçamento.&#34;
</span></span></span><span class="line"><span class="cl"><span class="s1">      }
</span></span></span><span class="line"><span class="cl"><span class="s1">    }
</span></span></span><span class="line"><span class="cl"><span class="s1">  }
</span></span></span><span class="line"><span class="cl"><span class="s1">}&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A resposta traz a opção e a distribuição de probabilidade, e é nela que você aplica o seu limiar, loga e decide se manda pra revisão humana. Num NVIDIA com CUDA e 16 GB de VRAM, um 4B em BF16 sobe sem drama. Os pesos pesam uns 8 GB. Na 5090, os 4B que testei respondem em 39 a 91 ms.</p>
<h3>Laya e Julia: menores ainda<span class="hx:absolute hx:-mt-20" id="laya-e-julia-menores-ainda"></span>
    <a href="#laya-e-julia-menores-ainda" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <strong>Laya</strong> é um pacote Python com encoder dentro. Instala com <code>pip install laya</code> e o projeto documenta execução em CPU, que é a graça dele. A versão fixada no meu servidor foi a 0.4.1, e o pico de memória do multilingual na GPU ficou abaixo de 2 GB. Pra rodar num notebook sem GPU ou num container pequeno, é o único da lista que cabe sem conversa.</p>
<p>O <strong>Julia-1</strong> é parecido em espírito: um runtime Python próprio, uma chamada <code>predict(state=..., questions=...)</code> e pronto, sem pipeline padrão de Transformers. Rodei o mesmo container em GPU e em CPU, e os dois deram o mesmo acerto com as mesmas respostas em 97% dos casos. Em CPU de 4 threads ele responde em 71 ms, o que pra um classificador de borda é ótimo. O problema continua sendo ele acertar no nível da moeda.</p>
<h3>Onde a coisa complica: AMD, ROCm e o resto<span class="hx:absolute hx:-mt-20" id="onde-a-coisa-complica-amd-rocm-e-o-resto"></span>
    <a href="#onde-a-coisa-complica-amd-rocm-e-o-resto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No meu caso o hardware é AMD, e aí o &ldquo;um comando&rdquo; vira um dia de trabalho. Os pontos que me custaram tempo, pra você economizar o seu:</p>
<ul>
<li>Nenhum desses projetos testa em ROCm. O Kev tinha um segfault específico do gfx1151 por causa de CUDA graphs (<a href="https://github.com/jaredpalmer/kev/issues/170"target="_blank" rel="noopener">issue 170</a>), corrigido só depois da tag 1.0, então a versão fixada é um commit do main e a tag oficial crasha.</li>
<li>Os kernels Triton de flash-linear-attention que o Qwen3.5 usa nas camadas DeltaNet são sem suporte testado no gfx1151. Tudo rodou no caminho de PyTorch puro, mais lento que a latência publicada em CUDA. Os 200 ms do Jebadiah que aparecem na tabela são com esse freio de mão puxado; na 5090, com o mesmo protocolo e os mesmos pesos, o freio some e ele faz 39 ms.</li>
<li>O Mapika Decider força CUDA graphs quando vê o device &ldquo;cuda&rdquo;, que no ROCm também se chama &ldquo;cuda&rdquo;. Precisei desligar na mão.</li>
<li>Precisa exportar <code>HSA_OVERRIDE_GFX_VERSION=11.5.1</code> e <code>PYTORCH_ROCM_ARCH=gfx1151</code> e usar uma imagem base com o torch de ROCm já validado. Um <code>pip install torch</code> qualquer sobrescreve com a build de CUDA e quebra tudo. No meu Dockerfile cada pacote é instalado com <code>--no-deps</code> e o build falha se o torch deixar de ser o de ROCm.</li>
<li>Um modelo por container, um container por vez. O Strix Halo tem memória compartilhada entre CPU e GPU, e três modelos de 4B residentes mais o Ollama que já roda ali esgotam os 64 GiB que a GPU enxerga.</li>
<li>O Perplexity Decider foi o mais chato de todos. O release espera um torch de CUDA específico (troquei pelo 2.9 do ROCm 7), os kernels de flash-linear-attention não existem pra ele (rodei nos kernels de referência do PyTorch, mais lentos), e o carregador padrão estourou o limite de 24 GiB de RAM do host: precisei de um carregador custom que manda os 49 GiB de pesos direto pra GPU. O card do modelo ainda dizia &ldquo;privado&rdquo; quando eu comecei, mas a API do Hugging Face confirmava público e sem gate, e os shards baixaram anônimos. O d3, com o mesmo tamanho, herdou o mesmo tratamento. O d3-edge, por sorte, sobe com o carregador padrão do Transformers e 1,3 GB de GPU.</li>
</ul>
<p>Nada disso é impossível, e está tudo documentado, com Dockerfiles, compose e os hashes de cada checkpoint, no <a href="https://github.com/akitaonrails/jev-docs-benchmarks/blob/master/docs/setup.md"target="_blank" rel="noopener">guia de setup do repositório</a>. Só que é um custo real que a planilha de &ldquo;local é de graça&rdquo; nunca inclui. Se a sua máquina é NVIDIA, a maior parte dessa lista some.</p>
<blockquote>
  <p>O Clef completo de 27B eu nem tentei local: são uns 54 GB de pesos em BF16 antes de qualquer ativação. O Perplexity, com 49 GB, provou que um 27B sobe no Strix Halo, ao custo de um segundo e meio por decisão com os kernels de referência. E nem cabe na RTX 5090: 49 GB não entram em 32. O Clef-flash de 9B roda, com uns 18 GB de pesos. Os de 4B são o ponto doce pra hardware de consumidor.</p>

</blockquote>
<h3>ROCm x CUDA: a mesma receita em duas cozinhas<span class="hx:absolute hx:-mt-20" id="rocm-x-cuda-a-mesma-receita-em-duas-cozinhas"></span>
    <a href="#rocm-x-cuda-a-mesma-receita-em-duas-cozinhas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Todos os modelos locais acima rodaram duas vezes, em duas máquinas minhas. De um lado, o minisforum MS-S1 Max com Strix Halo: Ryzen AI Max+ 395, 64 GiB endereçáveis pela GPU, via ROCm. Do outro, um PC com RTX 5090 (32 GB GDDR7) num Ryzen 7950X3D com 92 GB de RAM, via CUDA. Mesmos checkpoints por hash, mesmos requests, PyTorch 2.9.1 dos dois lados (build ROCm do fornecedor num, cu128 oficial no outro), kernels de referência, sem CUDA graphs, sem quantização, um modelo por vez, protocolo congelado em git antes de qualquer inferência.</p>
<p>Honestidade antes dos números: <strong>isso não é comparação de maçã com maçã.</strong> São duas implantações inteiras, com GPU, CPU, build e rede diferentes. A 5090 é uma placa absurdamente mais poderosa que a iGPU do Strix. O Strix é uma máquina completa (CPU, RAM, disco, fonte) que custa o que custa só a placa da NVIDIA. O que a comparação mede é a escolha real: qual máquina pra qual modelo.</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>RTX 5090</th>
          <th>Strix Halo (MS-S1 Max)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Preço de referência</td>
          <td>US$ 1.999 de MSRP, só a placa (no varejo, quase sempre mais)</td>
          <td>~US$ 1.500-2.000 a máquina completa, dependendo da RAM</td>
      </tr>
      <tr>
          <td>Memória</td>
          <td>32 GB GDDR7 dedicados, 1.792 GB/s</td>
          <td>64 GiB unificada endereçável pela GPU</td>
      </tr>
      <tr>
          <td>Faixa de modelo</td>
          <td>tudo até 9B em BF16, voando</td>
          <td>tudo até 27B em BF16, devagar</td>
      </tr>
      <tr>
          <td>Os campeões de 27B</td>
          <td><strong>não cabem</strong></td>
          <td>1,4-1,5 s por decisão</td>
      </tr>
  </tbody>
</table>
<p>O resultado em uma linha: a 5090 foi de 1,7× a 10,3× mais rápida em tudo que coube nela, e os campeões do benchmark não couberam. Os detalhes que valem:</p>
<p><strong>O placar de acerto não mudou.</strong> Mesmos pesos, mesma pontuação até dois casos de distância por modelo. Alguns rótulos individuais viraram (12 de 504 no pior caso, o Julia; 11 no Simple-Jev), diferença de aritmética de kernel, e as probabilidades diferem na terceira casa decimal em média. Toda conclusão de qualidade da tabela vale pras duas máquinas.</p>
<p><strong>Os campeões de 27B não cabem na 5090.</strong> O d3 pesa 48,6 GB e o Perplexity 49 GB em BF16, contra 31,8 GB de VRAM. A placa de consumo mais rápida do mercado não roda os melhores modelos abertos na mesma precisão. Quem quer os 99,4% em casa hoje compra memória, e memória unificada é exatamente o que o Strix vende. Fazer offload pra RAM do PC é outro experimento, que eu não medi e não conto como vitória de ninguém.</p>
<p><strong>O Jebadiah a 39 ms é o resultado prático mais importante.</strong> 90,5% de acerto, mais rápido que qualquer hospedado, Jev incluso. Na faixa de até 9B, a 5090 transforma modelos locais na opção mais rápida e mais barata de todas, e a diferença pro Strix (205 ms) é de mundo inteiro: 5,3×.</p>
<p><strong>Modelo pequeno nem sente.</strong> Os três minúsculos (Julia, Laya multilingual, Kev 0.8B) melhoraram só 1,7× a 1,8×. Eles já eram limitados por overhead de runtime, com um pé na CPU, não por falta de GPU. Comprar uma 5090 pra servir encoder de 300M é comprar foguete pra buscar pão na padaria.</p>
<p><strong>O maior ganho foi do pior modelo.</strong> O GLiNER2.5-Decide acelerou 10,3× (208 pra 20 ms). Continua errando igual, só que rápido. A correlação entre &ldquo;quanto o modelo melhora na 5090&rdquo; e &ldquo;quanto o modelo presta&rdquo; é negativa nesta amostra, porque os modelos maiores e mais úteis são justamente os que mais tinham pra destravar.</p>
<p><strong>A linha de base escala junto.</strong> O Simple-Jev, que é um LLM de 4B inteiro lendo token, foi de 1.349 pra 161 ms (8,4×), chegando perto do Kev 9B no Strix. E o Mapika decepcionou comparado aos irmãos: 2× (186 pra 91 ms), o menor ganho dos 4B, sinal de que o runtime dele já era enxuto no Strix.</p>
<p>Fechando a conta de custo-benefício: pelo preço de só a placa 5090 você leva o minisforum inteiro. Se o seu trabalho é modelo de até 9B com latência de dezenas de milissegundos, a 5090 justifica cada dólar e o ROCm nem entra na discussão. Se o seu alvo é o topo de 27B rodando em casa, hoje a memória do Strix é o único caminho das duas máquinas, e um segundo e meio por decisão é o pedágio. A pergunta certa não é &ldquo;CUDA ou ROCm&rdquo;. É &ldquo;velocidade ou capacidade&rdquo;.</p>
<h2>Destaques e pegadinhas<span class="hx:absolute hx:-mt-20" id="destaques-e-pegadinhas"></span>
    <a href="#destaques-e-pegadinhas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>O Laya truncou todo o português longo.</strong> Na condição com distratores, o Laya base reportou truncamento nas 84 chamadas em português, descartando quase 31 mil tokens de estado, e em nenhuma das 84 em inglês. O limite de contexto padrão do checkpoint inglês é só 512 tokens (o multilingual e o typed usam 1.024, configurável até 8.192), e português tokeniza mais pesado. Os outros modelos sequer expõem telemetria de truncamento, então &ldquo;zero truncamento reportado&rdquo; neles significa &ldquo;não sei&rdquo;.</p>
<p><strong>Português custa mais tokens.</strong> Pros mesmos 84 pares de casos, o Jev reportou uns 14% mais tokens de entrada em português que em inglês, e os dois Clef uns 15% mais. Se você orçar uma operação bilíngue pela média em inglês, vai errar pra baixo.</p>
<p><strong>Exemplo no prompt salva modelo fraco? Salva nada.</strong> Um exemplo resolvido por classe deixou o Laya igual ou pior; vários agregados dele caíram. Ajudou o Kev 9B (mais 4 pontos) e o Clef em português (mais 3,6 pontos, chegando ao nível do Jev), e quase nada no Jev, um caso a mais em português. Valide exemplo em tráfego real antes de assumir que ajuda.</p>
<p><strong>Tamanho correlaciona, e explica pouco.</strong> Entre os 19 competidores de tamanho conhecido, a correlação de Spearman entre parâmetros e acerto compacto foi 0,96. A série Kev é mais informativa: 63% em 0,8B, 87% em 4B, 88% em 9B. O salto grande é de 0,8B pra 4B, uns 24 pontos, com intervalo entre 16 e 33. De 4B pra 9B a diferença compacta cruza zero, e vários 4B nominais, com backbones parecidos, deram resultados bem diferentes. Na família d3, o flagship e o Edge ficam a quase 40 pontos de distância sob o mesmo release, enquanto um 9B, o Microsoft, empata no topo dos hospedados. Tamanho ajuda até 4B; depois disso, a receita de treino manda mais.</p>
<p><strong>O Clef de 27B errou um ticket crítico que dois dos Laya acertaram.</strong> No v1, um cenário de severidade diz que perda contínua de dados em produção é crítica, independentemente de quantos clientes afeta. O registro descreve perda permanente confirmada, de um cliente só. Jev, Clef-flash, Perplexity, d3, Microsoft, Laya typed, Simple-Jev e Laya multilingual responderam &ldquo;crítico&rdquo;. Clef completo, OpenAI, Laya base, Eikos, Jebadiah, Mapika, Kev 4B e Kev 9B responderam &ldquo;degradado&rdquo;.</p>
<blockquote>
  <p>É um caso só, e taxa de erro exigiria muito mais. Mas mostra que a média esconde exatamente o erro que mais custa. Quando os fatos de impacto são estruturados, a regra de severidade devia estar em código, e o modelo entra só pra extrair os fatos do texto livre.</p>

</blockquote>
<p><strong>Hospedado e local divergem com o tempo.</strong> O Clef-flash local reproduziu o hospedado nas 520 classificações do v1 e divergiu em 19 das 504 do v2. A Cloudflare mantém a versão dos pesos por trás do alias hospedado em aberto. Se a reprodutibilidade importa, fixe o hash local.</p>
<p><strong>Confiança alta e segurança são coisas diferentes.</strong> No v2 compacto, Jev, Clef, os dois Clef-flash, Eikos, Jebadiah, OpenAI, Microsoft, Perplexity e o d3 erraram zero palpites acima de 0,9. O Perplexity é o caso extremo: respondeu nesse nível em 96% dos casos sem errar um, com o d3 logo atrás (94%, zero erros). O Microsoft cobriu 95% e o OpenAI só metade. A ressalva do Microsoft: o recorde limpo é só no compacto, porque com exemplos ou distratores ele erra em português com confiança acima de 99%. O Mapika errou 5% dos palpites nesse nível, o Kev 4B 3% e o Simple-Jev 14%. E os dois menores do lote quebraram o padrão com violência: o Julia errou 48 de 71 palpites acima de 0,9, e o d3-edge errou 14 de 68. A cobertura também variou: o Jev respondeu acima de 0,9 em quase 90% dos casos, o Clef em quase 80%, o Clef-flash em 40% e o Laya base em 2 de 168. No v1 o Jev errou 1% dos palpites acima de 0,9 e o Laya base errou 61%. Um limiar de confiança só serve depois de validado na sua distribuição, e o ECE baixo do Laya typed em português deixou ele nos mesmos 36% de acerto.</p>
<p><strong>O Perplexity não caiu em armadilha de apresentação.</strong> Nas três variantes do v1 (opções invertidas, rótulos opacos, comentário malicioso), ele manteve as mesmas respostas do original, sem virar uma única. O d3 repetiu o feito, zero viradas também. O Microsoft só tropeçou nos rótulos opacos (2 viradas, uma pra cada lado); ordem e comentário não mexeram nele. O OpenAI virou 2 em cada variante, e o Laya desabou pra 19% com o comentário. O preço dessa robustez é latência: um segundo e meio por decisão na minha máquina, a pior mediana do lote.</p>
<p><strong>Limiar numérico é problema de código.</strong> A tarefa de triagem por limiar, com números claros e regra explícita, deu 100% no Jev e nos dois Clef-flash e menos de 30% nos dois Laya em inglês. Um <code>if</code> resolve isso com 100% e zero latência. O modelo só entra quando o número está enterrado em texto livre, e mesmo assim o certo é extrair o número e deixar a comparação com o código.</p>
<p><strong>Probabilidade de sorteio é outra habilidade.</strong> Nas 24 sondagens sem informação (moeda, dado), o Jev teve o pior erro de escolha entre opções. A coroa da estimativa numérica mudou de dono com a estreia da OpenAI: o predicado de sim/não dela errou quase nada, enquanto a sonda de escolha devolveu 94% pra cara numa moeda honesta. O Perplexity, campeão de tudo o resto, respondeu 0,73 pra um evento de chance 1 em 4, e o Microsoft foi no mesmo caminho, dando 87% pra moeda. O Laya typed teve o segundo melhor erro de escolha, atrás só do Kev 9B e colado no Clef, e mesmo assim classificou mal. É o &ldquo;aluno que marca a letra A&rdquo; do ensaio do Paulo, agora medido: calibração em sorteio e acerto em classificação são medidas independentes.</p>
<h2>Onde os dados convergem<span class="hx:absolute hx:-mt-20" id="onde-os-dados-convergem"></span>
    <a href="#onde-os-dados-convergem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ol>
<li>
<p><strong>O topo geral é de pesos abertos, e agora são dois.</strong> O Perplexity Decider 27B e o Decision 3.0 empataram em 99,4% nos compactos, com 100% em português e rótulos idênticos nas 1.024 classificações, os dois com pesos Apache 2.0 rodando na minha máquina. A vantagem deles sobre o Jev é de menos de 2 pontos e o intervalo encosta no zero, então trate como fotografia deste conjunto. O custo deles é hardware e latência, não API. Na nuvem, a ordem de avaliação mudou: <strong>comece pelo Microsoft Decision-1</strong>, que empata com o Jev no agregado, fecha 100% em português compacto e custa um terço. O Jev entra em seguida, mais rápido e com erros diferentes, porque empate no agregado não é intercambiabilidade. O OpenAI Decisions ficou no meio do caminho em preço e acerto.</p>
</li>
<li>
<p><strong>Rodar local ficou viável em 4B.</strong> Jebadiah, Mapika e Kev 4B entregam entre 87 e 90% nos casos compactos com latência de 200 ms no Strix Halo e 40 a 91 ms na RTX 5090, e num piloto com a sua tarefa qualquer um dos três pode ganhar. Se a sua máquina aguenta 49 GB de pesos, o Perplexity entrega o topo da tabela em casa. A conta financeira justifica usar a máquina que já existe, por controle de dado ou por obrigação de ficar offline. Comprar máquina pra isso ela deixa de justificar.</p>
</li>
<li>
<p><strong>Modelo pequeno é outra categoria.</strong> Julgar o Laya e o Julia pela coluna de acerto é ler a tabela errada. Eles erram da metade pra cima de fábrica, caem feio sob comentário malicioso, e junto com o d3-edge são os únicos da lista que cabem num dispositivo fraco e respondem em dezenas de milissegundos. O Edge é o melhor dos três em acerto (61%), mas despenca em português e erra um em cada cinco palpites confiantes. Se precisão tipo Jev é requisito, risque os três. Se rodar offline em hardware pequeno é requisito, risque todo o resto e comece pelo ajuste fino deles na sua tarefa, que é a medição que falta. E cuidado com o Julia e o Edge em particular: 48 erros em 71 e 14 em 68 palpites confiantes são o retrato de modelos que não sabem o que não sabem.</p>
</li>
<li>
<p><strong>Português custa uns 14% mais tokens</strong> e derruba os modelos do meio da tabela entre 1 e 10 pontos, com Kev 9B e Clef-flash no pior caso. O Jev perde nada, o OpenAI empata as duas línguas, e o Perplexity, o d3 e o Microsoft fecham 100% em português compacto, melhor que o inglês deles, embora o Microsoft volte a errar em português quando o contexto cresce. O Julia faz o caminho contrário e despenca 13 pontos, e o Edge despenca 14. Pra audiência brasileira, essa é a primeira coluna a olhar, antes do preço.</p>
</li>
</ol>
<blockquote>
  <p>E o que vale pra todos: nenhum desses modelos substitui a regra que já está em código. Limiar numérico, política de severidade com fatos estruturados, permissão de ferramenta, tudo isso fica no <code>if</code>. O modelo entra pra transformar texto livre em fato estruturado. E o teste que importa é o seu, com os seus tickets, porque cada célula desta tabela tem 12 cenários e a sua operação tem milhares.</p>

</blockquote>
<p>Os casos, as respostas brutas de cada modelo, os scripts de pontuação e o guia pra reproduzir tudo estão no <a href="https://github.com/akitaonrails/jev-docs-benchmarks"target="_blank" rel="noopener">repositório do benchmark</a>. Se você rodar com os seus tickets e chegar em outro ranking, me conta.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>inteligencia-artificial</category><category>llms</category></item><item><title>LLM Benchmark: Kimi v2.7 code, GLM 5.2, Minimax M3 local</title><link>https://akitaonrails.github.io/2026/06/14/llm-benchmark-kimi-v2-7-code-glm-5-2-minimax-m3-local/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/06/14/llm-benchmark-kimi-v2-7-code-glm-5-2-minimax-m3-local/</guid><pubDate>Sun, 14 Jun 2026 13:00:00 -0300</pubDate><description>&lt;p&gt;Mais uma rodada do meu benchmark de coding. Dessa vez são três entradas open source: o &lt;strong&gt;Kimi K2.7 Code&lt;/strong&gt;, o &lt;strong&gt;GLM 5.2&lt;/strong&gt;, e o &lt;strong&gt;MiniMax M3&lt;/strong&gt; que ganhou pesos abertos mas que eu não consigo rodar em casa nem espremendo. Antes de chegar nos números, vale o contexto de sempre pra quem caiu aqui de paraquedas, e vale uma atualização da novela dos data centers, porque tem coisa nova.&lt;/p&gt;
&lt;h2&gt;Pra quem chegou agora&lt;span class="hx:absolute hx:-mt-20" id="pra-quem-chegou-agora"&gt;&lt;/span&gt;
&lt;a href="#pra-quem-chegou-agora" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Esse benchmark dá pra cada modelo o mesmo prompt e pede pra construir, sozinho, um app de chat estilo ChatGPT em Rails 8 + RubyLLM + Hotwire + Docker, com testes e CI. A nota sai de uma rubrica de 8 dimensões, de 0 a 100, em tiers A/B/C/D. A série inteira:&lt;/p&gt;</description><content:encoded><![CDATA[<p>Mais uma rodada do meu benchmark de coding. Dessa vez são três entradas open source: o <strong>Kimi K2.7 Code</strong>, o <strong>GLM 5.2</strong>, e o <strong>MiniMax M3</strong> que ganhou pesos abertos mas que eu não consigo rodar em casa nem espremendo. Antes de chegar nos números, vale o contexto de sempre pra quem caiu aqui de paraquedas, e vale uma atualização da novela dos data centers, porque tem coisa nova.</p>
<h2>Pra quem chegou agora<span class="hx:absolute hx:-mt-20" id="pra-quem-chegou-agora"></span>
    <a href="#pra-quem-chegou-agora" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse benchmark dá pra cada modelo o mesmo prompt e pede pra construir, sozinho, um app de chat estilo ChatGPT em Rails 8 + RubyLLM + Hotwire + Docker, com testes e CI. A nota sai de uma rubrica de 8 dimensões, de 0 a 100, em tiers A/B/C/D. A série inteira:</p>
<ol>
<li><a href="/2026/04/24/llm-benchmarks-parte-3-deepseek-kimi-mimo/">Benchmark canônico de Maio (Parte 3)</a>. A metodologia completa e a referência do ranking.</li>
<li><a href="/2026/05/04/llm-benchmarks-deepseek-unlocked-deepclaude/">DeepSeek destravado com DeepClaude</a>. Troca de harness tirando o V4 Pro do limbo.</li>
<li><a href="/2026/06/01/llm-benchmarks-grok-4-3-minimax-m3-opus-4-8/">Update Grok 4.3, MiniMax M3, Opus 4.8</a>. O M3 estreando via cloud.</li>
<li><a href="/2026/06/11/llm-benchmark-fable-5-e-a-novela-da-anthropic/">Fable 5 e a novela da Anthropic</a>. O primeiro Mythos-class público.</li>
</ol>
<h2>A novela dos data centers ganhou um capítulo: SpaceX<span class="hx:absolute hx:-mt-20" id="a-novela-dos-data-centers-ganhou-um-capítulo-spacex"></span>
    <a href="#a-novela-dos-data-centers-ganhou-um-cap%c3%adtulo-spacex" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Nos últimos posts eu venho batendo na mesma tecla: a construção de data center virou o gargalo da era da IA, e a demanda passou por cima da base instalada com folga. <a href="https://www.techradar.com/pro/if-one-piece-of-your-supply-chain-is-delayed-then-your-whole-project-cant-deliver-nearly-half-of-us-data-centers-planned-for-2026-canceled-or-delayed-and-things-could-soon-get-much-worse"target="_blank" rel="noopener">Quase metade dos data centers planejados nos EUA pra 2026 foi adiada ou cancelada</a>, travada em transformador e componente elétrico com fila de três anos. Detalhei isso no <a href="/2026/06/11/llm-benchmark-fable-5-e-a-novela-da-anthropic/">post do Fable</a>.</p>
<p>O capítulo novo é financeiro. A <strong>SpaceX abriu capital</strong> e foi o maior IPO da história de Wall Street: <a href="https://www.datacenterdynamics.com/en/news/spacex-ipo-musks-firm-set-to-launch-first-orbital-data-center-ai1-satellites-in-2027-will-put-compute-on-starlink-craft/"target="_blank" rel="noopener">556 milhões de ações a US$ 135, avaliação de US$ 1,77 trilhão, com mais de US$ 250 bilhões de demanda de investidor</a> contra os US$ 75 bilhões que a empresa buscava no início. E o pitch que sustentou essa avaliação foi justamente compute pra IA: os <strong>satélites AI1</strong>, &ldquo;data centers orbitais&rdquo; que a SpaceX pretende lançar a partir do fim de 2027, com <a href="https://www.scientificamerican.com/article/spacex-ipo-valuation-depends-on-starship-and-orbital-ai-data-centers/"target="_blank" rel="noopener">a empresa já fechando acordo de compute com Anthropic e Google</a>. Os IPOs da Anthropic e da OpenAI vêm logo atrás, e todo esse dinheiro tem um objetivo comum: acelerar a disponibilidade de compute, porque hoje ela não dá conta.</p>
<p>Aqui entra a parte que me interessa de verdade. Num futuro não tão distante, <strong>LLM open source pode ser parte da solução pra essa demanda</strong>. Se uma fatia das tarefas migrar pra modelo rodando localmente, na máquina do desenvolvedor, é menos carga batendo nos data centers das nuvens. Mas &ldquo;pode ser&rdquo; não é &ldquo;já é&rdquo;, e é exatamente sobre essa distância que esse benchmark existe.</p>
<h2>O recado de sempre: programação séria é Opus 4.8 ou GPT 5.5<span class="hx:absolute hx:-mt-20" id="o-recado-de-sempre-programação-séria-é-opus-48-ou-gpt-55"></span>
    <a href="#o-recado-de-sempre-programa%c3%a7%c3%a3o-s%c3%a9ria-%c3%a9-opus-48-ou-gpt-55" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu repito isso em todo post e não vou parar: trabalho de programação sério, hoje, se faz com <strong>Claude Opus 4.8 ou GPT 5.5</strong>, dentro do Claude Code ou do OpenCode. Ponto. Os modelos open source ainda não chegaram no ponto de tocar projeto de verdade, sessão longa, 100% vibe coded do começo ao fim. São pequenos demais, e a janela de contexto é limitada demais. Na minha régua, programação de verdade só fica viável acima de uns <strong>200 mil tokens</strong> de contexto, e a maioria dos open source não chega perto disso com qualidade. No meu benchmark, quase toda a oferta open source fica em Tier B ou abaixo.</p>
<p>Agora vem o &ldquo;mas&rdquo;. Eles não estão parados. A cada geração eles melhoram num ritmo constante, e as três entradas dessa rodada mostram isso preto no branco. O dia em que um open source virar Tier A consistente e rodar com 200k de contexto numa máquina que cabe na sua mesa, a conta dos data centers muda. Vamos aos números.</p>
<h2>Kimi K2.7 Code: quase lá, com uma regressão boba<span class="hx:absolute hx:-mt-20" id="kimi-k27-code-quase-lá-com-uma-regressão-boba"></span>
    <a href="#kimi-k27-code-quase-l%c3%a1-com-uma-regress%c3%a3o-boba" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Kimi K2.7 Code (exposto no OpenRouter como <code>moonshotai/kimi-k2.7-code</code>) marcou <strong>86/100, Tier A, #8</strong>. A $0.30 por run em 22 minutos, segue sendo um dos modelos mais baratos da Tier A inteira.</p>
<p>A integração com RubyLLM é genuína de ponta a ponta (<code>RubyLLM.chat</code> + <code>add_message</code> com replay de histórico completo + <code>complete</code> + <code>response.content</code>), verificada contra o source do gem 1.16.0. Persistência em session cookie que sobrevive a restart e é multi-worker safe, 22 testes que exercitam o caminho do LLM com mock de assinatura correta e teste de error path, Turbo Streams de verdade e três controllers Stimulus. Engenharia sólida.</p>
<p>Vale registrar uma armadilha metodológica que esse modelo expôs, porque ela é instrutiva. Meu scanner estrutural acusou <strong>seis chamadas <code>chat.user</code>/<code>chat.assistant</code> como alucinação</strong>, exatamente a assinatura inventada que jogou o GLM 5.1 pra Tier C meses atrás. Só que aqui eram todas falso positivo: resolvem pros métodos de domínio <strong>do próprio app</strong> (<code>Chat#user</code>, <code>Message.assistant</code>), não pra DSL do RubyLLM. Lendo o código na mão dava pra ver que a API real está certa. Se eu tivesse confiado cego no scanner, teria afundado um Tier A injustamente. É por isso que esse benchmark sempre lê a integração na mão, nunca só conta arquivo.</p>
<p>E aí vem o tropeço que segura ele <strong>abaixo</strong> do próprio K2.6 (87): o K2.7 Code <strong>não manda system prompt via <code>with_instructions</code></strong>. O assistente sobe sem persona e sem guardrail, o que é uma lacuna real pra um app &ldquo;estilo ChatGPT&rdquo; e é a principal regressão em relação ao K2.6, que tinha. Além disso, o cookie de sessão não tem cap de mensagens (risco de <code>CookieOverflow</code> em conversa longa), e o I/O do RubyLLM mora dentro do value object <code>Chat</code> em vez de num service isolado. Curioso: a variante &ldquo;Code&rdquo;, teoricamente especializada em programação, regrediu numa dimensão de produto que a versão geral acertava.</p>
<h2>GLM 5.2: a maior virada de uma versão pra outra no benchmark inteiro<span class="hx:absolute hx:-mt-20" id="glm-52-a-maior-virada-de-uma-versão-pra-outra-no-benchmark-inteiro"></span>
    <a href="#glm-52-a-maior-virada-de-uma-vers%c3%a3o-pra-outra-no-benchmark-inteiro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esse foi o destaque da rodada. O GLM 5.2 marcou <strong>87/100, Tier A, #6</strong>, e o tamanho do salto é o que impressiona: o GLM 5.1 tinha feito <strong>46/100, Tier C, #21</strong>. De uma versão pra outra, 41 pontos, três tiers, quinze posições. É a maior virada intra-família que eu já medi.</p>
<p>O bug que condenava o 5.1 era específico: ele inventava <code>chat.user</code>/<code>chat.assistant</code> pra montar o histórico multi-turn e crashava no turno 2. O 5.2 simplesmente usa o <code>chat.add_message(role:, content:)</code> real pra fazer o replay, e acabou. Todas as chamadas verificadas contra o source do gem 1.16.0, zero alucinação. Tem a <strong>injeção de dependência mais limpa de todo o grupo</strong>: tanto o client do RubyLLM quanto o <code>service_class</code> do controller são injetáveis, então a suíte de 26 testes mocka o caminho do LLM com <code>FakeChat</code>/<code>FakeClient</code> de assinatura correta, sem biblioteca externa de mock, exercitando streaming, aplicação de system prompt e wrapping de erro. Mira no slug certo (<code>anthropic/claude-sonnet-4.6</code>), Turbo Streams reais, Gemfile completo, Ruby 4.0.5 válido.</p>
<p>O que segura ele num empate com o Kimi K2.6 (e logo atrás dele) é a mesma dimensão que separou o Fable 5 do Opus 4.8: <strong>persistência</strong>. O store é um <code>Singleton</code> process-local <strong>sem cap</strong>, que morre no restart, não é multi-worker safe, e ainda cresce sem limite. O código é honesto sobre isso nos comentários, mas a rubrica pontua isso abaixo do cookie capado e multi-worker safe do Kimi. Foi também o run mais lento da Tier A, 43 minutos no endpoint de coding da Z.ai, que estava estrangulado em 12-55 tokens/s.</p>
<p>A leitura geral: GLM 5.1 → 5.2 é o exemplo mais claro de que open source não está estagnado. Um modelo que era lixo arquitetural virou Tier A em uma geração, corrigindo exatamente o bug que importava.</p>
<h2>MiniMax M3 local: o problema não é o modelo, é o hardware<span class="hx:absolute hx:-mt-20" id="minimax-m3-local-o-problema-não-é-o-modelo-é-o-hardware"></span>
    <a href="#minimax-m3-local-o-problema-n%c3%a3o-%c3%a9-o-modelo-%c3%a9-o-hardware" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui a história é diferente, e é a que mais interessa pro argumento de futuro. A MiniMax soltou <strong>pesos abertos</strong> do M3: existe um <a href="https://huggingface.co/unsloth/MiniMax-M3-GGUF"target="_blank" rel="noopener"><code>unsloth/MiniMax-M3-GGUF</code></a> e uma entrada <a href="https://ollama.com/library/minimax-m3"target="_blank" rel="noopener"><code>ollama.com/library/minimax-m3</code></a>. Disponibilidade não é o problema. <strong>Memória é.</strong></p>
<p>O M3 é um MoE/MSA de <strong>~428 bilhões de parâmetros totais e ~23 bilhões ativos por token</strong> (~427B nos safetensors; a NVIDIA lista ~22B ativos, por diferença de arredondamento/contagem) espalhados por <strong>128 experts, com 4 ativos por token</strong>. E aqui mora a pegadinha do MoE: mesmo que só ~23B ativem por token, o conjunto <strong>inteiro</strong> de pesos precisa estar residente em memória. Não dá pra paginar expert ocioso pra CPU sem introduzir latência que mata a inferência interativa. Ou seja, o número que importa é o tamanho total, não o ativo:</p>
<table>
  <thead>
      <tr>
          <th>Quantização</th>
          <th>Tamanho GGUF</th>
          <th style="text-align: center">RTX 5090 (32 GB)</th>
          <th style="text-align: center">Strix Halo (≤128 GB)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q4_K_M</td>
          <td>~264 GB</td>
          <td style="text-align: center">✗</td>
          <td style="text-align: center">✗</td>
      </tr>
      <tr>
          <td>Q3_K_M</td>
          <td>~195 GB</td>
          <td style="text-align: center">✗</td>
          <td style="text-align: center">✗</td>
      </tr>
      <tr>
          <td>1-bit</td>
          <td>~128 GB</td>
          <td style="text-align: center">✗</td>
          <td style="text-align: center">✗ (sem sobra pra KV cache / OS)</td>
      </tr>
  </tbody>
</table>
<p>Eu rodo o subset local desse benchmark num <a href="/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/">Minisforum MS-S1 Max, o Strix Halo com 128 GB unificados</a>. Não chega nem perto. Até o quant de 1-bit (~128 GB), que já seria inútil pra coding de qualquer jeito, consumiria a memória unificada inteira sem deixar nada pro KV cache ou pro sistema. A placa NVIDIA de 32 GB então nem entra na conversa. Não existe quant do M3 que rode com qualidade usável em nenhuma das duas máquinas. Rodar &ldquo;um MiniMax&rdquo; local hoje significaria pegar uma variante bem menor e mais fraca (tipo o distill <code>minimax-m2-tiny</code>), que é outro modelo, não o M3.</p>
<p>Quem <strong>consegue</strong> tentar? Mac Studio com 256 GB de RAM unificada ou mais. O Q3 (~195 GB) caberia num Mac Studio de 256 GB, o Q4 (~264 GB) só num de 512 GB. E se alguém rodar, a expectativa é que o resultado fique <strong>em torno da mesma nota do M3 via OpenRouter</strong>, os 78/100 Tier B que eu já registrei no <a href="/2026/06/01/llm-benchmarks-grok-4-3-minimax-m3-opus-4-8/">post anterior</a>: mesmo modelo, mesmos pesos, a quantização Q3/Q4 tira pouca coisa. Muda só onde roda, a entrega é a mesma. Ou seja, ninguém faria isso pela nota; faria pela soberania de rodar local. Mas a esse custo de hardware, não compensa.</p>
<h3>A convergência que eu enxergo (alvo: ~2030)<span class="hx:absolute hx:-mt-20" id="a-convergência-que-eu-enxergo-alvo-2030"></span>
    <a href="#a-converg%c3%aancia-que-eu-enxergo-alvo-2030" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>E é aqui que as duas histórias se encontram. O motivo de não existir solução local prática hoje é o mesmo motivo da seca de data center: <strong>fabricar chip continua caríssimo</strong>, porque a indústria inteira está com o foco travado em abastecer data center. Memória rápida e silício de ponta vão pra onde paga mais, e quem paga mais é hyperscaler comprando aos containers. Então pelos próximos dois anos, mais ou menos, a gente não vai ter uma solução local barata e boa. As peças não estão disponíveis a preço de mesa de desenvolvedor.</p>
<p>Mas o futuro próximo aponta pra uma convergência. Em algum momento a construção de data center bate num teto, seja por energia, por componente elétrico ou por saturação de demanda. Quando essa corrida desacelerar, a capacidade de fabricação de hardware sobra e começa a alcançar o resto do mercado, com silício cost-effective chegando pra rodar modelo local. Hoje os dois extremos não servem: o Mac Studio de 256 GB é caro demais pra ser &ldquo;acessível&rdquo;, e o Strix Halo, que é acessível, ainda é lento demais pra uso prático sério. Mas a próxima geração de hardware pode fechar exatamente esse vão, com a memória e a banda de um Mac Studio no preço de um mini PC.</p>
<p>Junta as duas pontas: modelos open source melhorando de forma constante (o pulo do GLM 5.2 é a prova) de um lado, e hardware local ficando viável conforme a corrida de data center esfria do outro. No dia em que um GLM ou um Kimi Tier A rodar com 200k de contexto numa máquina de mesa que custa o preço de um notebook bom, a equação inteira de compute muda. Meu chute pra esse encontro é algo como <strong>2030</strong>. Ainda longe, mas a gente está indo pra lá.</p>
<h2>Ranking atualizado<span class="hx:absolute hx:-mt-20" id="ranking-atualizado"></span>
    <a href="#ranking-atualizado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Kimi K2.7 Code e GLM 5.2 em negrito:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Rank</th>
          <th>Modelo</th>
          <th style="text-align: right">Score</th>
          <th style="text-align: center">Tier</th>
          <th style="text-align: center">RubyLLM OK</th>
          <th>Tempo</th>
          <th>Custo</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">1</td>
          <td>Claude Opus 4.7</td>
          <td style="text-align: right">97</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>18m</td>
          <td>~$1.10</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>GPT 5.4 xHigh (Codex)</td>
          <td style="text-align: right">97</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>22m</td>
          <td>~$16</td>
      </tr>
      <tr>
          <td style="text-align: right">3</td>
          <td>GPT 5.5 xHigh (Codex)</td>
          <td style="text-align: right">96</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>18m</td>
          <td>~$10</td>
      </tr>
      <tr>
          <td style="text-align: right">4</td>
          <td>Claude Opus 4.8</td>
          <td style="text-align: right">95</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>17m</td>
          <td>~$1.10</td>
      </tr>
      <tr>
          <td style="text-align: right">5</td>
          <td>Claude Fable 5</td>
          <td style="text-align: right">94</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>24m</td>
          <td>~$11 (est.)</td>
      </tr>
      <tr>
          <td style="text-align: right">6</td>
          <td>Kimi K2.6</td>
          <td style="text-align: right">87</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>20m</td>
          <td>~$0.30</td>
      </tr>
      <tr>
          <td style="text-align: right">6</td>
          <td><strong>GLM 5.2 (Z.ai)</strong></td>
          <td style="text-align: right"><strong>87</strong></td>
          <td style="text-align: center"><strong>A</strong></td>
          <td style="text-align: center">✅</td>
          <td><strong>43m</strong></td>
          <td><strong>subscription</strong></td>
      </tr>
      <tr>
          <td style="text-align: right"><strong>8</strong></td>
          <td><strong>Kimi K2.7 Code</strong></td>
          <td style="text-align: right"><strong>86</strong></td>
          <td style="text-align: center"><strong>A</strong></td>
          <td style="text-align: center">✅</td>
          <td><strong>22m</strong></td>
          <td><strong>~$0.30</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">9</td>
          <td>Claude Opus 4.6</td>
          <td style="text-align: right">83</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>16m</td>
          <td>~$1.10</td>
      </tr>
      <tr>
          <td style="text-align: right">10</td>
          <td>Gemini 3.1 Pro</td>
          <td style="text-align: right">82</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">✅</td>
          <td>14m</td>
          <td>~$0.40</td>
      </tr>
      <tr>
          <td style="text-align: right">11</td>
          <td>Claude Sonnet 4.6</td>
          <td style="text-align: right">78</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>16m</td>
          <td>~$0.63</td>
      </tr>
      <tr>
          <td style="text-align: right">11</td>
          <td>DeepSeek V4 Flash</td>
          <td style="text-align: right">78</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>3m</td>
          <td>~$0.01</td>
      </tr>
      <tr>
          <td style="text-align: right">11</td>
          <td>MiniMax M3</td>
          <td style="text-align: right">78</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>53m (fase 2 DNF)</td>
          <td>~$0.10</td>
      </tr>
      <tr>
          <td style="text-align: right">14</td>
          <td>Grok 4.3</td>
          <td style="text-align: right">72</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>15m</td>
          <td>~$1.74</td>
      </tr>
      <tr>
          <td style="text-align: right">15</td>
          <td>Qwen 3.6 Plus</td>
          <td style="text-align: right">71</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>17m</td>
          <td>~$0.15</td>
      </tr>
      <tr>
          <td style="text-align: right">16</td>
          <td>DeepSeek V4 Pro *</td>
          <td style="text-align: right">69</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>22m (DNF)</td>
          <td>~$0.50</td>
      </tr>
      <tr>
          <td style="text-align: right">16</td>
          <td>Kimi K2.5</td>
          <td style="text-align: right">69</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>29m</td>
          <td>~$0.10</td>
      </tr>
      <tr>
          <td style="text-align: right">18</td>
          <td>Xiaomi MiMo V2.5 Pro</td>
          <td style="text-align: right">67</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>11m</td>
          <td>~$0.14</td>
      </tr>
      <tr>
          <td style="text-align: right">19</td>
          <td>GLM 5</td>
          <td style="text-align: right">64</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">✅</td>
          <td>17m</td>
          <td>~$0.11</td>
      </tr>
      <tr>
          <td style="text-align: right">20</td>
          <td>Step 3.5 Flash</td>
          <td style="text-align: right">56</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">⚠️ bypass</td>
          <td>38m</td>
          <td>~$0.02</td>
      </tr>
      <tr>
          <td style="text-align: right">21</td>
          <td>Qwen 3.5 35B (local)</td>
          <td style="text-align: right">55</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">✅</td>
          <td>28m</td>
          <td>grátis</td>
      </tr>
      <tr>
          <td style="text-align: right">22</td>
          <td>GLM 4.7 Flash bf16 (local)</td>
          <td style="text-align: right">52</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">✅</td>
          <td>falhou</td>
          <td>grátis</td>
      </tr>
      <tr>
          <td style="text-align: right">23</td>
          <td>GLM 5.1 (Z.ai)</td>
          <td style="text-align: right">46</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">❌</td>
          <td>22m</td>
          <td>subscription</td>
      </tr>
      <tr>
          <td style="text-align: right">24</td>
          <td>DeepSeek V3.2</td>
          <td style="text-align: right">43</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">❌</td>
          <td>60m</td>
          <td>~$0.07</td>
      </tr>
      <tr>
          <td style="text-align: right">25</td>
          <td>MiniMax M2.7</td>
          <td style="text-align: right">41</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">❌</td>
          <td>14m</td>
          <td>~$0.30</td>
      </tr>
      <tr>
          <td style="text-align: right">26</td>
          <td>Qwen 3.5 122B (local)</td>
          <td style="text-align: right">37</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">❌</td>
          <td>43m</td>
          <td>grátis</td>
      </tr>
      <tr>
          <td style="text-align: right">27</td>
          <td>Qwen 3 Coder Next (local)</td>
          <td style="text-align: right">32</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">❌</td>
          <td>17m</td>
          <td>grátis</td>
      </tr>
      <tr>
          <td style="text-align: right">28</td>
          <td>Grok 4.20</td>
          <td style="text-align: right">25</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">❌</td>
          <td>8m</td>
          <td>~$0.60</td>
      </tr>
      <tr>
          <td style="text-align: right">29</td>
          <td>GPT OSS 20B (local)</td>
          <td style="text-align: right">11</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">❌</td>
          <td>falhou</td>
          <td>grátis</td>
      </tr>
  </tbody>
</table>
<p>* DeepSeek V4 Pro chega a Tier A (89/100) só via DeepClaude. História no <a href="/2026/05/04/llm-benchmarks-deepseek-unlocked-deepclaude/">post dedicado</a>.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pro trabalho de hoje, nada muda: Opus 4.8 e/ou GPT 5.5 seguem sendo a recomendação pra programação séria, e os melhores open source ainda empacam no teto da Tier B-baixa-Tier-A com janela de contexto curta. O Kimi K2.7 Code até regrediu uma casa por uma bobagem de system prompt, lembrete de que &ldquo;versão nova&rdquo; nem sempre é &ldquo;versão melhor&rdquo;.</p>
<p>Mas o GLM 5.2 saltando de Tier C pra #6 numa única geração é o tipo de sinal que eu fico de olho. O open source não está estagnado, está subindo de forma teimosa. E quando esse movimento encontrar o hardware ficando barato (a convergência que eu vejo lá por 2030), a história de onde a gente roda nossos coding agents vai ser bem diferente da de hoje. Por enquanto, é cloud, é Opus, é GPT. Mas eu continuo medindo, geração por geração, porque o dia que virar vai virar rápido.</p>
<p>O benchmark é aberto: <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">github.com/akitaonrails/llm-coding-benchmark</a>. Quer ver outro modelo aí? Fork, adiciona no <code>config/models.json</code>, roda e manda o PR.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>llms</category><category>modelos-locais</category></item><item><title>Como a ElevenLabs Não Foi Morta pelo Qwen3 TTS</title><link>https://akitaonrails.github.io/2026/04/09/como-a-elevenlabs-nao-foi-morta-pelo-qwen3-tts/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/09/como-a-elevenlabs-nao-foi-morta-pelo-qwen3-tts/</guid><pubDate>Thu, 09 Apr 2026 08:30:00 -0300</pubDate><description>&lt;p&gt;&lt;strong&gt;TL;DR — Escuta isso e continua lendo:&lt;/strong&gt;&lt;/p&gt;
&lt;audio controls preload="metadata" style="width: 100%; max-width: 640px;"&gt;
&lt;source src="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3" type="audio/mpeg"&gt;
Seu navegador não suporta o elemento de áudio. &lt;a href="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3"&gt;Baixar o mp3 aqui.&lt;/a&gt;
&lt;/audio&gt;
&lt;p&gt;Esse é o episódio do dia 6 de abril do podcast do &lt;a href="https://akitaonrails.github.io/tags/the-m-akita-chronicles/"&gt;The M.Akita Chronicles&lt;/a&gt;, já gerado com a nova pipeline da ElevenLabs v3. Assina o canal no &lt;a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener"&gt;Spotify&lt;/a&gt; pra não perder episódio novo desses.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Quando o Qwen3 TTS foi lançado, pelos idos de janeiro desse ano, todo mundo no Twitter/X e nas newsletters de IA gritou &amp;ldquo;ElevenLabs killer&amp;rdquo;. Tem &lt;a href="https://medium.com/@warpie/qwen3-tts-is-the-first-real-open-source-threat-to-elevenlabs-56ba200ab5ee"target="_blank" rel="noopener"&gt;artigo no Medium&lt;/a&gt; dizendo que é a primeira ameaça open source real à ElevenLabs. Tem &lt;a href="https://byteiota.com/qwen3-tts-3-second-voice-cloning-beats-elevenlabs/"target="_blank" rel="noopener"&gt;post da byteiota&lt;/a&gt; dizendo que a clonagem de voz em 3 segundos bate a ElevenLabs. Tem &lt;a href="https://www.analyticsvidhya.com/blog/2025/12/qwen3-tts-flash-review/"target="_blank" rel="noopener"&gt;análise no Analytics Vidhya&lt;/a&gt; falando que é o TTS open source mais realista já lançado. O consenso da internet entusiasta era: finalmente temos open source que faz frente à ElevenLabs, o jogo virou, é só questão de tempo.&lt;/p&gt;</description><content:encoded><![CDATA[<p><strong>TL;DR — Escuta isso e continua lendo:</strong></p>
<audio controls preload="metadata" style="width: 100%; max-width: 640px;">
  <source src="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3" type="audio/mpeg">
  Seu navegador não suporta o elemento de áudio. <a href="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3">Baixar o mp3 aqui.</a>
</audio>
<p>Esse é o episódio do dia 6 de abril do podcast do <a href="/tags/the-m-akita-chronicles/">The M.Akita Chronicles</a>, já gerado com a nova pipeline da ElevenLabs v3. Assina o canal no <a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener">Spotify</a> pra não perder episódio novo desses.</p>
<hr>
<p>Quando o Qwen3 TTS foi lançado, pelos idos de janeiro desse ano, todo mundo no Twitter/X e nas newsletters de IA gritou &ldquo;ElevenLabs killer&rdquo;. Tem <a href="https://medium.com/@warpie/qwen3-tts-is-the-first-real-open-source-threat-to-elevenlabs-56ba200ab5ee"target="_blank" rel="noopener">artigo no Medium</a> dizendo que é a primeira ameaça open source real à ElevenLabs. Tem <a href="https://byteiota.com/qwen3-tts-3-second-voice-cloning-beats-elevenlabs/"target="_blank" rel="noopener">post da byteiota</a> dizendo que a clonagem de voz em 3 segundos bate a ElevenLabs. Tem <a href="https://www.analyticsvidhya.com/blog/2025/12/qwen3-tts-flash-review/"target="_blank" rel="noopener">análise no Analytics Vidhya</a> falando que é o TTS open source mais realista já lançado. O consenso da internet entusiasta era: finalmente temos open source que faz frente à ElevenLabs, o jogo virou, é só questão de tempo.</p>
<p>Eu resolvi testar no meu próprio fluxo de produção, como de costume. Montei um pipeline inteiro em cima do Qwen3 TTS 1.7B pra gerar o podcast semanal do <a href="/tags/the-m-akita-chronicles/">The M.Akita Chronicles</a>, e documentei os bastidores no <a href="/2026/02/18/servindo-ia-na-nuvem-meu-tts-pessoal-bastidores-do-the-m-akita-chronicles/">post sobre servir IA na nuvem</a>. Quem quiser ver o detalhe de tempo de partida a frio, clonagem de voz, parâmetros de sampling que mudam de um modo pro outro, dá uma olhada nesse link que eu não vou repetir tudo aqui.</p>
<p>A pergunta desse post é diferente. Depois de quase dois meses rodando essa configuração em produção, com episódio indo ao ar toda segunda-feira, ontem à noite eu finalmente desliguei o Qwen3 e passei tudo pra ElevenLabs v3. Vou contar por quê.</p>
<h2>O que não funcionou no Qwen3<span class="hx:absolute hx:-mt-20" id="o-que-não-funcionou-no-qwen3"></span>
    <a href="#o-que-n%c3%a3o-funcionou-no-qwen3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Entre 15 de fevereiro e 30 de março eu fiz dezenas de commits ajustando o fluxo do podcast: prompts, parâmetros de sampling, ordem de geração, silêncios de referência na amostra de clone de voz, normalização de volume, pronúncia de siglas técnicas. Corrigir a voz do Marvin que cortava a primeira sílaba porque o áudio de referência começava sem silêncio inicial. Afinar a voz do Akita pra ela soar mais confiante e assertiva. Adicionar crossfade entre jingles de seção. Resolver a pronúncia errada de &ldquo;podcast&rdquo; pra ela não sair como &ldquo;pódcast&rdquo;. Mandar o gerador de roteiro preferir português a anglicismo gratuito pro TTS não engasgar. Cada um desses ajustes foi uma sessão de horas escutando áudio, gerando de novo, ajustando parâmetro.</p>
<p>O resultado ficou aceitável. &ldquo;Aceitável&rdquo; no sentido de que eu consegui publicar todo episódio sem ter que regravar nada à mão. Mas escutando com atenção, a voz do Qwen3 tem aquele jeito inconfundível de IA gerando áudio. Intonação meio morta, ritmo uniforme. Em trechos longos, você sente que é máquina falando. Serve pra ir pro ar, mas tá a quilômetros do que você escuta num podcast profissional feito por gente.</p>
<p>O problema pior foi a pronúncia em inglês. Meu podcast cobre notícias de tecnologia, então termos como &ldquo;MCP&rdquo;, &ldquo;RAG&rdquo;, &ldquo;Claude Opus&rdquo;, &ldquo;GPT-5&rdquo;, &ldquo;open source&rdquo; aparecem em toda conversa. O Qwen3 pegava esses termos e pronunciava eles com sotaque brasileiro, tipo &ldquo;ó-péne-ssourssê&rdquo;, coisa assim. Ficava ilegível pro ouvinte. A solução que eu tive que implementar foi mapear manualmente no prompt do LLM que gera o roteiro quais palavras em inglês trocar por equivalente em português. O prompt hoje tem uma seção inteira dividida entre &ldquo;manter em inglês&rdquo; (nomes próprios, marcas, termos já incorporados ao brasileiro) e &ldquo;traduzir pro português&rdquo; (anglicismo gratuito), mais ou menos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">**REPLACE with Portuguese** (common English words that have natural
</span></span><span class="line"><span class="cl">Brazilian Portuguese equivalents):
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;update&#34; → &#34;atualização&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;release&#34; → &#34;lançamento&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;feature&#34; → &#34;recurso/funcionalidade&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;deploy&#34; → &#34;implantação&#34; or just &#34;colocar em produção&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;trade-off&#34; → &#34;dilema&#34; or &#34;escolha&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;performance&#34; → &#34;desempenho&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;default&#34; → &#34;padrão&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;insight&#34; → &#34;percepção/sacada&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;skills&#34; → &#34;habilidades&#34;
</span></span><span class="line"><span class="cl"><span class="k">-</span> &#34;approach&#34; → &#34;abordagem&#34;
</span></span><span class="line"><span class="cl">- &#34;highlights&#34; → &#34;destaques&#34;</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Chamam isso aqui em casa de &ldquo;limpar anglicismo pra o TTS não engasgar&rdquo;. Engraçado porque eu não queria esse nível de restrição no meu roteiro. Queria que a voz simplesmente pronunciasse &ldquo;update&rdquo; quando o contexto natural fosse &ldquo;update&rdquo;. Como o modelo não consegue, tive que mutilar o vocabulário do podcast pra o resultado final ficar escutável. É uma solução paliativa, daquelas que você adiciona torcendo pra poder remover depois quando a tecnologia amadurecer.</p>
<h2>A experiência com a ElevenLabs v3<span class="hx:absolute hx:-mt-20" id="a-experiência-com-a-elevenlabs-v3"></span>
    <a href="#a-experi%c3%aancia-com-a-elevenlabs-v3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ontem à tarde abri uma conta na ElevenLabs, comprei o plano Pro (US$99/mês), e comecei a experimentar com o modelo <code>eleven_v3</code>, que saiu em fevereiro desse ano. Trinta minutos depois eu tinha uma prova de conceito rodando, e umas duas horas depois o sistema inteiro do podcast migrou. A diferença de esforço é abissal.</p>
<p>Os detalhes técnicos da migração ficaram documentados num doc interno do projeto, então vou resumir aqui o quadro comparativo que importa:</p>
<table>
  <thead>
      <tr>
          <th>Dimensão</th>
          <th>Qwen3 TTS 1.7B (antigo)</th>
          <th>ElevenLabs <code>eleven_v3</code> (atual)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qualidade (Akita)</td>
          <td>Boa, clone de áudio real</td>
          <td>Melhor, mesmo clone mas prosódia mais natural</td>
      </tr>
      <tr>
          <td>Emoção inline no roteiro</td>
          <td>Não suporta</td>
          <td><code>[sighs]</code>, <code>[sarcastically]</code>, <code>[excited]</code>, <code>[laughs]</code>, funciona em pt-BR</td>
      </tr>
      <tr>
          <td>Tempo de partida a frio</td>
          <td>5 a 15 min subindo GPU na RunPod antes de cada rodagem</td>
          <td>Zero, chamada HTTPS com resposta imediata</td>
      </tr>
      <tr>
          <td>Vazão</td>
          <td>~1× do tempo real (serializado)</td>
          <td>~6× do tempo real com concorrência 4</td>
      </tr>
      <tr>
          <td>Tempo total pra um episódio de 28 min</td>
          <td>~25 a 30 min</td>
          <td><strong>~4 min</strong></td>
      </tr>
      <tr>
          <td>Superfície operacional</td>
          <td>RunPod, Docker, FastAPI, pesos do Qwen, conta de GPU</td>
          <td>Uma variável de ambiente (<code>ELEVENLABS_API_KEY</code>)</td>
      </tr>
      <tr>
          <td>Custo por episódio</td>
          <td>~$0.08 de GPU</td>
          <td>~$2.70 em créditos ElevenLabs</td>
      </tr>
  </tbody>
</table>
<p>Repara no último ponto. O Qwen3 custa menos de dez centavos de dólar por episódio. A ElevenLabs custa quase trinta vezes mais. E mesmo assim vale a pena. Os outros pontos do quadro resolvem problemas que sugavam horas da minha semana. Eu não preciso mais escrever código pra escalar GPU na nuvem, não preciso esperar a máquina ligar toda vez, não preciso ficar de babá do modelo nem reconstruir a imagem Docker quando o peso muda. A operação virou uma linha de configuração.</p>
<p>E o mais interessante: as tags de emoção no meio do texto. O modelo v3 aceita marcadores tipo <code>[sighs]</code>, <code>[sarcastically]</code>, <code>[dryly]</code>, <code>[excited]</code>, e muda a entonação de acordo. Isso funciona em mais de 70 idiomas, incluindo português do Brasil. Isso transformou a geração do roteiro porque agora eu posso pedir ao LLM que monta o roteiro pra colocar tags emocionais nos momentos certos, o que dá uma vivacidade que o Qwen3 não conseguia entregar nem de longe. Um exemplo concreto do que sai no roteiro depois:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>AKITA: Isso é simples. [dismissive] Quem ainda acredita que Bitcoin
vai morrer não tá prestando atenção.
MARVIN: [sighs] Mais uma semana, mais uma leva de devs confiando
cegamente em pacotes npm. Previsível.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eu tenho até duas paletas separadas de tags, uma pro Akita (expressivo mas controlado, usa <code>[excited]</code>, <code>[dismissive]</code>, <code>[emphatic]</code>) e outra pro Marvin (estóico, só <code>[sighs]</code>, <code>[sarcastically]</code>, <code>[tired]</code>, <code>[dryly]</code>). Isso tá tudo codificado nos prompts de geração do roteiro pro LLM saber que personagem pode usar o quê.</p>
<h2>Sobre a voz do Marvin<span class="hx:absolute hx:-mt-20" id="sobre-a-voz-do-marvin"></span>
    <a href="#sobre-a-voz-do-marvin" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pros ouvintes que já se acostumaram com a voz do Marvin, fica tranquilo: eu fiz o clone dele na ElevenLabs usando a mesma amostra de áudio que eu já tinha usado pra treinar no Qwen3. É a mesma voz. Só que agora ela soa ainda melhor, porque o modelo da ElevenLabs captura nuance e prosódia que o Qwen3 não conseguia entregar.</p>
<h2>Escuta aí e me conta<span class="hx:absolute hx:-mt-20" id="escuta-aí-e-me-conta"></span>
    <a href="#escuta-a%c3%ad-e-me-conta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra provar que o papo é real, aqui tá o episódio de segunda-feira, dia 6 de abril, já gerado com a nova pipeline:</p>
<audio controls preload="metadata" style="width: 100%; max-width: 640px;">
  <source src="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3" type="audio/mpeg">
  Seu navegador não suporta o elemento de áudio. <a href="https://makita-news.s3.amazonaws.com/podcasts/episodes/2026-04-06.mp3">Baixar o mp3 aqui.</a>
</audio>
<p>Se você já é ouvinte do podcast no <a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener">Spotify</a> e escutou os episódios anteriores feitos com Qwen3, compara e me diz nos comentários se você nota a diferença ou se pra você tanto faz. Eu tô curioso de saber quanto é percepção treinada minha escutando horas de áudio de TTS e quanto é diferença óbvia pra ouvinte casual.</p>
<p>A partir da próxima semana, todos os episódios do podcast serão gerados pela ElevenLabs v3. A newsletter já tá ligada na nova pipeline, os jobs agendados de pré-aquecer a GPU na RunPod foram desativados, e o código legado do Qwen3 fica no repositório como plano B caso um dia o v3 dê problema crônico. Em duas edições de arquivo eu volto pra ele. Provavelmente nunca vou ter que voltar.</p>
<h2>A parte de dublar os vídeos do YouTube<span class="hx:absolute hx:-mt-20" id="a-parte-de-dublar-os-vídeos-do-youtube"></span>
    <a href="#a-parte-de-dublar-os-v%c3%addeos-do-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora o gancho pra segunda metade desse post. No <a href="/2026/04/09/20-anos-de-blog-o-ano-em-que-a-ia-finalmente-me-deixou-traduzir-tudo/">artigo de aniversário que eu publiquei mais cedo hoje</a>, contei que o Claude Code traduziu quase metade do meu blog pra inglês num fim de semana. No mesmo espírito, fui atrás de dublar os vídeos do canal <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">Akitando</a>.</p>
<p>O canal tem 146 episódios, algo como 96 horas de conteúdo técnico em português, e mais de 500 mil inscritos. Eu já tinha as legendas traduzidas (um <code>.srt</code> curado por episódio), e o YouTube até oferece dublagem automática em inglês. Mas o resultado é tipo Google Translate de 2015: entende, passa a ideia, mas ninguém quer ouvir por muito tempo.</p>
<p>Testei as três abordagens de voz da ElevenLabs e só uma servia. Speech-to-Speech converte voz mas não traduz. A API de Dubbing traduz mas cria a voz sozinha, sem deixar forçar um clone específico. Só a Text-to-Speech resolvia: pegar meu <code>.srt</code> em inglês, mandar cada bloco pro endpoint TTS com a minha voz clonada, e montar o áudio alinhado com o vídeo original.</p>
<p>Só que ter o <code>.srt</code> traduzido não basta. Tradução crua de legenda não sobrevive ao TTS. Trechos com código-fonte, URLs, hashes hexadecimais, listas de comandos shell — o modelo entra em modo soletrar e o áudio sai com o dobro da duração esperada. Traduções longas demais estouram a janela de tempo e precisam ser condensadas pra a voz não ficar atropelada. SRTs truncadas precisam ser completadas. E a cada correção, rodar a pipeline de novo, escutar, achar o próximo problema, corrigir, repetir. O processo inteiro foi um ciclo de interrupções, correções manuais e re-runs — longe do &ldquo;aperta o botão e sai dublado&rdquo; que a gente imagina antes de meter a mão.</p>
<p>O grande desafio era o sotaque. Minha voz clonada foi treinada em português brasileiro, então quando tenta falar inglês o sotaque puxado aparece. A ElevenLabs tem a tag <code>[American accent]</code> que funciona em v3, mas em cima de uma voz treinada em outra língua ela é fraca — o sotaque brasileiro ainda saía por baixo. A saída foi treinar uma segunda voz minha, só em inglês. Gravei uns minutos no meu melhor sotaque americano, subi como Instant Voice Clone separado na minha conta, batizei de &ldquo;Akita English&rdquo;, e configurei o pipeline pra usar essa voz por padrão. O resultado sai mais natural, sem precisar de tag nenhuma, e a identidade de voz continua sendo a minha.</p>
<h3>O teto honesto de qualidade da minha voz em inglês<span class="hx:absolute hx:-mt-20" id="o-teto-honesto-de-qualidade-da-minha-voz-em-inglês"></span>
    <a href="#o-teto-honesto-de-qualidade-da-minha-voz-em-ingl%c3%aas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Vamos ser francos. Eu sou brasileiro falando inglês, não sou nativo, e não tenho tempo nem paciência pra treinar pronúncia por horas. O clone só consegue ser tão bom quanto a amostra original — se a amostra é um não-nativo lendo um roteiro, o clone herda as imperfeições. Nenhum clone vai me fazer soar como &ldquo;Fabio falando inglês perfeito&rdquo;, porque esse som não existe pra o modelo imitar.</p>
<p>Pra chegar na versão que tá rodando, gravei cinco amostras de treino diferentes ao longo do dia, cada uma com ritmo e roteiro diferentes, e rodei um A/B ao vivo comparando com o original em português. Nenhuma soou como nativo, e nunca foi esse o objetivo. A meta realista era mais modesta: soar reconhecivelmente como eu, ler conteúdo técnico sem tropeçar, e aguentar um episódio inteiro sem cansar o ouvinte. O vencedor acabou sendo a primeira iteração, a &ldquo;Akita English&rdquo; original. Longe de perfeito, mas suficiente pra colocar no ar. Trocar no futuro é literalmente uma linha no arquivo de configuração.</p>
<p>O que mais me surpreendeu no caminho foi perceber que o ritmo da amostra importa mais que o timbre. O clone aprende a cadência de quem gravou: a iteração 5 ficou 25% mais lenta que a iteração 2 lendo exatamente o mesmo texto, só porque eu gravei aquela amostra mais devagar. Pra dublagem de vídeo tech, o alvo certo é ritmo de explicação de YouTube, vivo, uns 150 palavras por minuto. Nada de ritmo de audiobook.</p>
<h3>Como eu alinho o áudio com o vídeo original<span class="hx:absolute hx:-mt-20" id="como-eu-alinho-o-áudio-com-o-vídeo-original"></span>
    <a href="#como-eu-alinho-o-%c3%a1udio-com-o-v%c3%addeo-original" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um ponto não óbvio: texto em inglês tende a ser mais longo ao falar do que o português equivalente. Se você simplesmente gera o áudio de cada legenda e cola no tempo original, o dub vai se acumulando. Com 30 minutos de vídeo, você já tá vários segundos fora de sincronismo.</p>
<p>Minha abordagem foi atacar o problema em camadas. A primeira versão dividia o SRT em blocos menores, chamados de &ldquo;chunks&rdquo;. Cada chunk tinha no máximo 700 caracteres (pra v3 não alucinar) e só podia cortar em fim de frase, nunca no meio. Um algoritmo simples acumulava legendas num buffer até chegar perto do teto, depois voltava procurando a última legenda do buffer que terminava com ponto, exclamação ou interrogação, e fazia flush só até ali. O resto das legendas já acumuladas ficava pra compor o próximo chunk. Cada chunk guardava o timestamp de início e fim das legendas que cobria, pra a montagem saber exatamente onde posicionar aquele pedaço de áudio depois.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Pra cada nova legenda que chega, checa se adicionar ela</span>
</span></span><span class="line"><span class="cl"><span class="c1"># estouraria o limite do chunk atual.</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">cue</span> <span class="ow">in</span> <span class="n">cues</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">projected</span> <span class="o">=</span> <span class="n">buffer_char_len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">+</span> <span class="nb">len</span><span class="p">(</span><span class="n">cue</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">projected</span> <span class="o">&gt;</span> <span class="n">max_chars</span> <span class="ow">and</span> <span class="n">buf</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Procura a última legenda do buffer que termina</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># com pontuação final (&#39;.&#39;, &#39;!&#39;, &#39;?&#39;, &#39;…&#39;).</span>
</span></span><span class="line"><span class="cl">        <span class="n">sentence_end</span> <span class="o">=</span> <span class="n">last_sentence_end_index</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">sentence_end</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="n">buf</span> <span class="o">=</span> <span class="n">flush</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">sentence_end</span><span class="p">,</span> <span class="n">chunks</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Run-on maior que max_chars sem fim de frase.</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># Raro, mas acontece. Avisa e corta mesmo assim.</span>
</span></span><span class="line"><span class="cl">            <span class="n">log</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s2">&#34;run-on sentence &gt; </span><span class="si">%d</span><span class="s2"> chars — &#34;</span>
</span></span><span class="line"><span class="cl">                        <span class="s2">&#34;splitting mid-sentence as a last resort&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">max_chars</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">buf</span> <span class="o">=</span> <span class="n">flush</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">chunks</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">cue</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Soft-break: se o buffer já tá pelo menos 60% cheio E</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># a legenda atual terminou uma frase, esvazia agora.</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Mantém os chunks balanceados em torno de 60 a 100% do max.</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">cue</span><span class="o">.</span><span class="n">ends_sentence</span> <span class="ow">and</span> <span class="n">buffer_char_len</span><span class="p">(</span><span class="n">buf</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">max_chars</span> <span class="o">*</span> <span class="mf">0.6</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">buf</span> <span class="o">=</span> <span class="n">flush</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">chunks</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tecnicamente, esse foi o ponto de virada: em vez de deixar a estrutura da dublagem seguir a estrutura visual da legenda, o chunker passou a seguir a estrutura real dos parágrafos do roteiro. Não vou repetir aqui toda a novela de como cheguei nisso e por que isso acabou forçando rerender em massa, porque volto nessa história mais abaixo, na parte dos problemas que só apareceram depois do primeiro upload. O ponto técnico aqui é simples: quando a janela passou a representar um parágrafo falado de verdade, o áudio começou a soar natural e o alvo de stretch pôde subir de 92% pra 95%.</p>
<p>Feito isso, ainda sobra o problema do inglês ser mais longo ao falar que o português equivalente. Aqui a sacada é prever se um chunk vai estourar a janela alvo <strong>antes</strong> de gerar o áudio. Se a razão <code>caracteres / 16 caracteres-por-segundo</code> já passa da duração alvo com uma margem, a pipeline manda o parâmetro <code>speed</code> direto pra API da ElevenLabs, pedindo pro modelo gerar o áudio nativamente mais rápido. Isso preserva a prosódia muito melhor do que comprimir depois. O teto é 1.15× (acima disso a voz começa a soar atropelada).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># antes de chamar a API, prevê se vai estourar</span>
</span></span><span class="line"><span class="cl"><span class="n">expected_sec</span> <span class="o">=</span> <span class="n">char_count</span> <span class="o">/</span> <span class="n">EXPECTED_CHARS_PER_SEC</span>  <span class="c1"># 16 chars/s</span>
</span></span><span class="line"><span class="cl"><span class="n">predicted_ratio</span> <span class="o">=</span> <span class="n">expected_sec</span> <span class="o">/</span> <span class="n">target_sec</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">voice_speed</span> <span class="o">=</span> <span class="mf">1.0</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">predicted_ratio</span> <span class="o">&gt;</span> <span class="n">PREEMPTIVE_SPEED_THRESHOLD</span><span class="p">:</span>   <span class="c1"># 1.05</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice_speed</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">predicted_ratio</span> <span class="o">*</span> <span class="mf">0.98</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="n">PREEMPTIVE_SPEED_MAX</span><span class="p">)</span>         <span class="c1"># cap em 1.15</span>
</span></span><span class="line"><span class="cl">    <span class="n">voice_settings</span><span class="p">[</span><span class="s2">&#34;speed&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="n">voice_speed</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mesmo com o <code>speed</code> preventivo ligado, às vezes o áudio gerado ainda fica um pouquinho mais longo do que a janela alvo. Pra isso existe uma segunda rede de proteção: medir o áudio real com <code>ffprobe</code> depois de gerado, comparar com a janela alvo, e aplicar o filtro <code>atempo</code> do <code>ffmpeg</code> pra comprimir no pós-processo se passou de 2% de tolerância, com teto de 1.20×. A combinação do <code>speed</code> nativo (1.15×) com o <code>atempo</code> (1.20×) dá uma compressão efetiva de até 1.38×, suficiente pra caber naturalmente mesmo nos casos mais extremos sem quebrar qualidade.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># depois de gerar o chunk</span>
</span></span><span class="line"><span class="cl"><span class="n">actual</span> <span class="o">=</span> <span class="n">ffprobe_duration</span><span class="p">(</span><span class="n">chunk_path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">ratio</span> <span class="o">=</span> <span class="n">actual</span> <span class="o">/</span> <span class="n">target_sec</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">ratio</span> <span class="o">&gt;</span> <span class="n">FIT_TOLERANCE</span><span class="p">:</span>       <span class="c1"># 1.02</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># comprime o áudio sem mexer no pitch</span>
</span></span><span class="line"><span class="cl">    <span class="n">ffmpeg_atempo</span> <span class="o">=</span> <span class="nb">min</span><span class="p">(</span><span class="n">ratio</span><span class="p">,</span> <span class="n">FIT_MAX_ATEMPO</span><span class="p">)</span>   <span class="c1"># 1.20</span>
</span></span><span class="line"><span class="cl">    <span class="n">apply_atempo</span><span class="p">(</span><span class="n">chunk_path</span><span class="p">,</span> <span class="n">ffmpeg_atempo</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando um chunk gerado fica mais curto que a janela alvo, o pipeline estende o áudio levemente (sem afetar pitch) pra reduzir o silêncio feio depois que ele termina. O objetivo não é preencher a janela inteira, uma pausa natural entre frases é boa, mas suavizar os casos mais gritantes de silêncio que dariam a impressão de &ldquo;áudio cortado&rdquo;.</p>
<p>Num episódio grande, 95 minutos, 194 chunks, o desvio cumulativo total ficou em -0,7%. Uns 43 segundos de drift ao longo do episódio inteiro, imperceptível enquanto você assiste.</p>
<p>O que salvou o orçamento nessa arquitetura é que cada chunk fica salvo individualmente no disco como um <code>.mp3</code> separado. A pipeline mantém um manifesto com o texto normalizado de cada chunk, e antes de chamar a API da ElevenLabs, compara o texto atual com o cache. Se o texto não mudou, reutiliza o áudio já gerado sem gastar crédito nenhum. Se eu reescrevo uma cue problemática, só os chunks afetados por aquela cue são regenerados — o resto do episódio fica intacto.</p>
<p>Isso é o que viabilizou as iterações. Eu rodava o batch, escutava trechos, identificava um problema (tradução longa demais, snippet de código que o TTS não conseguia pronunciar, chunking que cortou num ponto ruim), corrigia o SRT ou ajustava os parâmetros do chunker, e rodava de novo. Cada re-run consumia uma fração dos créditos e do tempo do run original, porque só os chunks alterados eram regenerados. Sem esse cache, cada iteração teria custado quase o mesmo que a primeira rodada, e o custo total do batch teria sido duas ou três vezes maior.</p>
<p>Essa mudança de chunking também teve impacto pesado no cache e no custo, mas deixo essa parte pro trecho mais abaixo onde explico a sequência de retrabalho. Aqui basta dizer que, do ponto de vista técnico, era a mudança certa.</p>
<h3>Quando o <code>atempo</code> não salva: reescrevendo cue antes do TTS<span class="hx:absolute hx:-mt-20" id="quando-o-atempo-não-salva-reescrevendo-cue-antes-do-tts"></span>
    <a href="#quando-o-atempo-n%c3%a3o-salva-reescrevendo-cue-antes-do-tts" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nem tudo é questão de alinhamento de janela. Tem um tipo de cue que quebra o pipeline inteiro, onde nem <code>speed</code> preventivo nem <code>atempo</code> no pós-processo resolvem, e eu só esbarrei nisso quando comecei a rodar o batch nos episódios mais técnicos do canal.</p>
<p>O problema é o seguinte. O v3 lê a uns 16 caracteres por segundo quando você entrega texto normal de conversa. Mas quando você entrega uma cue recheada de URL literal, hash hexadecimal, string binária, uma lista de números ordenados ou um bloco de código shell, o modelo entra em modo &ldquo;soletrar letra por letra&rdquo; e despenca pra uns 9 caracteres por segundo. Uma cue de 500 caracteres que devia virar 30 segundos de áudio vira 55. A checagem de sanidade derruba (porque passou de 1.8×), o retry automático tenta de novo com os mesmos 500 caracteres, os cinco retries falham seguidos, e o chunk fica preso.</p>
<p>E isso não aconteceu por acaso. Eu escrevia desse jeito porque sabia que esses mesmos roteiros depois virariam post no blog, então fazia sentido deixar a URL crua, o comando completo, o hash inteiro, o detalhe técnico todo mastigado pro leitor. Em português isso funcionava bem porque era exatamente o meu fluxo original: gravar o vídeo e depois publicar o texto correspondente no blog. O que eu nunca tinha preparado era a etapa seguinte, de transformar esse mesmo material em dublagem automatizada pra inglês. Eu só fui perceber esse conflito quando a pipeline começou a falhar de verdade.</p>
<p>Peguei isso primeiro no ep052, o guia de Ubuntu pra devs iniciantes, onde duas cues traziam URLs do <code>github.com</code>, URLs <code>hkp://keyserver.ubuntu.com</code> e um hash GPG de 40 caracteres. Tentar resolver no pós-processo seria perda de tempo. O teto de 1.20× do <code>atempo</code> nunca vai comprimir 55 segundos em 30, e mesmo se conseguisse ia sair num chipmunk ilegível. A saída é atacar antes, no texto.</p>
<p>A solução foi reescrever a cue dizendo o que o comando faz, em vez de mostrar o comando literal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">- Rode: apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 \
</span></span></span><span class="line"><span class="cl"><span class="gd">-   --recv-keys 0A6A3E7F79F93EF8AAB9E92BAEBB74C8B5A1E44D
</span></span></span><span class="line"><span class="cl"><span class="gi">+ Rode o comando completo que tá na descrição do vídeo pra importar
</span></span></span><span class="line"><span class="cl"><span class="gi">+ a chave de assinatura do repositório do keyserver do Ubuntu.
</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A legenda em inglês no vídeo continua mostrando o comando inteiro na tela, então quem lê a legenda vê o que precisa digitar. O áudio dublado descreve a intenção em linguagem falada, que é o que o TTS entrega sem engasgar. A instrução pro ouvinte fica intacta, e o caractere-por-segundo volta pros 16 esperados, então o áudio gerado cabe na janela original sem precisar esticar nem comprimir nada no pós.</p>
<p>Saí caçando cue assim em todos os episódios do batch. Varri do ep052 ao ep146 procurando cues com densidade alta de caractere &ldquo;duro&rdquo; (dígitos, colchetes, operadores, URLs longas, binário, hex) e acabei reescrevendo umas 30 cues em 13 episódios:</p>
<ul>
<li>ep052 Ubuntu pra devs: URLs do GitHub e hash GPG de 40 caracteres</li>
<li>ep091 Hello World em C: exemplos binários</li>
<li>ep095 Memória 640kB: wrap de endereço, segment-offset</li>
<li>ep106 CodeMiner: endereço de e-mail soletrado</li>
<li>ep113 Compressão: strings de binário puro, 75% de caractere duro</li>
<li>ep115 SQL Server: lista longa de números ordenados</li>
<li>ep120 Internet: endereços IP com pontos</li>
<li>ep121 Sockets: dois blocos de código JavaScript</li>
<li>ep122 Proxies: headers User-Agent do Chrome</li>
<li>ep123 Rede segura: one-liners shell, flags do Docker</li>
<li>ep126 Gentoo: demo de <code>chroot</code> em C</li>
<li>ep136 Containers: URL de release do GitHub</li>
<li>ep144 Criptografia: endereço de signing key</li>
</ul>
<p>O padrão foi sempre o mesmo: mantém a legenda exibindo o código, a URL ou o hash na íntegra, e reescreve o texto pro narrador descrever o que aquilo faz. Eu podia ter automatizado a reescrita, deixando um LLM ler a cue e propor uma substituição no voo, só que achei mais seguro revisar tudo manualmente. É exatamente o tipo de coisa onde o modelo resolve &ldquo;melhorar&rdquo; um comando pra deixá-lo mais limpo e sai com shell errado. A varredura manual levou umas duas horas. A automática teria custado o mesmo tempo em revisão, com o bônus da ansiedade de ter deixado passar algum comando mutilado.</p>
<p>Teve também um caso estrutural esquisito no ep146, sobre Docker Compose. Duas cues ficaram grudadas por causa de uma linha em branco faltando no SRT, e o <code>pysrt</code> tratava as duas como uma única cue gigante. O TTS nem chegava a processar, o chunker engasgava antes. Corrigi à mão adicionando a linha em branco que faltava. Fix de um caractere, meia hora pra rastrear.</p>
<h3>Reconstruindo SRTs truncadas<span class="hx:absolute hx:-mt-20" id="reconstruindo-srts-truncadas"></span>
    <a href="#reconstruindo-srts-truncadas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Outra armadilha apareceu quando fui rodar o batch completo: três episódios tinham legendas em inglês curtas demais em relação à duração real do vídeo. O ep056, sobre Rails, era o caso mais absurdo. Dezenove minutos de legenda pra um vídeo de 80 minutos. Sessenta e dois minutos de conteúdo sem legenda nenhuma. O ep057 (WSL 2) tinha 14 minutos faltando, e o ep068 (Git Direito) tinha 2.</p>
<p>Isso é rastro do meu fluxo de tradução antigo. Em algum momento eu comecei a revisar a legenda manualmente, parei no meio, e o arquivo <code>.en.srt</code> ficou salvo truncado no ponto onde eu parei. Não dá pra dublar um vídeo de 80 minutos com legenda de 19. O chunker gera áudio até onde consegue ler e simplesmente não sabe o que fazer com o resto do vídeo.</p>
<p>A solução virou um script novo. Ele faz diff entre o <code>.en.srt</code> truncado e o <code>.pt-orig.srt</code> (o auto-caption bruto do YouTube, que sempre cobre o vídeo inteiro), pega as cues que existem só em português, manda pro Claude Sonnet 4.6 com um schema JSON rígido pedindo tradução cue a cue, e cola o resultado de volta no fim do arquivo em inglês. O schema é o detalhe que importa. Quando eu tentei pedir a resposta em texto no formato <code>N|texto</code>, o Claude deixava cair umas 20% das cues no caminho. Com um schema JSON estrito, a taxa de drop caiu pra zero nos três reparos.</p>
<p>O resultado em números:</p>
<ul>
<li>ep068 Git Direito: +50 cues / 2 minutos reconstruídos</li>
<li>ep057 WSL 2: +368 cues / 14 minutos</li>
<li>ep056 Rails: +1445 cues / 62 minutos (e ainda drop de 5 cues falsas que o tradutor antigo tinha inventado no fim pra tampar a saída)</li>
</ul>
<p>Depois dos reparos, os três episódios entraram no batch normalmente e foram dublados como qualquer outro. Tipo de ferramenta que você espera nunca precisar, só que quando precisa, compensa escrever uma vez e resolver o problema inteiro de uma vez.</p>
<h3>Tags de emoção automáticas<span class="hx:absolute hx:-mt-20" id="tags-de-emoção-automáticas"></span>
    <a href="#tags-de-emo%c3%a7%c3%a3o-autom%c3%a1ticas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Enquanto eu tava testando, resolvi adicionar mais um passo no pipeline: um &ldquo;emotion tagger&rdquo; que lê o SRT em inglês antes de mandar pro TTS e insere tags tipo <code>[sarcastic]</code>, <code>[thoughtful]</code>, <code>[emphatic]</code>, <code>[deadpan]</code> em pontos onde um narrador humano naturalmente mudaria o tom. A ideia é imitar o que um ator profissional faria, colocar ênfase nos momentos certos, sem transformar o vídeo num teatro de emoções exageradas.</p>
<p>Essa parte é delicada por dois motivos. O primeiro: deixar um LLM solto editando o SRT tem risco real de ele &ldquo;melhorar&rdquo; o texto (trocar uma palavra aqui, reescrever uma frase ali) e você acabar com uma dublagem que não bate com a legenda. O segundo: LLM adora exagerar. Se você pedir pra marcar emoção, ele vai colocar tag em toda segunda frase. Pra evitar os dois, eu fixei uma lista pequena de tags permitidas e rodo uma validação de round-trip depois que a resposta do Claude chega:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">ALLOWED_TAGS</span><span class="p">:</span> <span class="nb">frozenset</span><span class="p">[</span><span class="nb">str</span><span class="p">]</span> <span class="o">=</span> <span class="nb">frozenset</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[sarcastic]&#34;</span><span class="p">,</span> <span class="s2">&#34;[thoughtful]&#34;</span><span class="p">,</span> <span class="s2">&#34;[emphatic]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[deadpan]&#34;</span><span class="p">,</span> <span class="s2">&#34;[serious]&#34;</span><span class="p">,</span> <span class="s2">&#34;[amused]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[sighs]&#34;</span><span class="p">,</span> <span class="s2">&#34;[exasperated]&#34;</span><span class="p">,</span> <span class="s2">&#34;[confident]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;[matter-of-fact]&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">validate_tagged</span><span class="p">(</span><span class="n">original</span><span class="p">,</span> <span class="n">tagged</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;&#34;Garante que o SRT com tags preserva o original sem drift.&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">original</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">tagged</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="s2">&#34;cue count mismatch&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">o</span><span class="p">,</span> <span class="n">t</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">original</span><span class="p">,</span> <span class="n">tagged</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># O index e o timestamp têm que bater byte a byte.</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">o</span><span class="o">.</span><span class="n">index</span> <span class="o">!=</span> <span class="n">t</span><span class="o">.</span><span class="n">index</span> <span class="ow">or</span> <span class="n">o</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">!=</span> <span class="n">t</span><span class="o">.</span><span class="n">timestamp</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;cue </span><span class="si">{</span><span class="n">o</span><span class="o">.</span><span class="n">index</span><span class="si">}</span><span class="s2">: header drift&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># Qualquer tag fora da whitelist invalida tudo.</span>
</span></span><span class="line"><span class="cl">        <span class="n">bad</span> <span class="o">=</span> <span class="n">find_disallowed_tags</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">text</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">bad</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;cue </span><span class="si">{</span><span class="n">o</span><span class="o">.</span><span class="n">index</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">bad</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1"># O texto sem as tags tem que ser idêntico ao original.</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># Se o LLM trocou uma palavra, reescreveu algo, a</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># validação quebra e a resposta é rejeitada.</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">strip_tags</span><span class="p">(</span><span class="n">o</span><span class="o">.</span><span class="n">text</span><span class="p">)</span> <span class="o">!=</span> <span class="n">strip_tags</span><span class="p">(</span><span class="n">t</span><span class="o">.</span><span class="n">text</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">            <span class="k">raise</span> <span class="n">TagValidationError</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;cue </span><span class="si">{</span><span class="n">o</span><span class="o">.</span><span class="n">index</span><span class="si">}</span><span class="s2">: text drift&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando a validação quebra, a pipeline ignora a resposta e reemite o request (ou, se falhar muito, sobe o SRT sem tags mesmo). Sobre a quantidade de tags, o prompt que vai pro Claude diz explicitamente: mira em <code>N/10</code> tags pra um SRT de N legendas, chão de 1 tag a cada 12, teto de 1 tag a cada 7, distribuição equilibrada entre os quatro quartos do episódio, nunca duas tags adjacentes a menos de 4 legendas uma da outra. Esse conjunto de regras foi a sexta iteração — as cinco anteriores ou enchiam tudo de tag ou concentravam no começo do vídeo.</p>
<h3>Não deixe a v3 alucinar<span class="hx:absolute hx:-mt-20" id="não-deixe-a-v3-alucinar"></span>
    <a href="#n%c3%a3o-deixe-a-v3-alucinar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um detalhe crítico que só aparece em produção: a ElevenLabs v3 tende a alucinar quando você manda blocos de texto muito longos. Já peguei blocos onde o texto tinha uns 1.500 caracteres e o modelo gerou um áudio com <strong>nove minutos</strong> quando o esperado era um minuto e meio. O modelo simplesmente resolve continuar falando sozinho, inventando conteúdo.</p>
<p>A documentação oficial da ElevenLabs recomenda manter cada chamada abaixo de 800 caracteres pra evitar isso. Eu fui mais conservador e cortei em 700, sempre em fim de frase (nunca no meio de uma sentença, porque o corte no meio soa horrível quando concatena). Depois disso, subi o parâmetro <code>stability</code> pra 0.9 (modo Robust, mais estável porém menos responsivo a tags de emoção), liguei o <code>apply_text_normalization</code> pra o modelo pronunciar números e siglas direito, e adicionei uma verificação de sanidade que rejeita qualquer áudio gerado que passe de 1.8× da duração esperada.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DEFAULT_VOICE_SETTINGS</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;stability&#34;</span><span class="p">:</span> <span class="mf">0.9</span><span class="p">,</span>          <span class="c1"># modo Robust</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;similarity_boost&#34;</span><span class="p">:</span> <span class="mf">0.95</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;style&#34;</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;use_speaker_boost&#34;</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">EXPECTED_CHARS_PER_SEC</span> <span class="o">=</span> <span class="mf">16.0</span>
</span></span><span class="line"><span class="cl"><span class="n">CHUNK_SANITY_MAX_FACTOR</span> <span class="o">=</span> <span class="mf">1.8</span>   <span class="c1"># rejeita se actual &gt; 1.8× esperado</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Depois dessas mitigações, um episódio de 95 minutos (194 blocos) rodou do começo ao fim sem nenhuma alucinação. Exatamente um bloco falhou por erro 502 transitório da API, que o retry automático pegou na tentativa seguinte.</p>
<h3>Masterização pro YouTube<span class="hx:absolute hx:-mt-20" id="masterização-pro-youtube"></span>
    <a href="#masteriza%c3%a7%c3%a3o-pro-youtube" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O YouTube não publica número oficial, mas o consenso da indústria é que a normalização de volume dele na hora da reprodução mira em -14 LUFS. Se você entregar áudio mais alto que isso, o YouTube baixa; se entregar mais baixo, ele deixa como tá. Pra bater o alvo exato, o pipeline roda o filtro <code>loudnorm</code> do <code>ffmpeg</code> em duas passadas. A primeira mede o áudio inteiro e imprime as estatísticas (<code>input_i</code>, <code>input_tp</code>, <code>input_lra</code>, <code>input_thresh</code>) em formato JSON no stderr. A segunda passada lê essas estatísticas de volta e aplica um ganho estático linear pra pousar exato em -14 LUFS, -1.5 dBTP:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Passada 1: medir</span>
</span></span><span class="line"><span class="cl">ffmpeg -i final_en.mp3 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -af <span class="s2">&#34;highpass=f=80,loudnorm=I=-14:TP=-1.5:LRA=9:print_format=json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -f null -
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Passada 2: aplicar ganho linear com os valores medidos</span>
</span></span><span class="line"><span class="cl">ffmpeg -i final_en.mp3 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -af <span class="s2">&#34;highpass=f=80,loudnorm=I=-14:TP=-1.5:LRA=9\
</span></span></span><span class="line"><span class="cl"><span class="s2">:measured_I=-18.3:measured_TP=-3.1:measured_LRA=5.4\
</span></span></span><span class="line"><span class="cl"><span class="s2">:measured_thresh=-28.7:offset=1.2:linear=true&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -ar <span class="m">48000</span> -ac <span class="m">2</span> -c:a pcm_s16le final_en_mastered.wav</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Duas passadas em vez de uma porque o modo de passada única roda em compressão dinâmica e acaba &ldquo;bombeando&rdquo; o ganho em trechos com muito silêncio. Com <code>linear=true</code> na passada 2, o ganho fica estático em cima das medições da passada 1, então não tem bombeamento. O resultado pousa dentro de ±0.1 LU do alvo, que é praticamente inaudível. O filtro <code>highpass=f=80</code> na frente derruba rumble de ar-condicionado e hum de rede elétrica abaixo de 80 Hz, que o ouvido humano não escuta mas mexe nas medições de pico.</p>
<h3>O ponto em que eu achei que estava pronto. E não estava.<span class="hx:absolute hx:-mt-20" id="o-ponto-em-que-eu-achei-que-estava-pronto-e-não-estava"></span>
    <a href="#o-ponto-em-que-eu-achei-que-estava-pronto-e-n%c3%a3o-estava" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui entra a parte mais chata, e mais educativa, do projeto.</p>
<p>Eu realmente achei que tinha acertado a mão na primeira grande rodada. A pipeline fechou os 146 episódios, eu subi tudo, e parecia resolvido. A duração batia, os arquivos estavam todos lá, o processo inteiro tinha finalmente atravessado o lote completo de quase 96 horas de vídeo. Só que foi justamente depois do upload que comecei a perceber problemas que eu simplesmente não tinha antecipado.</p>
<p>O primeiro susto foi a situação dos jingles. Eu já sabia que muitos episódios do Akitando começam com aquele jingle instrumental curto antes de eu começar a falar, mas subestimei o problema. O <code>.srt</code> do YouTube praticamente não ajuda nisso, porque jingle não é fala. Às vezes ele marca como <code>[Música]</code>, às vezes não marca nada útil, às vezes a janela fica torta. Na primeira montagem, teve episódio onde o jingle sumiu, episódio onde ele entrou duplicado, e episódio onde o source-fill do áudio original já trazia o jingle certo e o meu splice vinha por cima, empurrando todo o resto pra frente. Foi o tipo de bug que só aparece quando você para pra escutar a master final de verdade, não quando olha só pra duração total.</p>
<p>A solução foi abandonar a esperança de que a legenda me diria onde estava o jingle e ir direto pra fonte. Montei um detector usando os próprios áudios originais dos vídeos e jingles de referência, com auditoria e repair pass em cima dos masters já gerados. Isso acabou virando um mini-sistema próprio: detector, auditoria, reassemble por cache, correção por janela exata. No fim dessa novela, o batch de episódios alterados caiu num conjunto de reupload bem definido e o problema dos jingles ficou finalmente sob controle. Foi um daqueles casos clássicos de produção: a primeira solução parecia suficiente até bater no acervo inteiro.</p>
<p>Só que aí veio a segunda pancada, e essa foi mais cara: eu tinha esquecido de um detalhe fundamental sobre legendas de YouTube. SRT é feita pra ser lida na tela, não pra virar roteiro de fala. Então uma ideia que no meu texto original era um parágrafo inteiro aparecia quebrada em várias cues pequenas, cada uma com sua microjanela de tempo. Na primeira versão da pipeline eu estava usando essas cues como unidade de geração na ElevenLabs. Resultado: o TTS gerava áudio em blocos coerentes localmente, mas a montagem introduzia silêncios artificiais no meio da frase. Tecnicamente alinhado, humanamente esquisito.</p>
<p>O conserto foi doloroso porque exigiu voltar várias casas. Em vez de respeitar a estrutura das cues do SRT, precisei voltar pros meus textos originais e reconstruir o chunking com base nos parágrafos que eu realmente li quando gravei os vídeos. Quase todo episódio do Akitando tem o post correspondente com a seção <code>## Script</code>, e isso salvou o projeto. Como eu fui meticuloso na época em que produzi os vídeos — escrevendo os roteiros antes, lendo exatamente aquilo na gravação e arquivando tanto os vídeos quanto os scripts — eu tinha a fonte de verdade. Então deu pra mapear os parágrafos do script aos timings do <code>.pt-BR.srt</code>, reagrupar o <code>.en.srt</code> em cima disso e regenerar os chunks no formato certo.</p>
<p>O problema é que essa mudança destrói o cache anterior. Mesmo quando o texto quase não muda, se a fronteira do chunk muda, a chamada ao TTS muda junto: muda contexto, muda prosódia, muda começo, muda fim. Então não era um retoquezinho. Era uma regeneração completa de tudo de novo. Foi aí que o custo começou a sair do meu controle inicial.</p>
<p>E quando eu achei que agora sim tinha terminado, veio a terceira paulada: os clipes externos. Muitos vídeos do canal têm trechos inseridos de outras fontes. No episódio de Ruby on Rails, por exemplo, tem 37signals, Apple, comerciais, pedaços externos no meio da narrativa. O <code>.srt</code> do YouTube não sabia lidar direito com isso. Em alguns pontos ele simplesmente alucinava texto, em outros os timings ficavam completamente tortos, em outros a fala original não tinha nada a ver com a legenda que eu estava usando de base. De novo: olhando só pra automação parecia tudo bem; escutando a master final, não estava.</p>
<p>A correção, mais uma vez, foi sair da legenda e voltar pro material original. Montei um detector de janelas de clipe externo a partir de gaps de alinhamento entre transcript, <code>.pt-BR.srt</code>, cues sem fala útil e chunks que atravessavam essas janelas. Depois, em vez de tentar dublar o que não fazia sentido dublar, o processo passou a recuperar o áudio original do vídeo exatamente nesses trechos e preencher a master com o source correto. Foi isso que consertou os casos de 37signals, Apple Education, comercial de Mac vs PC e outros inserts parecidos que eu tinha esquecido que existiam.</p>
<p>Esse foi o aprendizado mais importante dessa segunda metade do projeto: automação te leva muito longe, mas o acervo completo sempre tem mais cantos escuros do que você imagina no primeiro passe.</p>
<h2>Os números do batch de dublagem (e por que doeu mais do que eu imaginei)<span class="hx:absolute hx:-mt-20" id="os-números-do-batch-de-dublagem-e-por-que-doeu-mais-do-que-eu-imaginei"></span>
    <a href="#os-n%c3%bameros-do-batch-de-dublagem-e-por-que-doeu-mais-do-que-eu-imaginei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu preciso me corrigir de novo. Não errei só a primeira conta. Errei também o grau de confiança que eu tinha depois da primeira rodada.</p>
<p>Quando subi pro plano Business da ElevenLabs, eu achei que finalmente estaria com folga. Na prática, não fiquei. O screenshot mais recente do dashboard mostra <strong>10.046.279 créditos consumidos de um limite de 11.000.000</strong>, sobrando <strong>953.721</strong>:</p>
<p><img src="elevenlabs-credits-dashboard.png" alt="Dashboard da ElevenLabs mostrando 10.046.279 créditos consumidos de 11.000.000"  loading="lazy" /></p>
<p>Em português claro: eu torrei mais de 91% do limite do workspace Business. E isso ajuda a entender o que &ldquo;10 milhões de créditos&rdquo; realmente significa nesse contexto. Não é só &ldquo;renderizei 146 episódios uma vez&rdquo;. É renderizar 146 episódios, descobrir um problema estrutural, voltar, corrigir, regenerar chunks, descobrir outro problema estrutural, voltar, reconstruir masters, auditar jingles, reparar janelas de clipes externos, reupar de novo. Cada rodada a mais custa. E quando a mudança invalida o cache, custa muito.</p>
<p>O custo também não para na ElevenLabs. Todo o trabalho de curadoria das legendas, reescrita de cues problemáticas, reconstrução de SRTs truncadas, auditorias de jingle, detector de clipes externos e o resto rodou via Claude Code no plano Claude Max 20×. Somando isso com a tradução em massa do blog pra inglês que eu descrevi no <a href="/2026/04/09/20-anos-de-blog-o-ano-em-que-a-ia-finalmente-me-deixou-traduzir-tudo/">artigo de aniversário</a>, o limite semanal do Claude Max 20× bateu 100%, com gasto extra acima de R$ 300. Pra quem acha que IA generativa vira grátis depois que você assinou o plano, os boletos discordam.</p>
<p>Mesmo assim, continuo achando que vale a pena. Estamos falando de 146 episódios, quase 96 horas de conteúdo técnico, mais de 5 anos de acervo do canal. Fazer isso manualmente em estúdio, com ator, direção, pickup e revisão humana em tudo, custaria uma ordem de grandeza completamente diferente.</p>
<p>O que mudou foi minha visão do projeto. No começo eu tava olhando pra isso como &ldquo;um batch grande&rdquo;. No fim, era outra coisa. Era mexer num acervo inteiro, achar exceção escondida, consertar ferrugem antiga, refazer master, rerender, reouvir.</p>
<p>O primeiro batch completo levou pouco menos de dois dias e me fez pensar que eu tinha resolvido tudo. Não tinha. A segunda rodada, com chunking por parágrafo, já foi cara. A terceira, com os reparos de jingle e os consertos de masters, mais cara ainda. E a quarta pancada veio dos clipes externos que eu não tinha modelado direito na primeira automação.</p>
<p>Mas agora, finalmente, eu acho que acabou. Ou melhor: eu espero que tenha acabado. Tudo foi enviado de novo, pela última vez. Não está perfeito no sentido absoluto da palavra. Está tão bom quanto eu consigo deixar com script, auditoria e automação, sem entrar num inferno de intervenção manual vídeo por vídeo.</p>
<p>E se isso tudo foi possível, é porque anos atrás eu tive a disciplina de produzir os vídeos do jeito certo: escrever primeiro, ler o roteiro quando gravava, e arquivar os materiais depois. Sem os vídeos originais e sem os scripts originais, eu não teria como descobrir onde o <code>.srt</code> mentia, onde o timing estava torto, onde o clipe externo entrava, onde o parágrafo original começava e terminava. Essa organização antiga foi o que permitiu consertar o projeto agora.</p>
<p>Então sim: a dublagem dos 146 episódios está pronta. Não perfeita. Mas fechada. Pelo menos, eu sinceramente espero que agora sim esteja.</p>
<h2>O primeiro teste, assista<span class="hx:absolute hx:-mt-20" id="o-primeiro-teste-assista"></span>
    <a href="#o-primeiro-teste-assista" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Fiz um vídeo teste pra provar que funciona. Dois avisos antes: primeiro, o embed abaixo já carrega com a legenda em inglês ligada por padrão, então esse lado dá pra resolver via URL. Segundo, o áudio é chato: o YouTube removeu o seletor de faixa de áudio dos players embedados lá por março desse ano. Então dentro do embed você não vai encontrar essa opção no menu de engrenagem mesmo, só roda em português. Pra ouvir a dublagem em inglês de verdade, clica em <strong><a href="https://www.youtube.com/watch?v=QNLd8TZ_JQc"target="_blank" rel="noopener">assistir direto no YouTube</a></strong>, que a página principal do YouTube ainda tem o seletor de faixa de áudio no menu de engrenagem.</p>
<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/QNLd8TZ_JQc?cc_load_policy=1&amp;cc_lang_pref=en&amp;hl=en"
    title="Akitando dublado em inglês (teste)"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>
<p>Se você tá acostumado com a dublagem automática do YouTube ou com as dublagens de IA de TikTok, compara. A diferença é gritante. A voz é a minha, com sotaque americano trabalhado, e a sincronização com o vídeo original fica dentro de 1 a 2% de desvio cumulativo, praticamente imperceptível em 95 minutos de vídeo.</p>
<h2>E o que faltava: traduzir as thumbnails<span class="hx:absolute hx:-mt-20" id="e-o-que-faltava-traduzir-as-thumbnails"></span>
    <a href="#e-o-que-faltava-traduzir-as-thumbnails" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando eu estava fechando esse post, me caiu a ficha de que tinha deixado uma ponta solta. As 146 thumbnails do meu canal estão em português, muitas com título em caixa alta grande tipo &ldquo;9 DICAS PARA PALESTRANTES&rdquo; ou &ldquo;7 RECOMENDAÇÕES DE SHOWS PARA PESSOAS DE TECH&rdquo;. De nada adianta áudio em inglês perfeito se a imagem que aparece na busca é um bloco de texto ilegível pra quem não lê português. O YouTube permite subir uma thumbnail alternativa por idioma, então eu precisava gerar versões em inglês, mantendo o resto da arte idêntico, trocando só o texto.</p>
<p>A ferramenta usa duas peças. <strong><code>yt-dlp</code></strong> é o fork moderno do antigo <code>youtube-dl</code>, uma CLI em Python que baixa qualquer coisa do YouTube (vídeos, áudios, legendas, thumbnails) sem precisar de API key. <strong>Nano Banana Pro</strong> (<code>nano-banana-pro-preview</code> na API) é o modelo de edição de imagem mais recente do Google Gemini — aceita uma imagem de entrada com um prompt e devolve a imagem editada, preservando o resto da composição quando você pede pra mexer só numa parte.</p>
<p>O pipeline tem dois passos. Passo 1: <code>yt-dlp</code> pega a thumbnail em <code>.jpg</code>, sem baixar o vídeo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yt-dlp --skip-download <span class="se">\
</span></span></span><span class="line"><span class="cl">       --write-thumbnail <span class="se">\
</span></span></span><span class="line"><span class="cl">       --convert-thumbnails jpg <span class="se">\
</span></span></span><span class="line"><span class="cl">       -o <span class="s2">&#34;thumbnails/originals/&lt;slug&gt;/&lt;video_id&gt;&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">       <span class="s2">&#34;https://www.youtube.com/watch?v=&lt;video_id&gt;&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Passo 2: mandar cada imagem pro Gemini Nano Banana Pro com um prompt que é um contrato rígido de requisitos duros, não só um &ldquo;traduz isso&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">TASK:
</span></span><span class="line"><span class="cl">Detect every piece of Portuguese text visible on this image and
</span></span><span class="line"><span class="cl">translate it to clear, natural American English. Replace the
</span></span><span class="line"><span class="cl">Portuguese text with the English translation in the same visual
</span></span><span class="line"><span class="cl">position.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">STRICT REQUIREMENTS — follow every one:
</span></span><span class="line"><span class="cl">- Preserve EVERY non-text element identically: face, pose,
</span></span><span class="line"><span class="cl">  expression, background, color palette, lighting, icons, logos,
</span></span><span class="line"><span class="cl">  decorative shapes, borders, layout. Only the text changes.
</span></span><span class="line"><span class="cl">- The English translation must be IDIOMATIC and CONFIDENT — not
</span></span><span class="line"><span class="cl">  a literal word-for-word rewrite. It&#39;s a YouTube thumbnail for a
</span></span><span class="line"><span class="cl">  tech audience, so use punchy phrasing a native English-speaking
</span></span><span class="line"><span class="cl">  tech YouTuber would write.
</span></span><span class="line"><span class="cl">- Match the original text&#39;s font family, weight, size, color,
</span></span><span class="line"><span class="cl">  stroke outline, drop shadow, and any decorative treatment.
</span></span><span class="line"><span class="cl">- If there is no Portuguese text at all on the image, return the
</span></span><span class="line"><span class="cl">  original image unchanged.</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A cláusula &ldquo;return the original image unchanged&rdquo; é o que salva os primeiros episódios do canal, que não têm texto na thumbnail, só minha cara. Sem ela, o modelo invariavelmente ia &ldquo;melhorar&rdquo; a composição, trocar a iluminação, etc.</p>
<p>Olha o resultado em dois exemplos (episódios 10 e 11):</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0;">
<div><strong>Original (PT)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep010_pt.jpg" alt="Thumbnail original em português: 7 Recomendações de Shows para pessoas de Tech" style="width: 100%;"></div>
<div><strong>Traduzida (EN)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep010_en.jpg" alt="Thumbnail traduzida em inglês: 7 TV Shows You Must Watch If You're In Tech" style="width: 100%;"></div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 16px 0;">
<div><strong>Original (PT)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep011_pt.jpg" alt="Thumbnail original em português: 9 Dicas para Palestrantes: venda sua caneta" style="width: 100%;"></div>
<div><strong>Traduzida (EN)</strong><br><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260409_thumb_ep011_en.jpg" alt="Thumbnail traduzida em inglês: 9 Tips for Speakers: sell your pen" style="width: 100%;"></div>
</div>
<p>Repara no detalhe do episódio 10. O modelo não traduziu &ldquo;7 Recomendações de Shows para Pessoas de Tech&rdquo; literal pra &ldquo;7 Show Recommendations for Tech People&rdquo;, que seria a versão tradutor automático. Ele reformulou pra &ldquo;7 TV Shows You Must Watch If You&rsquo;re In Tech&rdquo;, como um tech YouTuber americano de verdade escreveria. O resto da imagem fica idêntico pixel a pixel. No episódio 11, &ldquo;9 DICAS PARA PALESTRANTES: venda sua caneta&rdquo; vira &ldquo;9 TIPS FOR SPEAKERS: sell your pen&rdquo;, com fonte, caixa alta e posição preservadas. Se você não soubesse que o original era em português, não dava pra adivinhar que é uma edição automática feita por IA.</p>
<p>Custo do lado Gemini: alguns centavos por imagem, poucos dólares totais pro batch inteiro. Trivial perto dos $1.500+ do dub. Com a thumbnail resolvida, a conversão do canal pra inglês fica fechada — áudio clonado pela ElevenLabs v3 em cima das legendas curadas em <code>.srt</code>, e a imagem editada pelo Nano Banana Pro pra o título bater com o resto.</p>
<h2>A conclusão, que é o título desse post<span class="hx:absolute hx:-mt-20" id="a-conclusão-que-é-o-título-desse-post"></span>
    <a href="#a-conclus%c3%a3o-que-%c3%a9-o-t%c3%adtulo-desse-post" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando o Qwen3 TTS saiu, o hype dizia que era &ldquo;ElevenLabs killer&rdquo;. Passei semanas tentando viver com essa premissa na prática, com conteúdo real no ar toda semana. E o que eu descobri é que open source de TTS ainda tá muito longe da ElevenLabs. A diferença é grande, e ela aparece justamente quando você sai do tweet de demo de 5 segundos e coloca o modelo pra rodar um podcast semanal de 30 minutos de verdade.</p>
<p>Na prática, o Qwen3 nem bate o modelo v2 da ElevenLabs. O v3, que é o atual, fica um degrau acima do v2 ainda. A prosódia sai melhor, as tags de emoção no meio do texto funcionam em português e em inglês sem esforço, e a API fica de pé sem você precisar manter servidor nenhum. O custo por caractere é um pouco mais alto, mas pro meu volume atual ele fica confortavelmente dentro do orçamento e me devolve horas que eu gastava cuidando de GPU.</p>
<p>A lição aqui é a mesma que a galera de LLMs tá aprendendo devagar. Demo de tweet de 30 segundos é uma coisa, produção é outra coisa completamente diferente. Open source tem seu nicho, principalmente quando você tem dado sensível que não pode sair de casa, ou quando você tem muita GPU ociosa e pouco orçamento recorrente. Só que pra uso sério de TTS em produto comercial, aqui em abril de 2026, a ElevenLabs segue imbatível. O Qwen3 não matou ninguém.</p>
<p>E não esquece: assina o <a href="https://open.spotify.com/show/7MzG2UB7IAkC3GAwEXEIVD"target="_blank" rel="noopener">The M.Akita Chronicles no Spotify</a> pra não perder episódio novo desses, feito com a nova pipeline.</p>
]]></content:encoded><category>midia-generativa</category><category>modelos-locais</category><category>reviews</category></item><item><title>Testando LLMs Open Source e Comerciais - Quem Consegue Bater o Claude Opus?</title><link>https://akitaonrails.github.io/2026/04/05/testando-llms-open-source-e-comerciais-quem-consegue-bater-o-claude-opus/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/04/05/testando-llms-open-source-e-comerciais-quem-consegue-bater-o-claude-opus/</guid><pubDate>Sun, 05 Apr 2026 18:00:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Artigo obsoleto (atualização de 2026-04-24).&lt;/strong&gt; As conclusões e rankings desse artigo foram superados depois que eu re-auditei o benchmark contra o código-fonte do gem &lt;code&gt;ruby_llm&lt;/code&gt; e reestruturei o critério de avaliação. Várias &amp;ldquo;hallucinations&amp;rdquo; que eu catalogava eram API válida. Kimi K2.6 e Gemini 3.1 Pro subiram pra Tier A. GLM 5.1 caiu pra Tier C. MiMo V2.5 Pro caiu de &amp;ldquo;primeiro Tier 1 não-Anthropic&amp;rdquo; pra Tier B. &lt;strong&gt;A versão canônica está em &lt;a href="https://akitaonrails.github.io/2026/04/24/llm-benchmarks-parte-3-deepseek-kimi-mimo/"&gt;Benchmark de LLMs pra Coding (Abril 2026)&lt;/a&gt;.&lt;/strong&gt; Esse artigo fica aqui como registro histórico do que eu concluí antes do re-audit.&lt;/p&gt;</description><content:encoded><![CDATA[<blockquote>
  <p>⚠️ <strong>Artigo obsoleto (atualização de 2026-04-24).</strong> As conclusões e rankings desse artigo foram superados depois que eu re-auditei o benchmark contra o código-fonte do gem <code>ruby_llm</code> e reestruturei o critério de avaliação. Várias &ldquo;hallucinations&rdquo; que eu catalogava eram API válida. Kimi K2.6 e Gemini 3.1 Pro subiram pra Tier A. GLM 5.1 caiu pra Tier C. MiMo V2.5 Pro caiu de &ldquo;primeiro Tier 1 não-Anthropic&rdquo; pra Tier B. <strong>A versão canônica está em <a href="/2026/04/24/llm-benchmarks-parte-3-deepseek-kimi-mimo/">Benchmark de LLMs pra Coding (Abril 2026)</a>.</strong> Esse artigo fica aqui como registro histórico do que eu concluí antes do re-audit.</p>

</blockquote>
<hr>
<p><strong>Update 16 de Abril, 2026:</strong> Adicionamos Claude Opus 4.7, Qwen 3.6 e GPT 5.4 via Codex CLI (xHigh reasoning). Opus 4.7 é melhoria incremental sobre 4.6 (28 testes vs 16, mesma API correta) e passa a ser o novo baseline. GPT 5.4, que estava no Tier 1 pelo meu aval pessoal, agora tem dados objetivos e caiu pro Tier 2 — gastou 7.6M tokens (~$16/run, 15x mais caro que Opus) e errou a convenção de chamada do <code>add_message</code> no multi-turn. Qwen 3.6 Plus continua Tier 3 com a mesma alucinação de API do 3.5. A conclusão se mantém: se quer segurança, Opus.</p>
<hr>
<p><strong>TL;DR:</strong> Se você não quer ler a análise inteira: os únicos modelos que geraram código que funciona de verdade no nosso benchmark foram Claude Opus 4.7, Claude Opus 4.6, GLM 5 e GLM 5.1 (da Z.AI, ~89% mais baratos que Opus). O Sonnet também funciona nesse benchmark, mas na prática falha em projetos que exijam raciocínio mais profundo — detalhes na conclusão. O GPT 5.4, que antes estava no Tier 1 pelo meu aval pessoal, agora tem dados objetivos via Codex CLI: gastou 7.6M tokens ($16/run) e errou a convenção de chamada do <code>add_message</code> — funciona na primeira mensagem, quebra no multi-turn. Caiu pro Tier 2. O resto — Kimi, DeepSeek, MiniMax, Qwen, Gemini, Grok 4.20 — inventou APIs que não existem ou ignorou o gem pedido.</p>
<p>Tem uma novidade nesse update: refiz a parte local do benchmark numa RTX 5090 (em vez do AMD Strix Halo) e adicionei um lote de modelos Qwen, incluindo um Qwen 3.5 27B distilado direto do Claude 4.6 Opus. Isso reabriu a conversa sobre rodar modelo open source localmente. A banda de memória da 5090 muda o jogo de &ldquo;inviável&rdquo; pra &ldquo;viável com 1-2 prompts de correção&rdquo;. E o gargalo dos modelos open source virou falta de conhecimento factual sobre bibliotecas, coisa que eu explico em detalhe na nova seção sobre a família Qwen. A aposta da distilação do Claude, aliás, deu um resultado bem frustrante que eu nunca tinha visto documentado nesses termos.</p>
<hr>
<p>Se você acompanhou meus <a href="/tags/vibe-coding/">artigos anteriores sobre vibe coding</a>, sabe que passei os últimos dois meses numa maratona de mais de 500 horas usando Claude Opus como coding agent principal. Os resultados foram bons, como reportei na <a href="https://akitaonrails.com/2026/03/05/37-dias-de-imers%c3%a3o-em-vibe-coding-conclus%c3%a3o-quanto-a-modelos-de-neg%c3%b3cio/"target="_blank" rel="noopener">conclusão sobre modelos de negócio</a>. Mas ficou uma coceira: será que eu estou preso num único modelo? Tem alternativa real ao Claude Opus pra uso diário em projetos de verdade?</p>
<p>Tenho uma RTX 5090 com 32 GB de GDDR7. Sei que posso rodar os modelos open source mais recentes. Comprei um <a href="https://akitaonrails.com/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/"target="_blank" rel="noopener">Minisforum MS-S1</a> com AMD Ryzen AI Max 395 e 128 GB de memória unificada, e montei um <a href="https://akitaonrails.com/2026/03/31/migrando-meu-home-server-com-claude-code/"target="_blank" rel="noopener">home server com Docker</a> pra servir modelos locais. A infraestrutura estava pronta. Faltava testar de verdade.</p>
<p>Construí um benchmark automatizado pra comparar modelos open source e comerciais em condições idênticas. 33 modelos configurados ao todo (25 da rodada original mais 8 adicionados na rerodada na NVIDIA), 27 executados, 16 completados de alguma forma. O código está no <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">GitHub</a>.</p>
<h2>O gargalo que ninguém explica: VRAM e KV Cache<span class="hx:absolute hx:-mt-20" id="o-gargalo-que-ninguém-explica-vram-e-kv-cache"></span>
    <a href="#o-gargalo-que-ningu%c3%a9m-explica-vram-e-kv-cache" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de falar dos resultados, preciso explicar por que rodar modelos grandes localmente é muito mais difícil do que parece.</p>
<p>Pega o Qwen3 32B como exemplo. O modelo em FP16 (precisão total) ocupa ~64 GB. Quantizado em Q4 (4 bits), cai pra ~19 GB. Então cabe nos 32 GB da minha RTX 5090, certo? Errado. Esse é só o peso do modelo. Falta a parte que ninguém conta pra você: o <strong>KV Cache</strong>.</p>
<p>KV Cache é a memória que o modelo usa pra &ldquo;lembrar&rdquo; o que já leu. Cada vez que processa um token (uma palavra ou pedaço de palavra), ele calcula dois vetores — K (key) e V (value) — pra cada camada de atenção. Esses vetores ficam armazenados pra que não precise recalcular tudo quando gera o próximo token. Sem isso, a geração seria quadraticamente lenta.</p>
<p>O KV Cache escala linearmente com o tamanho do contexto. A fórmula:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Memória KV = 2 × Camadas × Cabeças_KV × Dimensão_Cabeça × Bytes_por_Elemento × Tokens_no_Contexto</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra um modelo como o Llama 3.1 70B em BF16, isso dá ~0.31 MB por token. Parece pouco, até você perceber que um contexto de 128K tokens consome <strong>40 GB</strong> só de KV Cache. O modelo em si + KV Cache = muito mais VRAM do que a maioria das GPUs tem.</p>
<p>E pra uso real com coding agents, 128K tokens não é luxo — é necessidade. O agent precisa ler arquivos, manter histórico de conversação, receber output de comandos. Em sessões longas de benchmark, nossos modelos consumiram entre 39K e 156K tokens. Menos de 100K de contexto não é prático pra projetos do dia a dia.</p>
<p>O Google publicou o <a href="https://research.google/blog/turboquant-redefining-ai-efficiency-with-extreme-compression/"target="_blank" rel="noopener">TurboQuant</a> (ICLR 2026), que comprime o KV Cache pra 3 bits sem perda de acurácia — redução de 6x na memória e até 8x de speedup. Usa rotação aleatória de vetores (PolarQuant) seguida de um algoritmo de 1 bit nos resíduos. Funciona online durante inferência, comprimindo na escrita e descomprimindo na leitura. Ainda não está implementado nos runtimes que usamos (llama.cpp, Ollama), mas quando chegar vai mudar bastante a equação.</p>
<p>Pra quem quer se aprofundar nessa matemática de VRAM, recomendo <a href="https://x.com/TheAhmadOsman/status/2040103488714068245"target="_blank" rel="noopener">este link do Ahmad Osman</a> pro artigo &ldquo;GPU Memory Math for LLMs (2026 Edition)&rdquo;.</p>
<h2>O problema do hardware: memória não é toda igual<span class="hx:absolute hx:-mt-20" id="o-problema-do-hardware-memória-não-é-toda-igual"></span>
    <a href="#o-problema-do-hardware-mem%c3%b3ria-n%c3%a3o-%c3%a9-toda-igual" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>&ldquo;Mas eu tenho 128 GB de RAM!&rdquo; Legal, mas não é isso que importa. O que importa é largura de banda de memória, e a diferença entre tipos é absurda:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/memory-bandwidth.png" alt="Largura de banda de memória por tipo"  loading="lazy" /></p>
<p>A RTX 5090 tem 7x mais bandwidth que a memória LPDDR5x do meu Minisforum. Isso significa que mesmo que o modelo caiba na RAM unificada do AMD, a inferência vai ser proporcionalmente mais lenta. No meu Minisforum com LPDDR5x a 256 GB/s, o Qwen3 32B roda a ~7 tok/s. Na RTX 5090 a 1.792 GB/s, seria muito mais rápido — se coubesse inteiro na VRAM junto com o KV Cache.</p>
<p>A maioria das pessoas rodando modelos locais ainda está em DDR4. A 50 GB/s, modelos de 32B ficam praticamente unusáveis. E tem outro fator que pouca gente lembra: storage. Quando a RAM não dá conta e o sistema faz swap, a velocidade do armazenamento vira o gargalo:</p>
<table>
  <thead>
      <tr>
          <th>Storage</th>
          <th style="text-align: right">Velocidade Sequencial</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SATA SSD</td>
          <td style="text-align: right">~550 MB/s</td>
      </tr>
      <tr>
          <td>NVMe Gen3</td>
          <td style="text-align: right">~3.500 MB/s</td>
      </tr>
      <tr>
          <td>NVMe Gen4</td>
          <td style="text-align: right">~7.000 MB/s</td>
      </tr>
      <tr>
          <td>NVMe Gen5</td>
          <td style="text-align: right">~12.000 MB/s</td>
      </tr>
  </tbody>
</table>
<p>De SATA pra NVMe Gen5 são 22x de diferença. Se você está fazendo offloading parcial pro disco (comum quando o modelo não cabe inteiro na GPU), NVMe Gen4 ou Gen5 faz diferença real. SATA é inviável.</p>
<p>Resumindo: rodar modelos locais não é só &ldquo;ter RAM suficiente&rdquo;. Precisa do tipo certo de memória, com a bandwidth certa, e storage rápido como fallback. Pra muita gente, um Mac Studio com memória unificada de alta bandwidth (até 800 GB/s nos M4 Ultra com 512 GB) seria a opção mais prática, mas custa mais de US$ 10.000. O AMD Ryzen AI Max é a alternativa mais acessível com memória unificada, mas o LPDDR5 fica limitado a 256 GB/s.</p>
<h2>Ollama vs llama.cpp: por que o Ollama falha pra benchmarks<span class="hx:absolute hx:-mt-20" id="ollama-vs-llamacpp-por-que-o-ollama-falha-pra-benchmarks"></span>
    <a href="#ollama-vs-llamacpp-por-que-o-ollama-falha-pra-benchmarks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://ollama.com/"target="_blank" rel="noopener">Ollama</a> é a forma mais popular de rodar modelos locais. Instala, puxa o modelo, roda. Pra uso casual funciona. Mas quando tentei usar pra benchmarks automatizados, sessões longas sem intervenção humana, quebrou de 6 formas diferentes em 8 modelos:</p>
<ol>
<li>Descarrega o modelo no meio da sessão. Em runs longos, o Ollama decide que o modelo não está sendo usado e descarrega da GPU. O agent fica esperando resposta de um modelo que não existe mais.</li>
<li>Ignora o contexto solicitado. Você pede <code>num_ctx=131072</code>, o Ollama aceita, e no meio do run reverte pro padrão sem avisar.</li>
<li>Lifecycle instável. Pedir <code>keep_alive: 0</code> pra descarregar nem sempre funciona. O modelo fica residente e bloqueia o próximo.</li>
<li>Formatos incompatíveis. Variantes bf16 nativas do Ollama falhavam, enquanto o mesmo modelo como GGUF Q8 do HuggingFace funcionava sem problemas.</li>
</ol>
<p>A solução: migrar pro <a href="https://github.com/mostlygeek/llama-swap"target="_blank" rel="noopener">llama-swap</a>, um wrapper Go que gerencia processos llama.cpp com hot-swap. Chega um request pra um modelo diferente do que está carregado, ele mata o processo atual e inicia o novo. Sem negociação de contexto, sem lifecycle instável.</p>
<p>O llama-swap resolveu o carregamento de 6 dos 8 modelos que falharam no Ollama:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Ollama</th>
          <th>llama-swap</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Gemma 4 27B</td>
          <td>HTTP 500</td>
          <td>47.6 tok/s</td>
      </tr>
      <tr>
          <td>GLM 4.7 Flash</td>
          <td>Sem output</td>
          <td>47.4 tok/s</td>
      </tr>
      <tr>
          <td>Llama 4 Scout</td>
          <td>Descarregou</td>
          <td>17.5 tok/s</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B</td>
          <td>Output off-spec</td>
          <td>49.7 tok/s</td>
      </tr>
      <tr>
          <td>Qwen 3.5 122B</td>
          <td>Context drift</td>
          <td>23.1 tok/s</td>
      </tr>
      <tr>
          <td>GPT OSS 20B</td>
          <td>Model not found</td>
          <td>78.3 tok/s</td>
      </tr>
  </tbody>
</table>
<p>Mas o llama-swap não é mágico.</p>
<h2>Por que &ldquo;só usar llama.cpp&rdquo; não resolve tudo<span class="hx:absolute hx:-mt-20" id="por-que-só-usar-llamacpp-não-resolve-tudo"></span>
    <a href="#por-que-s%c3%b3-usar-llamacpp-n%c3%a3o-resolve-tudo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O llama.cpp resolve os problemas de lifecycle do Ollama mas traz os próprios:</p>
<p>Cada modelo precisa de flags específicas. GLM e Qwen 3.5 emitem <code>&lt;think&gt;</code> tags que quebram clientes se você não passar <code>--reasoning-format none</code>. Gemma 4 precisa de build b8665+ pro parser de tool calls funcionar.</p>
<p>Nem todo modelo suporta tool calling. O llama.cpp precisa de um parser dedicado pro formato de tool call de cada modelo. O Llama 4 Scout usa um formato &ldquo;pythonic&rdquo; (<code>[func(param=&quot;value&quot;)]</code>) que o llama.cpp simplesmente não parseia, emite como texto puro. O vLLM tem parser pra isso, o llama.cpp não.</p>
<p>E tem os repetition loops. O Gemma 4, mesmo com o parser certo, entra em loop infinito depois de ~11 tool calls em sessões longas. É um <a href="https://github.com/ggml-org/llama.cpp/issues/21375"target="_blank" rel="noopener">bug conhecido</a> que o PR #21418 não resolveu completamente.</p>
<p>Compatibilidade de tool calling por modelo:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tool Calling</th>
          <th>Flags Necessárias</th>
          <th>Resultado no Benchmark</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Gemma 4 27B</td>
          <td>Parcial (b8665+)</td>
          <td><code>--jinja --reasoning-format none</code></td>
          <td>Loop infinito após ~11 steps</td>
      </tr>
      <tr>
          <td>GLM 4.7 Flash</td>
          <td>Sim</td>
          <td><code>--jinja --reasoning-format none</code></td>
          <td>2029 arquivos, terminou mid-tool-call</td>
      </tr>
      <tr>
          <td>Qwen 3.5 (35B, 122B)</td>
          <td>Sim</td>
          <td><code>--jinja --reasoning-format none</code></td>
          <td>Completou com sucesso</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder Next</td>
          <td>Sim</td>
          <td><code>--jinja</code></td>
          <td>Completou (melhor resultado local)</td>
      </tr>
      <tr>
          <td>GPT OSS 20B</td>
          <td>Sim</td>
          <td><code>--jinja</code></td>
          <td>Tool calls ok, mas app no diretório errado</td>
      </tr>
      <tr>
          <td>Llama 4 Scout</td>
          <td>Não</td>
          <td>—</td>
          <td>Sem parser no llama.cpp</td>
      </tr>
  </tbody>
</table>
<p>No fim das contas, llama.cpp é melhor que Ollama pra runs automatizados, mas &ldquo;plug and play&rdquo; não é. Cada modelo exige configuração específica, e alguns simplesmente não funcionam pra agentic coding ainda.</p>
<h2>Reasoning: modelos que pensam vs modelos que chutam<span class="hx:absolute hx:-mt-20" id="reasoning-modelos-que-pensam-vs-modelos-que-chutam"></span>
    <a href="#reasoning-modelos-que-pensam-vs-modelos-que-chutam" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem uma diferença entre modelos que vale explicar: reasoning. A ideia é que o modelo &ldquo;pensa antes de responder&rdquo; em vez de gerar tokens direto da esquerda pra direita. Modelos com reasoning fazem uma etapa interna de cadeia de pensamento (chain-of-thought) onde avaliam o problema, consideram alternativas, planejam, e só depois emitem a resposta.</p>
<p>Na prática, isso aparece como <code>&lt;think&gt;...&lt;/think&gt;</code> tags no output, blocos de texto que o modelo gera pra si mesmo, que não devem ir pro usuário final. Claude Opus 4.6, GPT 5.4, DeepSeek V3.2 e a linha Qwen 3.5 suportam reasoning nativamente. Os menores (Gemma 4, GPT OSS 20B, modelos mais antigos) não têm essa capacidade.</p>
<p>Por que importa pra coding? Quando um coding agent recebe &ldquo;construa um app Rails com 9 componentes&rdquo;, ele precisa decompor a tarefa em passos, decidir a ordem, antecipar dependências, adaptar quando algo falha. Sem reasoning, o modelo gera código sequencialmente sem planejamento. Funciona pra tarefas simples, desmorona em projetos com partes interdependentes.</p>
<p>No benchmark, a diferença ficou clara:</p>
<ul>
<li>GPT OSS 20B (sem reasoning, 20B parâmetros) criou o app no diretório errado. Não conseguiu manter o contexto das instruções de workspace enquanto gerava código.</li>
<li>Qwen 3 32B tem reasoning, mas a 7 tok/s era lento demais. Os tokens de &ldquo;pensamento&rdquo; aumentam o tempo de geração.</li>
<li>Gemma 4 31B, sem reasoning treinado pra uso agentic, entrou em loops repetitivos de tool calling.</li>
<li>GLM 5 (cloud, 745B MoE) com reasoning e 44B parâmetros ativos, completou limpo e usou a API correta.</li>
</ul>
<p>Tem um trade-off: reasoning consome tokens extras (os <code>&lt;think&gt;</code> blocks), que ocupam VRAM no KV Cache e desaceleram a geração. Por isso flags como <code>--reasoning-format none</code> são necessárias no llama.cpp. Alguns clientes não sabem o que fazer com reasoning tokens e quebram. Modelos que emitem reasoning sem que o runtime espere podem gerar lixo no output.</p>
<p>E reasoning não é algo que você &ldquo;liga&rdquo; num modelo qualquer. É uma capacidade treinada com reinforcement learning em cima do modelo base, usando dados de problemas que exigem raciocínio multi-step. Os modelos open source menores (20B-35B) tipicamente não passaram por esse treinamento, ou passaram em escala menor. Eles sabem gerar código, mas não sabem <em>planejar</em> código. Em tarefas que exigem 50+ tool calls coordenadas, essa diferença mata.</p>
<h2>O benchmark: metodologia<span class="hx:absolute hx:-mt-20" id="o-benchmark-metodologia"></span>
    <a href="#o-benchmark-metodologia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra comparar modelos de forma justa, construí um harness automatizado em Python. Cada modelo recebe exatamente o mesmo prompt: construir uma aplicação Ruby on Rails completa, um chat SPA tipo ChatGPT usando o gem RubyLLM, com Hotwire/Stimulus/Turbo Streams, Tailwind CSS, testes Minitest, ferramentas de CI (Brakeman, RuboCop, SimpleCov, bundle-audit), Dockerfile, docker-compose e README.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/crush-screenshot.png" alt="Crush — CLI de coding da Charm"  loading="lazy" /></p>
<p>O runner é o <a href="https://github.com/sst/opencode"target="_blank" rel="noopener">opencode</a>, uma das CLIs de coding open source mais populares, competindo com Claude Code e Codex. Vale um esclarecimento: o autor original saiu do projeto e foi tocar o <a href="https://github.com/charmbracelet/crush"target="_blank" rel="noopener">Crush</a> junto com a equipe da <a href="https://charm.sh/"target="_blank" rel="noopener">Charm</a> (o pessoal por trás do Bubble Tea, Lip Gloss e várias outras ferramentas de terminal em Go), mas o restante do time original continuou evoluindo o opencode normalmente — o projeto não foi descontinuado. Hoje os dois coexistem. Se você leu <a href="https://akitaonrails.com/2026/01/09/omarchy-3-um-dos-melhores-agentes-pra-programacao-crush/"target="_blank" rel="noopener">meu artigo sobre o Crush</a>, já conhece essa vertente. Ambos rodam em tudo: macOS, Linux, Windows, Android, FreeBSD.</p>
<p>Na verdade, tentei usar o Crush primeiro pro benchmark. O problema: ele anunciava uma flag <code>--yolo</code> no help pra auto-aprovar todas as ações (essencial pra runs automatizados sem intervenção humana), mas na hora de rodar, rejeitava a flag. Sem auto-approve, não dá pra fazer benchmark desacompanhado. O opencode, por outro lado, tinha o modo <code>opencode run --agent build --format json</code> que emite eventos JSON com session IDs e contagem de tokens, perfeito pra automação. Então ficamos com o opencode.</p>
<p>Escolhi o opencode (e não Claude Code ou Codex) por dois motivos:</p>
<ol>
<li>Neutralidade. Claude Code é otimizado pra modelos Anthropic. Codex é otimizado pra modelos OpenAI. O opencode é agnóstico, mesma interface pra todos.</li>
<li>Automação. O opencode expõe um formato JSON legível por máquina. Claude Code e Codex não têm interface equivalente pra benchmarking externo.</li>
</ol>
<p>Modelos cloud rodaram em duas fases: fase 1 (construir o app) e fase 2 (validar boot local, docker build, docker compose). Modelos locais rodaram só fase 1.</p>
<p>Detalhe que vale mencionar: o benchmark inteiro custou menos de $10 em tokens no OpenRouter. Tirando o GPT 5.4 Pro que torrou $7.20 pra falhar, os outros 11 modelos cloud somaram uns $2.50 no total. Modelos locais custam só eletricidade. O ponto é: rodar seu próprio benchmark é barato. Se você quer saber se um modelo funciona pro seu caso de uso, gaste os $2 e teste. O código do harness está no GitHub, é só trocar o prompt pelo seu projeto.</p>
<h2>Por que o GPT 5.4 falhou no benchmark (mas não na vida real)<span class="hx:absolute hx:-mt-20" id="por-que-o-gpt-54-falhou-no-benchmark-mas-não-na-vida-real"></span>
    <a href="#por-que-o-gpt-54-falhou-no-benchmark-mas-n%c3%a3o-na-vida-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O GPT 5.4 Pro é o único modelo cloud que falhou consistentemente no nosso benchmark. Duas runs separadas, mesmo resultado: o modelo gerou arquivos mas nunca chegou a <code>finish_reason: stop</code>. Sempre terminava com <code>finish_reason: tool-calls</code> — queria continuar chamando tools mas o loop se quebrava.</p>
<p>Pra quem não sabe: tool calling é quando um LLM precisa executar uma ação (ler um arquivo, rodar um comando, editar código) e emite uma &ldquo;chamada de ferramenta&rdquo; num formato estruturado. O client (opencode, Claude Code, Codex) interpreta, executa, e devolve o resultado pro modelo. Cada provedor tem seu formato: a Anthropic usa <code>tool_use</code> blocks, a OpenAI usa <code>function_calling</code> com JSON schemas, o Google usa <code>FunctionCall</code>.</p>
<p>O GPT 5.4 é fortemente treinado pro formato nativo de function calling da OpenAI — <code>tool_choice</code>, <code>tools</code> com JSON schemas proprietários. Quando o benchmark rota por opencode → OpenRouter → GPT 5.4, os schemas de tools são traduzidos em cada hop. Se o GPT emite tool calls num formato que o OpenRouter ou opencode não parseia corretamente, o loop do agent quebra.</p>
<p>A evidência: todos os outros modelos cloud (Claude Opus, Claude Sonnet, Kimi K2.5, DeepSeek V3.2, MiniMax M2.7, GLM 5, Qwen 3.6 Plus, Step 3.5 Flash) terminaram com <code>finish_reason: stop</code>. Só o GPT termina com <code>finish_reason: tool-calls</code>.</p>
<p>Comparação justa do GPT 5.4 exigiria rodar no ambiente nativo. E agora temos essa comparação: construímos suporte pra automatizar o Codex CLI (<code>codex exec</code> com <code>--dangerously-bypass-approvals-and-sandbox</code> e reasoning effort <code>xhigh</code>) e rodamos o mesmo benchmark. O GPT 5.4 completou em 22 minutos, gerou todos os 9 artefatos, escreveu 22 testes com a arquitetura mais sofisticada do benchmark inteiro: injeção de dependência do client RubyLLM, PORO models pra <code>ChatMessage</code> e <code>PromptSubmission</code>, session-backed <code>ChatSession</code> com TTL e trimming de mensagens, bin/ci script.</p>
<p>Mas o código quebra na segunda mensagem. O GPT 5.4 usa <code>chat.add_message(role:, content:)</code> com keyword arguments em vez de hash posicional <code>chat.add_message({role:, content:})</code> — isso causa <code>ArgumentError: wrong number of arguments (given 0, expected 1)</code> na primeira troca multi-turn. A primeira mensagem funciona (usa <code>chat.ask</code> direto), o multi-turn não.</p>
<p>E o custo: <strong>7.6 milhões de tokens</strong> no reasoning effort xHigh. São 65x mais tokens que o Opus 4.7 (118K) pro mesmo benchmark. Custo estimado de ~$16 por run, contra ~$1.10 do Opus. Gastou 15x mais e ainda errou a convenção de chamada. Nem budget de token gigantesco nem reasoning effort máximo garante acerto factual numa API de gem. Conhecimento de API é memória binária, não é função de quanto o modelo &ldquo;pensa&rdquo;.</p>
<p>Com dados objetivos em mãos, o GPT 5.4 sai do Tier 1 e vai pro Tier 2. A arquitetura que ele gera é melhor que a do Opus em termos de design patterns. Mas o código precisa de correção pra rodar multi-turn, e o custo por token é proibitivo.</p>
<p>O Sonnet e o Opus via opencode/OpenRouter também provavelmente não foram usados ao máximo da capacidade. O Claude Code oferece suporte nativo de tools que o opencode não replica — o que significa que os resultados do benchmark representam um piso, não um teto, pra esses modelos.</p>
<h2>Modelos open source: a realidade vs a narrativa<span class="hx:absolute hx:-mt-20" id="modelos-open-source-a-realidade-vs-a-narrativa"></span>
    <a href="#modelos-open-source-a-realidade-vs-a-narrativa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem muita gente dizendo que modelos open source já alcançaram os comerciais e que dá pra rodar seu próprio &ldquo;Claude&rdquo; em casa. Na prática, não é bem assim.</p>
<p>A escala não é comparável. Modelos frontier como Claude Opus 4.6 e GPT 5.4 são closed-source, mas estimativas indicam que estão na faixa de centenas de bilhões a trilhões de parâmetros, treinados com compute e dados que nenhuma empresa open source replica. Os melhores modelos que cabem num hardware razoável são:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Parâmetros Totais</th>
          <th style="text-align: right">Parâmetros Ativos</th>
          <th>Arquitetura</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen 3.5 35B-A3B</td>
          <td style="text-align: right">35B</td>
          <td style="text-align: right">3B</td>
          <td>MoE (A3B)</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B</td>
          <td style="text-align: right">27B</td>
          <td style="text-align: right">27B</td>
          <td>Dense</td>
      </tr>
      <tr>
          <td>Qwen 3 32B</td>
          <td style="text-align: right">32B</td>
          <td style="text-align: right">32B</td>
          <td>Dense</td>
      </tr>
      <tr>
          <td>Qwen 3.5 122B</td>
          <td style="text-align: right">122B</td>
          <td style="text-align: right">122B</td>
          <td>Dense</td>
      </tr>
      <tr>
          <td>GPT OSS 20B</td>
          <td style="text-align: right">20B</td>
          <td style="text-align: right">20B</td>
          <td>Dense</td>
      </tr>
      <tr>
          <td>Gemma 4 31B</td>
          <td style="text-align: right">31B</td>
          <td style="text-align: right">31B</td>
          <td>Dense</td>
      </tr>
  </tbody>
</table>
<p>Correção pós-publicação: o Qwen 3.5 35B na verdade é o <strong>35B-A3B</strong>, um MoE com só 3B de parâmetros ativos por token (não denso, como eu tinha colocado originalmente). Isso explica por que ele roda relativamente rápido pro tamanho. E pra quem tem 24 GB de VRAM, o modelo recomendado pela própria <a href="https://unsloth.ai/docs/models/qwen3.5#qwen3.5-27b"target="_blank" rel="noopener">Unsloth</a> é o <strong>Qwen 3.5 27B</strong> denso — esse eu não cheguei a testar no benchmark, mas fica como recomendação. Pra quem quer se aprofundar em modelos locais, vale acompanhar o trabalho do <a href="https://x.com/sudoingX"target="_blank" rel="noopener">@sudoingX</a>, que tem feito experimentação séria nessa frente. Valeu <a href="https://x.com/thpmacedo/status/2041105305111502927"target="_blank" rel="noopener">@thpmacedo</a> pelo toque.</p>
<p>Mesmo os maiores modelos open source MoE (Mixture of Experts) que as empresas disponibilizam publicamente ativam poucos parâmetros por token:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Parâmetros Totais</th>
          <th style="text-align: right">Parâmetros Ativos</th>
          <th>Notas</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Kimi K2.5</td>
          <td style="text-align: right">1T</td>
          <td style="text-align: right">32B</td>
          <td>384 experts, top-8 + shared</td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td style="text-align: right">745B</td>
          <td style="text-align: right">44B</td>
          <td>256 experts, 8 ativados</td>
      </tr>
      <tr>
          <td>DeepSeek V3.2</td>
          <td style="text-align: right">671B</td>
          <td style="text-align: right">37B</td>
          <td>Sparse Attention</td>
      </tr>
      <tr>
          <td>Qwen 3.5 397B</td>
          <td style="text-align: right">397B</td>
          <td style="text-align: right">17B</td>
          <td>MoE, cloud-only</td>
      </tr>
  </tbody>
</table>
<p>Esses modelos grandes não são self-hostáveis. O Kimi K2.5 com 1T parâmetros precisa de clusters de GPUs com centenas de GBs de VRAM. O GLM 5 com 745B idem. Mesmo que a Alibaba ou a Z.AI liberem os pesos (e algumas liberam), ninguém tem hardware doméstico pra rodar eles.</p>
<p>O que cabe na sua GPU doméstica são os modelos de 20B-35B — e esses têm limitações reais.</p>
<h3>O que cada modelo local fez no benchmark<span class="hx:absolute hx:-mt-20" id="o-que-cada-modelo-local-fez-no-benchmark"></span>
    <a href="#o-que-cada-modelo-local-fez-no-benchmark" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Resultados da rodada original no AMD Strix Halo:</p>
<p><strong>Qwen 3 Coder Next (30B)</strong> — Completou em 17 minutos no Strix, gerou 1675 arquivos, app Rails com todos os artefatos. Mas só 3 testes. E o mais importante: inventou <code>RubyLLM::Client.new</code>, uma classe que não existe no gem. O app não roda.</p>
<p><strong>Qwen 3.5 35B</strong> — Completou em 28 minutos no Strix, 1478 arquivos, 11 testes. Usou <code>RubyLLM.chat</code> sem parâmetro de modelo — funciona só se o default estiver configurado. Sem mocking de LLM nos testes.</p>
<p><strong>Qwen 3.5 122B</strong> — Completou em 43 minutos no Strix, 1503 arquivos, 16 testes. Mas ignorou o gem RubyLLM completamente e construiu um cliente HTTP customizado pro OpenRouter. O prompt pedia explicitamente pra usar ruby_llm.</p>
<p><strong>GLM 4.7 Flash (local, Strix)</strong> — Produziu 2029 arquivos com todos os artefatos, mas a sessão terminou mid-tool-call. O modelo cloud (GLM 5) funciona perfeitamente.</p>
<p><strong>Gemma 4 31B (Strix)</strong> — Loop infinito de tool calls depois de ~11 steps produtivos. Bug conhecido do llama.cpp.</p>
<p><strong>GPT OSS 20B (Strix)</strong> — Criou o app Rails no diretório errado (<code>project/app/</code> em vez de <code>project/</code>). Um modelo de 20B não segue instruções de workspace de forma confiável.</p>
<p><strong>Qwen 3 32B (Strix)</strong> — Lento demais (7.3 tok/s). Hardware não dá conta.</p>
<p>E os resultados da rerodada na NVIDIA RTX 5090 (todos com Q3_K_M ou Q4_K_M e contexto entre 64k e 128k pra caber nos 32 GB de VRAM):</p>
<p><strong>Qwen 3.5 35B-A3B (5090)</strong> — 5 minutos a 273 tok/s. Projeto Rails reconhecível, entry point <code>RubyLLM.chat(model:)</code> está certo, mas alucina <code>chat.add_message(role:, content:)</code> e <code>chat.complete</code> em vez de <code>.ask</code>. Dá pra arrumar em 1-2 follow-ups. O melhor candidato a &ldquo;OSS local que vale a pena tentar&rdquo;.</p>
<p><strong>Qwen 3.5 27B Claude-distilado (5090)</strong> — 12 minutos a 129 tok/s. Estilo Claude impecável, alucinação total da API (<code>RubyLLM::Chat.new.with_model{}</code>, <code>add_message</code>, <code>response.text</code>). Mais detalhes na seção de distilação abaixo.</p>
<p><strong>Qwen 3 Coder 30B (5090)</strong> — 6 minutos a 145 tok/s. Devolveu uma string mockada hardcoded em vez de chamar a API. Tier 3 inutilizável.</p>
<p><strong>Qwen 2.5 Coder 32B (5090)</strong> — 90 minutos de timeout, zero arquivos. Modelo girou sem nunca chamar tool de write.</p>
<p><strong>Qwen 3 32B (5090)</strong> — 4 minutos a 69 tok/s, scaffold parcial, errors. Versão geral é melhor que a Coder mas ainda quebra.</p>
<p><strong>Gemma 4 31B (5090)</strong> — 8 minutos a 213 tok/s. Mesmo loop de repetição que tinha no Strix. Bug do llama.cpp não é hardware.</p>
<p><strong>Qwen 3.5 27B Sushi Coder RL (5090)</strong> — Falha de infra (<code>ProviderModelNotFoundError</code>), não foi possível avaliar. Refazer numa próxima rodada.</p>
<p><strong>GPT OSS 20B (5090)</strong> — Tirado da rodada por regressão recente do llama.cpp main no parser de tool calls da família harmony. Os logs mostram <code>Failed to parse input at pos 755: &lt;|channel|&gt;...</code> em sessões multi-turn. Funcionava no Strix com o llama.cpp <code>b8643</code>, quebrou no main de hoje. Aguardando upstream consertar.</p>
<h2>Modelos cloud: o que funciona de verdade<span class="hx:absolute hx:-mt-20" id="modelos-cloud-o-que-funciona-de-verdade"></span>
    <a href="#modelos-cloud-o-que-funciona-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Dos 12 modelos que completaram o benchmark, todos geraram um projeto Rails reconhecível com todos os artefatos pedidos (Gemfile, routes, views, JS, testes, README, Dockerfile, docker-compose). 9 de 9 no checklist de completude.</p>
<p>Mas aí vem a pergunta que importa: o código roda?</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/cost-vs-quality.png" alt="Custo vs tempo — e o código funciona?"  loading="lazy" /></p>
<p>A API correta do RubyLLM é simples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">chat</span> <span class="o">=</span> <span class="no">RubyLLM</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="ss">model</span><span class="p">:</span> <span class="s2">&#34;anthropic/claude-sonnet-4&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="o">.</span><span class="n">ask</span><span class="p">(</span><span class="s2">&#34;Hello&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span><span class="o">.</span><span class="n">content</span>  <span class="c1"># =&gt; &#34;Hi there!&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>8 dos 12 modelos inventaram APIs que não existem. O padrão mais comum: alucinação de uma interface que não é a do gem real:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>O que Inventou</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V3.2</td>
          <td><code>RubyLLM::Client.new</code> — classe inexistente</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder Next</td>
          <td><code>RubyLLM::Client.new</code> — mesmo erro</td>
      </tr>
      <tr>
          <td>Qwen 3.5 122B</td>
          <td><code>Openrouter::Client</code> — gem inexistente</td>
      </tr>
      <tr>
          <td>Kimi K2.5</td>
          <td><code>add_message()</code> e <code>complete()</code> — métodos inexistentes</td>
      </tr>
      <tr>
          <td>MiniMax M2.7</td>
          <td><code>RubyLLM.chat(messages: [...])</code> — assinatura inexistente</td>
      </tr>
      <tr>
          <td>Qwen 3.6 Plus</td>
          <td><code>chat.add_message()</code> — método inexistente</td>
      </tr>
      <tr>
          <td>Gemini 3.1 Pro</td>
          <td><code>RubyLLM::Chat.new()</code> e <code>add_message()</code> — API interna, não pública</td>
      </tr>
      <tr>
          <td>Grok 4.20</td>
          <td>Ignora o gem completamente — usa <code>OpenAI::Client</code> (ruby-openai) batendo direto na URL do OpenRouter</td>
      </tr>
  </tbody>
</table>
<p>Os modelos que acertaram — os dois Claudes, o GLM 5 e o GLM 5.1 — usaram o padrão simples de duas etapas (<code>chat = RubyLLM.chat(model:)</code> depois <code>chat.ask(message)</code>). Os que erraram tentaram fazer o RubyLLM parecer com o SDK Python da OpenAI, que é outra coisa. O Grok 4.20 foi o caso mais cínico: nem tentou usar o gem, foi direto pro <code>OpenAI::Client</code> apontando pra URL do OpenRouter, ignorando o pedido explícito do prompt.</p>
<p>E os testes? Só Opus, Sonnet, GLM 5 e GLM 5.1 fizeram mocking correto das chamadas LLM. Todos os outros ou batiam na API real (que falha sem chave) ou mockavam a API inventada (testes passam mas não provam nada). Contagem de testes é uma métrica enganosa: o Kimi K2.5 escreveu 37 testes, mais que qualquer outro, mas nenhum testa a funcionalidade real porque a API que ele usa não existe.</p>
<h3>Tabela de viabilidade real<span class="hx:absolute hx:-mt-20" id="tabela-de-viabilidade-real"></span>
    <a href="#tabela-de-viabilidade-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: center">API Correta?</th>
          <th style="text-align: center">Roda?</th>
          <th style="text-align: center">Mocking nos Testes?</th>
          <th>Problema</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Claude Opus 4.7</strong></td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center"><strong>Sim</strong></td>
          <td style="text-align: center">Sim (FakeChat)</td>
          <td>Implementação limpa, 28 testes</td>
      </tr>
      <tr>
          <td><strong>Claude Sonnet 4.6</strong></td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center"><strong>Sim</strong></td>
          <td style="text-align: center">Sim (mocha)</td>
          <td>Implementação limpa</td>
      </tr>
      <tr>
          <td><strong>Claude Opus 4.6</strong></td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center"><strong>Sim</strong></td>
          <td style="text-align: center">Sim (mocha)</td>
          <td>Implementação limpa</td>
      </tr>
      <tr>
          <td><strong>GLM 5</strong></td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center"><strong>Sim</strong></td>
          <td style="text-align: center">Sim (mocha)</td>
          <td>API correta, funciona</td>
      </tr>
      <tr>
          <td><strong>GLM 5.1</strong></td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center"><strong>Sim</strong></td>
          <td style="text-align: center">Sim</td>
          <td>API correta, funciona</td>
      </tr>
      <tr>
          <td>GPT 5.4 (Codex)</td>
          <td style="text-align: center">Parcial</td>
          <td style="text-align: center">Só 1ª msg</td>
          <td style="text-align: center">Sim (FakeChat)</td>
          <td><code>add_message(role:, content:)</code> com keyword args em vez de hash posicional — quebra no multi-turn</td>
      </tr>
      <tr>
          <td>Step 3.5 Flash</td>
          <td style="text-align: center">N/A</td>
          <td style="text-align: center"><strong>Sim</strong>*</td>
          <td style="text-align: center">Não</td>
          <td>Bypassa RubyLLM, usa HTTP direto</td>
      </tr>
      <tr>
          <td>Grok 4.20</td>
          <td style="text-align: center">N/A</td>
          <td style="text-align: center"><strong>Sim</strong>*</td>
          <td style="text-align: center">Não</td>
          <td>Bypassa RubyLLM, usa <code>OpenAI::Client</code> direto</td>
      </tr>
      <tr>
          <td>Qwen 3.6 Plus</td>
          <td style="text-align: center">Parcial</td>
          <td style="text-align: center">Só 1ª msg</td>
          <td style="text-align: center">Não</td>
          <td><code>add_message()</code> não existe</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B</td>
          <td style="text-align: center">Parcial</td>
          <td style="text-align: center">Talvez</td>
          <td style="text-align: center">Não</td>
          <td>Sem parâmetro de modelo</td>
      </tr>
      <tr>
          <td>Kimi K2.5</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center"><strong>Não</strong></td>
          <td style="text-align: center">Não</td>
          <td><code>add_message()</code>/<code>complete()</code> inventados</td>
      </tr>
      <tr>
          <td>MiniMax M2.7</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center"><strong>Não</strong></td>
          <td style="text-align: center">Não</td>
          <td>Assinatura de <code>RubyLLM.chat</code> errada</td>
      </tr>
      <tr>
          <td>DeepSeek V3.2</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center"><strong>Não</strong></td>
          <td style="text-align: center">Não</td>
          <td><code>RubyLLM::Client</code> inexistente</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder Next</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center"><strong>Não</strong></td>
          <td style="text-align: center">Não</td>
          <td><code>RubyLLM::Client</code> inexistente</td>
      </tr>
      <tr>
          <td>Gemini 3.1 Pro</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center"><strong>Não</strong></td>
          <td style="text-align: center">Mock errado</td>
          <td><code>RubyLLM::Chat.new()</code> e <code>add_message()</code> inexistentes</td>
      </tr>
      <tr>
          <td>Qwen 3.5 122B</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center"><strong>Não</strong></td>
          <td style="text-align: center">Não</td>
          <td><code>Openrouter::Client</code> gem inexistente</td>
      </tr>
  </tbody>
</table>
<p>*Step 3.5 Flash funciona chamando a API REST do OpenRouter direto com <code>Net::HTTP</code>, contornando completamente o gem que o prompt pedia.</p>
<p>Agora, isso não quer dizer que esses modelos são inúteis. Se você pegar o Kimi K2.5 ou o DeepSeek V3.2 e mandar &ldquo;a classe RubyLLM::Client não existe, corrija pra usar a API real do gem&rdquo;, provavelmente vai corrigir. Um ou dois follow-ups e o projeto fica funcional. A maioria dos modelos que falharam aqui conseguiria entregar um projeto rodando com mais algumas rodadas de conversa.</p>
<p>Só que aí está o trade-off. Com Opus ou GPT 5.4, o primeiro output já funciona. Pede, entrega, testa, roda. Com os modelos mais baratos, você vai gastar tempo corrigindo alucinações de API, debugando código que &ldquo;parece certo&rdquo; mas crasha, guiando o modelo na direção certa. Cada rodada dessas são 10-30 minutos. Três rodadas extras e você gastou uma hora do seu tempo pra economizar $0.90 de tokens.</p>
<p>Economiza dólar, gasta tempo. E tempo é dinheiro. Pra quem está aprendendo ou explorando sem pressa, essa troca pode fazer sentido. Pra quem precisa entregar, os modelos frontier se pagam rápido.</p>
<h3>Comparação dos modelos que funcionam<span class="hx:absolute hx:-mt-20" id="comparação-dos-modelos-que-funcionam"></span>
    <a href="#compara%c3%a7%c3%a3o-dos-modelos-que-funcionam" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Provedor</th>
          <th style="text-align: right">Tempo</th>
          <th style="text-align: right">Testes</th>
          <th style="text-align: right">Custo/Run</th>
          <th>vs Opus</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Opus 4.7</td>
          <td>OpenRouter</td>
          <td style="text-align: right">18m</td>
          <td style="text-align: right">28</td>
          <td style="text-align: right">~$1.10</td>
          <td>Novo baseline</td>
      </tr>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td>OpenRouter</td>
          <td style="text-align: right">16m</td>
          <td style="text-align: right">30</td>
          <td style="text-align: right">~$0.63</td>
          <td>40% mais barato, mais testes</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td>OpenRouter</td>
          <td style="text-align: right">16m</td>
          <td style="text-align: right">16</td>
          <td style="text-align: right">~$1.05</td>
          <td>Baseline anterior</td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td>OpenRouter</td>
          <td style="text-align: right">17m</td>
          <td style="text-align: right">7</td>
          <td style="text-align: right">~$0.11</td>
          <td>89% mais barato</td>
      </tr>
      <tr>
          <td>GLM 5.1</td>
          <td>Z.AI direto</td>
          <td style="text-align: right">22m</td>
          <td style="text-align: right">24</td>
          <td style="text-align: right">~$0.13</td>
          <td>~88% mais barato, mais testes que o GLM 5</td>
      </tr>
  </tbody>
</table>
<h3>Ranking completo por tempo e tokens<span class="hx:absolute hx:-mt-20" id="ranking-completo-por-tempo-e-tokens"></span>
    <a href="#ranking-completo-por-tempo-e-tokens" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/time-to-complete.png" alt="Tempo de conclusão por modelo"  loading="lazy" /></p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Provedor</th>
          <th style="text-align: right">Tempo</th>
          <th style="text-align: right">Tokens Totais</th>
          <th style="text-align: right">Tok/s</th>
          <th style="text-align: right">Custo/Run</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Grok 4.20</td>
          <td>OpenRouter</td>
          <td style="text-align: right">8m</td>
          <td style="text-align: right">63.457</td>
          <td style="text-align: right">412.54</td>
          <td style="text-align: right">~$0.04</td>
      </tr>
      <tr>
          <td>Gemini 3.1 Pro</td>
          <td>OpenRouter</td>
          <td style="text-align: right">14m</td>
          <td style="text-align: right">104.034</td>
          <td style="text-align: right">128.28</td>
          <td style="text-align: right">~$0.50</td>
      </tr>
      <tr>
          <td>MiniMax M2.7</td>
          <td>OpenRouter</td>
          <td style="text-align: right">14m</td>
          <td style="text-align: right">79.743</td>
          <td style="text-align: right">574.52</td>
          <td style="text-align: right">~$0.05</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td>OpenRouter</td>
          <td style="text-align: right">16m</td>
          <td style="text-align: right">136.806</td>
          <td style="text-align: right">347.18</td>
          <td style="text-align: right">~$1.05</td>
      </tr>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td>OpenRouter</td>
          <td style="text-align: right">16m</td>
          <td style="text-align: right">127.067</td>
          <td style="text-align: right">532.26</td>
          <td style="text-align: right">~$0.63</td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td>OpenRouter</td>
          <td style="text-align: right">17m</td>
          <td style="text-align: right">59.378</td>
          <td style="text-align: right">400.01</td>
          <td style="text-align: right">~$0.11</td>
      </tr>
      <tr>
          <td>Qwen 3.6 Plus</td>
          <td>OpenRouter</td>
          <td style="text-align: right">17m</td>
          <td style="text-align: right">88.940</td>
          <td style="text-align: right">182.91</td>
          <td style="text-align: right">Grátis</td>
      </tr>
      <tr>
          <td>Claude Opus 4.7</td>
          <td>OpenRouter</td>
          <td style="text-align: right">18m</td>
          <td style="text-align: right">118.216</td>
          <td style="text-align: right">328.24</td>
          <td style="text-align: right">~$1.10</td>
      </tr>
      <tr>
          <td>GLM 5.1</td>
          <td>Z.AI direto</td>
          <td style="text-align: right">22m</td>
          <td style="text-align: right">81.666</td>
          <td style="text-align: right">166.62</td>
          <td style="text-align: right">~$0.13</td>
      </tr>
      <tr>
          <td>GPT 5.4 (Codex)</td>
          <td>Codex CLI</td>
          <td style="text-align: right">22m</td>
          <td style="text-align: right">7.643.800</td>
          <td style="text-align: right">5.824.56</td>
          <td style="text-align: right">~$16.00</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder Next</td>
          <td>Local</td>
          <td style="text-align: right">17m</td>
          <td style="text-align: right">39.054</td>
          <td style="text-align: right">37.49</td>
          <td style="text-align: right">Eletricidade</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B</td>
          <td>Local</td>
          <td style="text-align: right">28m</td>
          <td style="text-align: right">76.919</td>
          <td style="text-align: right">46.03</td>
          <td style="text-align: right">Eletricidade</td>
      </tr>
      <tr>
          <td>Kimi K2.5</td>
          <td>OpenRouter</td>
          <td style="text-align: right">29m</td>
          <td style="text-align: right">63.638</td>
          <td style="text-align: right">160.14</td>
          <td style="text-align: right">~$0.07</td>
      </tr>
      <tr>
          <td>Step 3.5 Flash</td>
          <td>OpenRouter</td>
          <td style="text-align: right">38m</td>
          <td style="text-align: right">156.267</td>
          <td style="text-align: right">242.11</td>
          <td style="text-align: right">~$0.02</td>
      </tr>
      <tr>
          <td>Qwen 3.5 122B</td>
          <td>Local</td>
          <td style="text-align: right">43m</td>
          <td style="text-align: right">57.472</td>
          <td style="text-align: right">22.41</td>
          <td style="text-align: right">Eletricidade</td>
      </tr>
      <tr>
          <td>DeepSeek V3.2</td>
          <td>OpenRouter</td>
          <td style="text-align: right">60m</td>
          <td style="text-align: right">115.278</td>
          <td style="text-align: right">53.37</td>
          <td style="text-align: right">~$0.04</td>
      </tr>
  </tbody>
</table>
<p>O DeepSeek V3.2 é o mais lento apesar de ser cloud — não tem prompt caching, então reenvia o contexto completo a cada turno.</p>
<h3>Eficiência de tokens e cache<span class="hx:absolute hx:-mt-20" id="eficiência-de-tokens-e-cache"></span>
    <a href="#efici%c3%aancia-de-tokens-e-cache" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Modelos com prompt caching pagam muito menos tokens efetivos:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/token-efficiency.png" alt="Eficiência de tokens: cache vs novos"  loading="lazy" /></p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Tokens Totais</th>
          <th style="text-align: right">Cache Lido</th>
          <th style="text-align: right">Tokens Novos Efetivos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td style="text-align: right">127.067</td>
          <td style="text-align: right">126.429</td>
          <td style="text-align: right">638</td>
      </tr>
      <tr>
          <td>Claude Opus 4.7</td>
          <td style="text-align: right">118.216</td>
          <td style="text-align: right">116.824</td>
          <td style="text-align: right">1.392</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td style="text-align: right">136.806</td>
          <td style="text-align: right">135.976</td>
          <td style="text-align: right">830</td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td style="text-align: right">59.378</td>
          <td style="text-align: right">58.240</td>
          <td style="text-align: right">1.138</td>
      </tr>
      <tr>
          <td>GLM 5.1</td>
          <td style="text-align: right">81.666</td>
          <td style="text-align: right">81.216</td>
          <td style="text-align: right">450</td>
      </tr>
      <tr>
          <td>Grok 4.20</td>
          <td style="text-align: right">63.457</td>
          <td style="text-align: right">62.400</td>
          <td style="text-align: right">1.057</td>
      </tr>
      <tr>
          <td>Gemini 3.1 Pro</td>
          <td style="text-align: right">104.034</td>
          <td style="text-align: right">98.129</td>
          <td style="text-align: right">5.905</td>
      </tr>
      <tr>
          <td>GPT 5.4 (Codex)</td>
          <td style="text-align: right">7.643.800</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">7.643.800</td>
      </tr>
      <tr>
          <td>DeepSeek V3.2</td>
          <td style="text-align: right">115.278</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">115.278</td>
      </tr>
      <tr>
          <td>Kimi K2.5</td>
          <td style="text-align: right">63.638</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">63.638</td>
      </tr>
  </tbody>
</table>
<h2>Velocidade: o abismo entre cloud e local<span class="hx:absolute hx:-mt-20" id="velocidade-o-abismo-entre-cloud-e-local"></span>
    <a href="#velocidade-o-abismo-entre-cloud-e-local" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem um aspecto que as tabelas de custo escondem: velocidade de inferência. E a diferença é brutal.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/speed-comparison.png" alt="Velocidade de inferência por modelo"  loading="lazy" /></p>
<p>O Claude Sonnet gera 532 tok/s. O Qwen 3.5 122B rodando local no meu Minisforum (AMD Strix Halo) gera 22 tok/s. São 24x de diferença. Na prática, o que o Sonnet faz em 16 minutos, o Qwen 3.5 122B leva 43 minutos. O Qwen 3 Coder Next a 37 tok/s é o mais rápido dos locais no Strix e mesmo assim é 14x mais lento que o Sonnet.</p>
<p>E não é só tempo de relógio. Quando você está num loop de coding interativo — pede uma mudança, espera o output, testa, pede outra — a velocidade do modelo define seu ritmo. A 37 tok/s, cada resposta longa te faz esperar 30-60 segundos. A 530 tok/s, aparece quase instantaneamente. Ao longo de um dia, você sente.</p>
<p>O DeepSeek V3.2 é um caso curioso: é cloud mas roda a 53 tok/s, mais lento que o Qwen 3.5 35B local no Strix (46 tok/s). O motivo é que o DeepSeek não tem prompt caching — reenvia o contexto completo a cada turno, estrangulando o throughput. Pagar por um modelo cloud mais lento que rodar local não faz sentido nenhum.</p>
<p>Modelos locais são grátis em tokens, mas pagam em tempo. No AMD Strix, essa conta era inviável pra todos os Qwen que testei: dois minutos esperando uma resposta longa, multiplicado por 50 turnos, é tarde inteira. Mas isso muda quando o hardware muda, e foi por isso que rerodei a parte local do benchmark numa máquina diferente.</p>
<h2>AMD Strix Halo vs NVIDIA RTX 5090: o que muda quando a banda de memória dobra<span class="hx:absolute hx:-mt-20" id="amd-strix-halo-vs-nvidia-rtx-5090-o-que-muda-quando-a-banda-de-memória-dobra"></span>
    <a href="#amd-strix-halo-vs-nvidia-rtx-5090-o-que-muda-quando-a-banda-de-mem%c3%b3ria-dobra" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra checar se o gargalo era hardware ou modelo, peguei os mesmos modelos Qwen e refiz o benchmark numa workstation com NVIDIA RTX 5090 (Blackwell, 32 GB GDDR7, 1.792 GB/s de banda). Os números mudam de uma forma que vale ver com calma.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">AMD Strix (LPDDR5x)</th>
          <th style="text-align: right">NVIDIA 5090 (GDDR7)</th>
          <th style="text-align: right">Speedup</th>
          <th style="text-align: right">Tempo total no 5090</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen 3 32B (denso)</td>
          <td style="text-align: right">7 tok/s</td>
          <td style="text-align: right">69 tok/s</td>
          <td style="text-align: right">~10x</td>
          <td style="text-align: right">4 min</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder 30B (Coder)</td>
          <td style="text-align: right">37 tok/s</td>
          <td style="text-align: right">145 tok/s</td>
          <td style="text-align: right">~4x</td>
          <td style="text-align: right">6 min</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B-A3B (MoE)</td>
          <td style="text-align: right">46 tok/s</td>
          <td style="text-align: right"><strong>273 tok/s</strong></td>
          <td style="text-align: right">~6x</td>
          <td style="text-align: right">5 min</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B Claude (distilado)</td>
          <td style="text-align: right">timeout 90m</td>
          <td style="text-align: right">129 tok/s</td>
          <td style="text-align: right">n/a</td>
          <td style="text-align: right">12 min</td>
      </tr>
      <tr>
          <td>Gemma 4 31B</td>
          <td style="text-align: right">(não testei no Strix)</td>
          <td style="text-align: right">213 tok/s</td>
          <td style="text-align: right">n/a</td>
          <td style="text-align: right">8 min</td>
      </tr>
      <tr>
          <td>Qwen 2.5 Coder 32B</td>
          <td style="text-align: right">(não testei no Strix)</td>
          <td style="text-align: right">2.86 tok/s</td>
          <td style="text-align: right">n/a</td>
          <td style="text-align: right">timeout 90m</td>
      </tr>
  </tbody>
</table>
<p>Pra contextualizar essas velocidades, lembra que no cloud o Sonnet roda a 532 tok/s, o Opus a 347 tok/s, o Step 3.5 Flash a 242 tok/s, o Gemini 3.1 Pro a 128 tok/s e o Kimi K2.5 a 160 tok/s. O Qwen 3.5 35B-A3B na 5090, a 273 tok/s, está na faixa do Step 3.5 Flash, mais rápido que Gemini, Kimi e GLM 5.1. O Qwen 3 Coder 30B a 145 tok/s está na faixa do Gemini. A frase clássica &ldquo;modelo local é dez vezes mais lento que cloud&rdquo; parou de valer no momento que a 5090 entrou na conta.</p>
<p>A consequência prática é que o argumento &ldquo;tempo é dinheiro&rdquo; muda de figura. No Strix, &ldquo;esperar 1 hora pra um Qwen 3.5 122B fazer o que o Sonnet faz em 16 minutos&rdquo; é pura perda. Na 5090, esperar 5 minutos pra o Qwen 3.5 35B-A3B fazer o trabalho, mais 10-15 minutos pra você fazer 1-2 prompts de correção, dá um total na faixa de 20-25 minutos. O Sonnet faz em 16 minutos com zero correção. A diferença ficou pequena o suficiente pra que, se custo importa muito, valha a pena.</p>
<p>A pegadinha: pra que isso valha a pena, o modelo precisa estar perto o suficiente da resposta certa pra que 1-2 prompts de correção resolvam. Quando o erro é do tipo &ldquo;o modelo decidiu não usar o gem que pedi e devolveu uma string mockada&rdquo;, como fez o Qwen 3 Coder 30B, nenhum prompt de correção fácil arruma. Isso é refazer.</p>
<h3>Antes que você gaste em hardware achando que é a saída<span class="hx:absolute hx:-mt-20" id="antes-que-você-gaste-em-hardware-achando-que-é-a-saída"></span>
    <a href="#antes-que-voc%c3%aa-gaste-em-hardware-achando-que-%c3%a9-a-sa%c3%adda" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Tenho que dar um aviso, porque é o erro de compra mais comum que vejo agora. Toda hora aparece alguém dizendo que vai pegar um Ryzen AI Max porque tem 128 GB de memória unificada e isso &ldquo;permite rodar modelos enormes&rdquo;. Tecnicamente, sim — o modelo cabe. Na prática, é quase inutilizável. A memória é LPDDR5x a 256 GB/s, sete vezes mais lenta que a GDDR7 da 5090. O que cabe não roda em velocidade humana. Meu próprio Strix com Qwen 3.5 122B chegou a 22 tok/s e o run levou 43 minutos. Pra fazer qualquer coisa de verdade no dia a dia, isso é impraticável.</p>
<p>A 5090 é claramente superior, e começa a fazer sentido até pra modelos menores justamente por causa da banda de memória. Mac Studio com memória unificada de alta velocidade (até 800 GB/s nos M4 Ultra) é a outra opção viável, e custa proporcionalmente o mesmo. Mas nenhuma das duas chega perto de bater os modelos comerciais em qualidade — e o preço de Claude, GPT ou GLM por token, somado à velocidade brutal de inferência deles, faz a conta ser difícil de justificar pra quem não é entusiasta ou pesquisador. Hardware caro de IA local é hobby de fim de semana, ferramenta pra quem precisa rodar offline por compliance, ou playground de pesquisa. Pra trabalho de produção dia após dia, no momento atual, cloud ainda é a escolha racional. Ryzen AI Max com 128 GB pode parecer tentador na planilha, mas se a ideia é coding agent sério, é dinheiro mal gasto.</p>
<h2>A família Qwen: Coder vs Geral, distilação, e por que nada é bala de prata<span class="hx:absolute hx:-mt-20" id="a-família-qwen-coder-vs-geral-distilação-e-por-que-nada-é-bala-de-prata"></span>
    <a href="#a-fam%c3%adlia-qwen-coder-vs-geral-distila%c3%a7%c3%a3o-e-por-que-nada-%c3%a9-bala-de-prata" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com tantos Qwens diferentes rodando nessa rerodada, dá pra fazer uma análise mais focada. O que aprendi é capaz de surpreender quem segue benchmark de modelo no Twitter.</p>
<h3>Antes de ir aos resultados: o que é quantização e o que é distilação<span class="hx:absolute hx:-mt-20" id="antes-de-ir-aos-resultados-o-que-é-quantização-e-o-que-é-distilação"></span>
    <a href="#antes-de-ir-aos-resultados-o-que-%c3%a9-quantiza%c3%a7%c3%a3o-e-o-que-%c3%a9-distila%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Os dois conceitos aparecem o tempo todo nessa discussão e merecem uma explicação curta.</p>
<p><strong>Quantização</strong> é a técnica de comprimir os pesos do modelo pra ocupar menos memória. Um modelo treinado em FP16 (16 bits por peso) pode ser quantizado pra Q8 (8 bits), Q4 (4 bits), Q3_K_M (3 bits, mas com agrupamentos médios), e por aí vai. Cada passo divide o tamanho do modelo em disco e na VRAM, ao custo de uma certa perda de precisão. Q8 é praticamente lossless. Q4 já perde alguma coisa mensurável. Q3 perde mais. Q2 é o limite onde o modelo começa a falar besteira de verdade. A regra de ouro é que pra coding e raciocínio multi-step, vale ficar em Q4 ou acima. Q3_K_M é o mínimo que ainda funciona pra muitos modelos, e é o que cabe num 27B na 5090 com 128k de contexto.</p>
<p>A surpresa do meu teste, e olha que isso vai contra o consenso, é que a quantização não foi o gargalo aqui. Rodei o Qwen 3.5 27B Claude-distilado em duas versões: Q8 no AMD Strix (~27 GB de pesos) e Q3_K_M na 5090 (~12 GB de pesos). Ambos alucinaram exatamente as mesmas APIs falsas do RubyLLM. O Q3_K_M até gerou um Gemfile mais limpo. A limitação do modelo estava no que esses pesos sabem, não na precisão com que eles foram comprimidos.</p>
<p><strong>Distilação</strong> é a técnica de treinar um modelo menor (o &ldquo;aluno&rdquo;) pra imitar a saída ou o comportamento de um modelo maior (o &ldquo;professor&rdquo;). A versão clássica é distilação de logits — o aluno aprende a aproximar as distribuições de probabilidade do professor. A versão moderna, mais popular pra coding agents, é distilação de <strong>traços de raciocínio</strong> (reasoning traces): você pega cadeias de pensamento do modelo grande pra problemas reais e treina o menor a reproduzir o mesmo estilo de raciocínio.</p>
<p>O hype do momento é distilar Claude e GPT em modelos open source. A promessa é que dá pra ter &ldquo;Claude-em-casa&rdquo; rodando local. Eu queria testar isso, e foi por isso que coloquei no benchmark o <strong><a href="https://huggingface.co/Jackrong/Qwen3.5-27B-Claude-4.6-Opus-Reasoning-Distilled"target="_blank" rel="noopener">Jackrong&rsquo;s Qwen 3.5 27B distilado do Claude 4.6 Opus</a></strong>. Se algum modelo open source ia conseguir usar o RubyLLM corretamente, era essa aposta — afinal, no benchmark inteiro, Claude e GLM 5 são os únicos que acertam a API.</p>
<h3>O que o Claude-distilled aprendeu (e o que não aprendeu)<span class="hx:absolute hx:-mt-20" id="o-que-o-claude-distilled-aprendeu-e-o-que-não-aprendeu"></span>
    <a href="#o-que-o-claude-distilled-aprendeu-e-o-que-n%c3%a3o-aprendeu" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Rodei a mesma distilação duas vezes: uma em Q8 no AMD Strix (que estourou no timeout de 90 minutos), outra em Q3_K_M na 5090 (completou em 12 minutos). Em ambas, o resultado é a mesma frustração elegante.</p>
<p>O código que sai parece Claude. Tem <code># frozen_string_literal: true</code> no topo de todo arquivo. Tem uma classe <code>Response</code> separada como value object com leitores de atributo explícitos. Tem separação clara entre service, controller e model. Tem comentário de doc no topo de cada arquivo. Comenta corretamente coisas como <code>active_record</code>, <code>active_job</code> e <code>action_mailer</code> no <code>application.rb</code>. Tem <code>case</code> defensivo tentando múltiplos formatos de retorno. Estilisticamente, é Claude.</p>
<p>Funcionalmente, é alucinação completa do RubyLLM. Olha o serviço gerado pelo run da 5090:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">RubyLLM</span><span class="o">::</span><span class="no">Chat</span><span class="o">.</span><span class="n">new</span><span class="o">.</span><span class="n">with_model</span><span class="p">(</span><span class="vi">@model</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">chat</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">conversation_history</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">msg</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">chat</span><span class="o">.</span><span class="n">add_message</span><span class="p">(</span><span class="ss">role</span><span class="p">:</span> <span class="ss">:user</span><span class="p">,</span> <span class="ss">content</span><span class="p">:</span> <span class="n">msg</span><span class="o">[</span><span class="ss">:content</span><span class="o">]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="n">response</span> <span class="o">=</span> <span class="n">chat</span><span class="o">.</span><span class="n">ask</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">Response</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">content</span><span class="p">:</span> <span class="n">response</span><span class="o">.</span><span class="n">text</span><span class="p">,</span> <span class="ss">usage</span><span class="p">:</span> <span class="n">build_usage</span><span class="p">(</span><span class="n">response</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Cada primitiva nesse código é inventada:</p>
<ul>
<li><code>RubyLLM::Chat.new</code> — o construtor não é público, o entry correto é <code>RubyLLM.chat(model:)</code></li>
<li><code>.with_model(@model) do |chat| ... end</code> — não existe API de bloco assim</li>
<li><code>chat.add_message(role:, content:)</code> — não existe</li>
<li><code>response.text</code> — a API real expõe <code>response.content</code></li>
<li><code>response.usage.prompt_tokens</code> — o objeto não tem essa forma</li>
</ul>
<p>Isso vai estourar <code>NoMethodError</code> na primeira request. O initializer também tenta <code>config.openrouter_api_base=</code> que não existe no <code>RubyLLM.configure</code>, então o app provavelmente nem boota.</p>
<p>A versão Q8 no AMD Strix faz exatamente o mesmo, com uma diferença: a chamada de entrada é <code>RubyLLM.chat(model:, provider: :openrouter)</code> — o entry point está certo, mas o <code>provider:</code> é inventado e logo em seguida vem o mesmo <code>chat.add_message(role:, content:)</code> falso. Pior, o Gemfile do run de 90 minutos lista <code>gem &quot;ruby-openai&quot;</code> (gem errada!), <code>gem &quot;minitest&quot;, &quot;~&gt; 6.0&quot;</code> (não existe minitest 6.0) e <code>gem &quot;tailwindcss&quot;</code> (nome errado, é <code>tailwindcss-rails</code>). O Gemfile não inclui o gem que o próprio service tenta usar.</p>
<p>Pra comparar, olha o Claude Opus 4.6 baseline real, no mesmo benchmark, acertando tudo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="vi">@chat</span> <span class="o">=</span> <span class="no">RubyLLM</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span><span class="ss">model</span><span class="p">:</span> <span class="n">model_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span> <span class="o">=</span> <span class="vi">@chat</span><span class="o">.</span><span class="n">ask</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">response</span><span class="o">.</span><span class="n">content</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Doze linhas no service inteiro. Sem alucinação. Inclui streaming via bloco. O modelo distilado produziu o triplo do volume de código e errou a API.</p>
<p>A leitura honesta é que distilação transferiu uma camada e parou. A camada que veio junto foi a do estilo: organização do código, comentários, estrutura de classes, ordem das coisas. A camada que ficou pra trás foi a da memória factual sobre bibliotecas específicas. Isso faz sentido quando você pensa: traços de raciocínio do Claude, mesmo escritos com calma, raramente contêm referências repetidas a <code>chat.ask(msg).content</code> num gem Ruby obscuro. O aluno só aprende o que o professor repete, e Claude nunca teve motivo pra ficar sussurrando &ldquo;use ask, não use complete&rdquo; ao longo das suas cadeias de pensamento. Conhecimento de API de biblioteca é memória binária de recall, do tipo que ou está nos pesos ou não está. Decompor isso em passos de raciocínio é impossível porque não é raciocínio, é só lembrança crua.</p>
<p>Pra fechar com a recomendação prática: se você precisa que o modelo realmente use o RubyLLM, ou qualquer biblioteca menos popular que seja, distilação de Claude não te salva. Use Claude de verdade ou GLM 5. Os &ldquo;Claude-stand-ins&rdquo; open source vão falhar do mesmo jeito que o Qwen base falharia, só que com letrinha mais bonita.</p>
<h3>Coder vs Geral: a surpresa dos modelos &ldquo;pra coding&rdquo;<span class="hx:absolute hx:-mt-20" id="coder-vs-geral-a-surpresa-dos-modelos-pra-coding"></span>
    <a href="#coder-vs-geral-a-surpresa-dos-modelos-pra-coding" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A intuição de quase todo mundo é que modelos com &ldquo;Coder&rdquo; no nome são os melhores pra programação. Faz sentido, foram fine-tunados especificamente em código. Mas no benchmark, foi exatamente o oposto.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tipo</th>
          <th>Hardware</th>
          <th style="text-align: right">Tempo</th>
          <th>Resultado</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen 3.5 35B-A3B</td>
          <td>Geral (MoE)</td>
          <td>5090</td>
          <td style="text-align: right">5 min</td>
          <td>Roda Rails, alucina <code>add_message</code>/<code>complete</code> (1-2 follow-ups arrumam)</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder 30B</td>
          <td>Coder</td>
          <td>5090</td>
          <td style="text-align: right">6 min</td>
          <td>Devolveu uma string mockada hardcoded em vez de chamar o RubyLLM</td>
      </tr>
      <tr>
          <td>Qwen 2.5 Coder 32B</td>
          <td>Coder</td>
          <td>5090</td>
          <td style="text-align: right">timeout 90m</td>
          <td>Zero arquivos, modelo travou</td>
      </tr>
      <tr>
          <td>Qwen 3 32B</td>
          <td>Geral</td>
          <td>5090</td>
          <td style="text-align: right">4 min</td>
          <td>Scaffold parcial, errors</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B Claude-distilado</td>
          <td>Geral + distilado</td>
          <td>5090</td>
          <td style="text-align: right">12 min</td>
          <td>Roda Rails, alucina API toda</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B Sushi Coder RL</td>
          <td>Coder (RL)</td>
          <td>5090</td>
          <td style="text-align: right">6 min</td>
          <td>Falha de infra, não pôde testar</td>
      </tr>
  </tbody>
</table>
<p>Dos três Coders dedicados, dois falharam catastroficamente (timeout total e mock string hardcoded) e um nem rodou direito por bug de infra. Já o Qwen 3.5 35B-A3B, que é o modelo geral da linha (não o Coder), foi o que mais perto chegou de algo aproveitável: 5 minutos de execução, projeto Rails reconhecível, e o problema dele se conserta em 1-2 prompts.</p>
<p>O Qwen 3 Coder 30B é particularmente decepcionante. Ele passou longe de qualquer tentativa séria de usar a API: o controller que ele gerou tem isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Api</span><span class="o">::</span><span class="no">V1</span><span class="o">::</span><span class="no">MessagesController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create</span>
</span></span><span class="line"><span class="cl">    <span class="n">render</span> <span class="ss">json</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="ss">response</span><span class="p">:</span> <span class="s2">&#34;This is a mock response. In a real implementation, this would connect to RubyLLM with Claude Sonnet via OpenRouter.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Gemfile lista <code>gem &quot;ruby_llm&quot;</code> mas nada importa. O service layer é inexistente. O modelo decidiu que era mais fácil devolver uma string fake e seguir a vida. Isso é Tier 3 garbage de uma forma que nem prompt de correção arruma — tem que mandar reescrever do zero.</p>
<p>O Qwen 2.5 Coder 32B é ainda pior: 90 minutos rodando, zero arquivos. O <code>opencode-output.ndjson</code> de 1.8 MB mostra o modelo girando sem conseguir escrever nada. Provavelmente entrou em loop de planejamento sem nunca chamar as ferramentas de write. Total perda de slot.</p>
<p>Por que os &ldquo;Coder&rdquo; Qwens foram tão mal? Minha leitura é que o fine-tuning específico pra coding deles foi treinado em problemas mais isolados (Codeforces, Leetcode, snippets curtos), longe dos fluxos agentic com tool calling de longa duração. O modelo geral Qwen 3.5 35B-A3B tem treinamento mais amplo e se vira melhor com a parte de orquestração. A intuição popular &ldquo;Coder = melhor pra coding agent&rdquo; tá errada pra esse tipo de tarefa. O caso de uso onde os Coders se saem bem é &ldquo;completar uma função isolada&rdquo;, que é exatamente pro que eles foram treinados, e isso é uma fração pequena do que um coding agent faz no dia a dia.</p>
<h3>A pergunta que eu queria responder<span class="hx:absolute hx:-mt-20" id="a-pergunta-que-eu-queria-responder"></span>
    <a href="#a-pergunta-que-eu-queria-responder" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Era essa: rodando local na 5090, qual modelo Qwen vale o 1-2 prompts de correção pra entregar código que funcione?</p>
<p>A resposta honesta é: só o Qwen 3.5 35B-A3B, e talvez o Claude-distilado se você não se importa de gastar 12 minutos a mais.</p>
<ul>
<li>Qwen 3.5 35B-A3B na 5090: 5 minutos, entry point certo (<code>RubyLLM.chat(model:)</code>), erros nas chamadas seguintes. Total realista até funcionar: na faixa de 15-20 minutos com 1-2 follow-ups. Bate cloud OSS na conta de custo.</li>
<li>Qwen 3.5 27B Claude-distilado na 5090: 12 minutos, alucinação mais profunda (entry point inventado também). Total realista: 25-30 minutos com 2-3 follow-ups. Ainda compete em custo, e perde em tempo absoluto pro Claude real.</li>
<li>Os outros (Coder 30B, Coder 2.5 32B, 3 32B): não recompensam o tempo de correção. Cada um tem um problema estrutural que pede reescrita inteira do zero.</li>
</ul>
<p>Pra quem tem hardware nessa categoria e quer fugir do vendor lock-in da Anthropic, agora dá. Não dava na 5090 do ano passado, no Strix Halo então, esquece. Em 2026, na NVIDIA Blackwell, com o modelo certo, dá. Pra quem tem hardware de banda baixa (LPDDR5x, DDR4, DDR5), continua sendo perda de tempo: o relógio sozinho derruba qualquer plano de tornar isso prático.</p>
<h3>Qwen 3.6: o que mudou em relação ao 3.5<span class="hx:absolute hx:-mt-20" id="qwen-36-o-que-mudou-em-relação-ao-35"></span>
    <a href="#qwen-36-o-que-mudou-em-rela%c3%a7%c3%a3o-ao-35" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Testamos dois sabores do Qwen 3.6: o <strong>3.6 Plus</strong> (cloud, OpenRouter, grátis) e o <strong>3.6 35B</strong> (local, NVIDIA 5090, Q3_K_M).</p>
<p>O Qwen 3.6 Plus (cloud) completou em 17 minutos com 88.940 tokens e 9/9 no checklist de artefatos. Completou rápido e de graça. Mas o serviço gerado usa <code>chat.add_message()</code>, método que não existe no RubyLLM. A primeira mensagem funciona, a segunda quebra. O mesmo problema do 3.5.</p>
<p>O Qwen 3.6 35B (local, 5090) é mais interessante. Completou em 4.7 minutos a 240 tok/s, 169 arquivos, entry point <code>RubyLLM.chat(model:, provider:)</code> correto e <code>chat.ask(message)</code> correto. O bug é mais sutil: retorna <code>response</code> em vez de <code>response.content</code> e não faz replay de histórico. Correção de 1 linha. Isso é uma melhoria real sobre o Qwen 3.5 35B-A3B (que alucinava <code>add_message</code> e <code>complete</code>). É o resultado Qwen mais limpo que vimos até agora.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Versão</th>
          <th>Hardware</th>
          <th style="text-align: center">API correta?</th>
          <th style="text-align: center">Multi-turn funciona?</th>
          <th style="text-align: right">Tempo</th>
          <th style="text-align: right">Tok/s</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen 3.5 35B-A3B</td>
          <td>3.5</td>
          <td>NVIDIA 5090</td>
          <td style="text-align: center">Entry point sim, <code>add_message</code>/<code>complete</code> não</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: right">5m</td>
          <td style="text-align: right">273</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B Claude-distilado</td>
          <td>3.5</td>
          <td>NVIDIA 5090</td>
          <td style="text-align: center">Não (entry point inventado)</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: right">12m</td>
          <td style="text-align: right">129</td>
      </tr>
      <tr>
          <td>Qwen 3.6 35B</td>
          <td>3.6</td>
          <td>NVIDIA 5090</td>
          <td style="text-align: center">Entry point sim, <code>chat.ask</code> sim, falta <code>.content</code></td>
          <td style="text-align: center">Não (sem replay)</td>
          <td style="text-align: right">5m</td>
          <td style="text-align: right">240</td>
      </tr>
      <tr>
          <td>Qwen 3.6 Plus</td>
          <td>3.6</td>
          <td>Cloud</td>
          <td style="text-align: center">Entry point sim, <code>add_message</code> não</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: right">17m</td>
          <td style="text-align: right">183</td>
      </tr>
  </tbody>
</table>
<p>O gap diminuiu mas não fechou. O 3.6 35B local está mais perto de funcionar que qualquer Qwen anterior — o bug é um <code>.content</code> esquecido, não uma API inteira inventada. Mas continua sem multi-turn funcional de primeira. Na prática, o Qwen 3.6 35B local sobe do Tier 3 pro Tier 2: é o modelo open source local que mais perto chega de entregar código correto no primeiro try, a uma correção de distância.</p>
<h2>O Deep Code Review: Sonnet vs GLM 5 vs Gemini vs Kimi vs MiniMax<span class="hx:absolute hx:-mt-20" id="o-deep-code-review-sonnet-vs-glm-5-vs-gemini-vs-kimi-vs-minimax"></span>
    <a href="#o-deep-code-review-sonnet-vs-glm-5-vs-gemini-vs-kimi-vs-minimax" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>As tabelas acima medem completude estrutural. Mas o projeto funciona? Fiz code review detalhado dos modelos que completaram o benchmark.</p>
<p><strong>Claude Sonnet 4.6 — funciona e é o mais completo.</strong> Respostas síncronas via Turbo Stream. Histórico de chat persistido em session cookie com replay completo das mensagens anteriores a cada request. Mocking correto do LLM nos testes com mocha (30 testes em 328 linhas). Lógica de LLM extraída pra um <code>LlmChatService</code> separado. Views decompostas em 9 partials. Problemas menores: constante de modelo duplicada, leak no event listener do auto-resize. Nenhum é blocker. Dos projetos gerados, é o que mais se aproxima de algo que você colocaria em produção.</p>
<p><strong>GLM 5 — funciona, mas é o mínimo viável.</strong> Usa a API correta (<code>RubyLLM.chat(model:)</code> depois <code>.ask()</code>), faz mocking com mocha nos testes. Só que o projeto é bem mais enxuto que o do Sonnet: controller de 21 linhas (vs 52 do Sonnet), sem service layer (lógica LLM inline no controller), sem persistência de histórico de chat, cada mensagem tratada isoladamente. A primeira mensagem funciona, mas o app não mantém contexto de conversação, então não dá pra ter um diálogo multi-turn. Os testes existem (7 métodos) mas são esqueléticos: o <code>ruby_llm_test.rb</code> só checa se o módulo está carregado, o <code>chat_flow_test.rb</code> é cópia do controller test. O Dockerfile, por outro lado, é o melhor dos quatro: multi-stage, non-root, jemalloc. Mas como app de chat? É mais uma prova de conceito do que algo funcional. Detalhe engraçado: o README diz &ldquo;Powered by Claude Sonnet 4&rdquo; em vez do modelo que realmente gerou o projeto.</p>
<p><strong>Gemini 3.1 Pro — o mais rápido, mas tropeça na API.</strong> Completou em 14 minutos, o mais rápido junto com o MiniMax. O código Rails em si é bem feito: usa <code>Rails.cache</code> com session ID e expiração de 2 horas pra manter estado (em vez de banco de dados), Turbo Streams bem integrados, Stimulus controller pra scroll automático, e o Dockerfile é o melhor do grupo (multi-stage, non-root, jemalloc). O problema é o de sempre: usa <code>RubyLLM::Chat.new()</code> em vez de <code>RubyLLM.chat()</code>, e chama <code>add_message()</code> que não existe. O app boota, o Docker roda, o health check passa, mas a primeira mensagem de chat dá 500. Os testes (5 métodos) mockam com um <code>FakeChat</code> que replica a assinatura errada, então passam. É frustrante porque o resto do código é o mais &ldquo;Rails way&rdquo; dos modelos que não são Anthropic. Corrigir seriam 3 linhas, mas o benchmark mede o que sai de primeira.</p>
<p><strong>Kimi K2.5 — ambicioso mas quebrado.</strong> Tentou a arquitetura mais sofisticada: ActionCable streaming, modelos configuráveis, dual Dockerfiles, 37 testes em 374 linhas. Problema: o streaming depende do ActionCable, que está comentado no <code>config/application.rb</code>. O guard <code>return unless defined?(ActionCable)</code> faz o método não fazer nada. O assistente nunca responde. O Stimulus controller tem bug de escopo: o <code>submitTarget</code> referencia um botão fora da subtree do controller. Storage thread-unsafe com hash em variável de classe. O Kimi escreveu mais testes que qualquer outro modelo (37), mas nenhum mocka as chamadas LLM — então os testes passam sem provar que a funcionalidade funciona de verdade.</p>
<p><strong>Grok 4.20 — rápido e errado.</strong> Foi o mais rápido do benchmark inteiro: 8 minutos, 412 tok/s. Só que foi rápido porque cortou caminho. O prompt pedia explicitamente o gem <code>ruby_llm</code>, e o Grok ignorou. Foi direto no <code>OpenAI::Client</code> da gem <code>ruby-openai</code> apontando pra URL do OpenRouter. Tecnicamente a primeira mensagem volta, então sim, &ldquo;funciona&rdquo;. Mas é o mesmo truque do Step 3.5 Flash e do Qwen 3.5 122B: pula a peça que tava sendo testada. Sem histórico, controller de 33 linhas chamando o cliente HTTP no braço, dois testes, nenhum mock de verdade. Foi rápido porque fez menos do que pediram.</p>
<p><strong>MiniMax M2.7 — parece certo, crasha.</strong> Chama <code>RubyLLM.chat(model: '...', messages: [...])</code> — essa assinatura não existe. Sem persistência de mensagens. HTML duplicado (DOCTYPE dentro do layout). master.key commitada. E os testes? Mockam a API errada, então passam mas não provam nada.</p>
<p>Resumo do code review:</p>
<table>
  <thead>
      <tr>
          <th>Aspecto</th>
          <th style="text-align: center">Sonnet 4.6</th>
          <th style="text-align: center">GLM 5</th>
          <th style="text-align: center">Gemini 3.1 Pro</th>
          <th style="text-align: center">Kimi K2.5</th>
          <th style="text-align: center">MiniMax M2.7</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>API correta</td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center">Não</td>
      </tr>
      <tr>
          <td>Histórico de chat</td>
          <td style="text-align: center">Session cookie</td>
          <td style="text-align: center">Nenhum</td>
          <td style="text-align: center">Rails.cache (2h)</td>
          <td style="text-align: center">Broken (ActionCable off)</td>
          <td style="text-align: center">Nenhum</td>
      </tr>
      <tr>
          <td>Service layer</td>
          <td style="text-align: center">LlmChatService</td>
          <td style="text-align: center">Inline no controller</td>
          <td style="text-align: center">LlmService</td>
          <td style="text-align: center">LlmService</td>
          <td style="text-align: center">ChatService (API errada)</td>
      </tr>
      <tr>
          <td>Testes (métodos)</td>
          <td style="text-align: center">30</td>
          <td style="text-align: center">7</td>
          <td style="text-align: center">5</td>
          <td style="text-align: center">37</td>
          <td style="text-align: center">12</td>
      </tr>
      <tr>
          <td>Mocking LLM</td>
          <td style="text-align: center">Sim (mocha)</td>
          <td style="text-align: center">Sim (mocha)</td>
          <td style="text-align: center">FakeChat (API errada)</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center">Mock da API errada</td>
      </tr>
      <tr>
          <td>Dockerfile</td>
          <td style="text-align: center">Multi-stage</td>
          <td style="text-align: center">Multi-stage + jemalloc</td>
          <td style="text-align: center">Multi-stage + jemalloc</td>
          <td style="text-align: center">Dual (dev/prod)</td>
          <td style="text-align: center">Single-stage</td>
      </tr>
      <tr>
          <td>Roda de verdade?</td>
          <td style="text-align: center">Sim</td>
          <td style="text-align: center">Sim (sem histórico)</td>
          <td style="text-align: center">Não (500 no chat)</td>
          <td style="text-align: center">Não</td>
          <td style="text-align: center">Não</td>
      </tr>
  </tbody>
</table>
<h3>GLM 5 vs GLM 5.1: o que mudou<span class="hx:absolute hx:-mt-20" id="glm-5-vs-glm-51-o-que-mudou"></span>
    <a href="#glm-5-vs-glm-51-o-que-mudou" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O GLM 5 foi um dos poucos modelos que cuspiu código funcional de primeira, então era óbvio testar a versão nova. Um detalhe que importa antes dos números: o GLM 5 rodou via OpenRouter, o GLM 5.1 ainda não tava lá quando rodei o teste, então usei a API direta da Z.AI. Provedor diferente, infra diferente, cache diferente. Os números abaixo são referência, não medida exata.</p>
<table>
  <thead>
      <tr>
          <th>Aspecto</th>
          <th>GLM 5</th>
          <th>GLM 5.1</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Provedor</td>
          <td>OpenRouter</td>
          <td>Z.AI direto</td>
      </tr>
      <tr>
          <td>Tempo total</td>
          <td>17m</td>
          <td>22m</td>
      </tr>
      <tr>
          <td>Tok/s (final phase)</td>
          <td>400</td>
          <td>167</td>
      </tr>
      <tr>
          <td>Tokens efetivos novos</td>
          <td>1.138</td>
          <td>450</td>
      </tr>
      <tr>
          <td>Cache lido</td>
          <td>58.240</td>
          <td>81.216</td>
      </tr>
      <tr>
          <td>API RubyLLM correta</td>
          <td>Sim</td>
          <td>Sim</td>
      </tr>
      <tr>
          <td>Mocking nos testes</td>
          <td>Sim (mocha)</td>
          <td>Sim</td>
      </tr>
      <tr>
          <td>Testes</td>
          <td>7</td>
          <td>24</td>
      </tr>
      <tr>
          <td>Histórico de chat</td>
          <td>Não</td>
          <td>Sim (in-memory)</td>
      </tr>
      <tr>
          <td>Service layer</td>
          <td>Inline no controller</td>
          <td><code>ChatSession</code> model com <code>add_user_message</code>/<code>add_assistant_message</code></td>
      </tr>
  </tbody>
</table>
<p>O projeto do GLM 5.1 ficou bem mais completo. 24 testes contra 7. Separação real entre <code>ChatSession</code>, <code>ChatMessage</code> e o controller, em vez do GLM 5 botando tudo inline. Histórico de chat persistido em memória durante a sessão, então dá pra ter uma conversa multi-turn de verdade (o GLM 5 tratava cada mensagem como se fosse a primeira). E a API do RubyLLM continua correta, mesmo padrão <code>RubyLLM.chat(model:, provider:)</code> seguido de <code>c.user</code>/<code>c.assistant</code> pra montar o contexto. Tem até teste cobrindo a constante <code>MODEL</code>, coisa que normalmente ninguém faz.</p>
<p>O preço foi velocidade. 22 minutos contra 17, e a vazão caiu de 400 pra 167 tok/s. Pode ser provedor (Z.AI direto não é a mesma infra do OpenRouter), pode ser um run mais carregado no servidor, pode ser que o 5.1 raciocine mais. Não rodei várias vezes pra fazer média, então não vou dizer que o 5.1 é &ldquo;mais lento&rdquo;. Uma run só não prova regressão. O que dá pra dizer é que, no meu teste, o 5.1 entregou um projeto melhor estruturado e demorou um pouco mais pra fazer isso.</p>
<p>Pra quem quer fugir da Anthropic sem perder qualidade, GLM 5 e GLM 5.1 são as duas opções que funcionam. Se você precisa de billing centralizado no OpenRouter, GLM 5. Se consegue usar a Z.AI direto e quer um projeto mais redondo de primeira, GLM 5.1.</p>
<h2>Custos: API vs Assinatura<span class="hx:absolute hx:-mt-20" id="custos-api-vs-assinatura"></span>
    <a href="#custos-api-vs-assinatura" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Primeiro, o preço por token de cada modelo no OpenRouter:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/token-pricing.png" alt="Preço por token no OpenRouter"  loading="lazy" /></p>
<p>O GPT 5.4 Pro cobra $180 por milhão de tokens de output. O Claude Opus cobra $25. O GLM 5 cobra $2.30. E o Qwen 3.6 Plus é grátis (com rate limit). A escala logarítmica no gráfico esconde um pouco a brutalidade da diferença: de Qwen grátis pra GPT 5.4 Pro são ordens de magnitude.</p>
<p>Mas preço por token não é a história completa. Se você usa Claude ou GPT diariamente pra coding, a assinatura mensal pode sair muito mais barata que pagar por token via API:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/04/05/llm-benchmark/en/monthly-pricing.png" alt="Assinatura vs API: quanto custa usar Claude e GPT por mês"  loading="lazy" /></p>
<table>
  <thead>
      <tr>
          <th>Abordagem</th>
          <th style="text-align: right">Est. $/mês*</th>
          <th>Notas</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen 3.6 Plus (OpenRouter)</td>
          <td style="text-align: right">$0</td>
          <td>Grátis mas rate-limited</td>
      </tr>
      <tr>
          <td>Modelos locais</td>
          <td style="text-align: right">Eletricidade</td>
          <td>Precisa de hardware</td>
      </tr>
      <tr>
          <td>Claude Pro</td>
          <td style="text-align: right">$20</td>
          <td>~44K tokens/5hr</td>
      </tr>
      <tr>
          <td>ChatGPT Plus</td>
          <td style="text-align: right">$20</td>
          <td>Inclui Codex</td>
      </tr>
      <tr>
          <td>Claude Max 5x</td>
          <td style="text-align: right">$100</td>
          <td>~88K tokens/5hr</td>
      </tr>
      <tr>
          <td>Claude Sonnet (OpenRouter API)</td>
          <td style="text-align: right">~$150</td>
          <td>Sem cap, pay-as-you-go</td>
      </tr>
      <tr>
          <td>Claude Max 20x</td>
          <td style="text-align: right">$200</td>
          <td>~220K tokens/5hr</td>
      </tr>
      <tr>
          <td>ChatGPT Pro</td>
          <td style="text-align: right">$200</td>
          <td>GPT 5.4 Pro ilimitado</td>
      </tr>
      <tr>
          <td>Claude Opus (OpenRouter API)</td>
          <td style="text-align: right">~$450</td>
          <td>Sem cap, pay-as-you-go</td>
      </tr>
      <tr>
          <td>GPT 5.4 Pro (OpenRouter API)</td>
          <td style="text-align: right">~$990</td>
          <td>Absurdamente caro</td>
      </tr>
  </tbody>
</table>
<p>*Estimativa pra uso moderado de coding (~15M input + ~3M output tokens/mês).</p>
<p>O ponto principal: se você usa GPT 5.4 Pro, a assinatura ChatGPT Pro a $200/mês com uso ilimitado é 5x mais barata que pagar por token na API. Pra Claude, o Pro a $20/mês cobre uso leve, mas pra quem usa pesado (maratona de coding como a minha), o Max 20x a $200/mês sai mais barato que pagar Opus por token no OpenRouter (~$450/mês). Os modelos open source no OpenRouter ficam todos abaixo de $2.50/M output tokens, mas como vimos, a maioria gera código que não roda.</p>
<h2>O que funciona pra uso real<span class="hx:absolute hx:-mt-20" id="o-que-funciona-pra-uso-real"></span>
    <a href="#o-que-funciona-pra-uso-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de testar 33 modelos ao longo das duas rodadas e olhar o código gerado em detalhe:</p>
<p>Tier 1 (funciona plug and play):</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Qualidade</th>
          <th style="text-align: right">Custo/Run</th>
          <th>Trade-off</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Opus 4.7</td>
          <td>Novo baseline (28 testes, FakeChat, 96.7% cobertura)</td>
          <td style="text-align: right">~$1.10</td>
          <td>Incremental sobre 4.6, novo gold standard</td>
      </tr>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td>Melhor que Opus 4.6 no opencode (30 vs 16 testes)</td>
          <td style="text-align: right">~$0.63</td>
          <td>Mais barato, mas no Claude Code o Opus pode se sair melhor</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td>Gold standard anterior</td>
          <td style="text-align: right">~$1.05</td>
          <td>Baseline anterior</td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td>Bom (7 testes, API correta)</td>
          <td style="text-align: right">~$0.11</td>
          <td>89% mais barato, alternativa non-Anthropic/OpenAI que funciona</td>
      </tr>
      <tr>
          <td>GLM 5.1</td>
          <td>Bom (24 testes, histórico, API correta)</td>
          <td style="text-align: right">~$0.13</td>
          <td>~88% mais barato, projeto mais completo que o GLM 5</td>
      </tr>
  </tbody>
</table>
<p>Tier 2 (funciona com ressalvas):</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: center">Hardware</th>
          <th style="text-align: right">Custo/Run</th>
          <th>Ressalva</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPT 5.4 (Codex)</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: right">~$16.00</td>
          <td>Arquitetura impressionante (22 testes, injeção de dependência, PORO models), mas <code>add_message</code> com keyword args em vez de hash posicional quebra multi-turn. 7.6M tokens, 15x mais caro que Opus</td>
      </tr>
      <tr>
          <td>Step 3.5 Flash</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: right">~$0.02</td>
          <td>Bypassa o gem pedido, lento (38m)</td>
      </tr>
      <tr>
          <td>Grok 4.20</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: right">~$0.04</td>
          <td>Bypassa o gem pedido (vai direto pro <code>OpenAI::Client</code>), mas é o mais rápido do benchmark</td>
      </tr>
      <tr>
          <td>Qwen 3.6 35B</td>
          <td style="text-align: center">NVIDIA 5090</td>
          <td style="text-align: right">Grátis</td>
          <td>Entry point e <code>chat.ask</code> corretos, falta <code>.content</code>. Correção de 1 linha. ~10-15 min total</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B-A3B</td>
          <td style="text-align: center">NVIDIA 5090</td>
          <td style="text-align: right">Grátis</td>
          <td>Entry point certo, alucina <code>add_message</code>/<code>complete</code>. Dá pra arrumar em 1-2 follow-ups. ~15-20 min total</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B Claude-distilado</td>
          <td style="text-align: center">NVIDIA 5090</td>
          <td style="text-align: right">Grátis</td>
          <td>Estilo Claude, alucinação completa da API. 2-3 follow-ups pra arrumar. ~25-30 min total</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B (local)</td>
          <td style="text-align: center">AMD Strix</td>
          <td style="text-align: right">Grátis</td>
          <td>Funciona se default configurado, sem mocking, e lento</td>
      </tr>
  </tbody>
</table>
<p>Tier 3 (código quebrado, mesmo com prompts de correção é mais fácil refazer):</p>
<p>Kimi K2.5, MiniMax M2.7, DeepSeek V3.2, Gemini 3.1 Pro, Qwen 3 Coder Next (Strix), Qwen 3 Coder 30B (5090, devolveu mock string hardcoded), Qwen 3.5 122B, Qwen 3.6 Plus — todos inventam APIs que não existem ou nem tentam usar o gem.</p>
<p>Tier 4 (não completaram):</p>
<p>Gemma 4 (loop infinito nos dois hardwares), Llama 4 Scout (sem parser), GPT OSS 20B (diretório errado no Strix, regressão de parser na 5090), Qwen 3 32B (lento demais no Strix, scaffold parcial na 5090), Qwen 2.5 Coder 32B (timeout 90m com zero arquivos).</p>
<h3>Ranking simplificado (qualidade, tempo, preço)<span class="hx:absolute hx:-mt-20" id="ranking-simplificado-qualidade-tempo-preço"></span>
    <a href="#ranking-simplificado-qualidade-tempo-pre%c3%a7o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra quem quer só o resumão em forma de boletim escolar. Qualidade é se o código roda e quão completo está. Tempo é o tempo total do run. Preço é o custo estimado por execução no opencode. <strong>Hardware</strong> indica onde o modelo rodou — Cloud, Strix (AMD Strix Halo, LPDDR5x 256 GB/s) ou 5090 (NVIDIA RTX 5090, GDDR7 1792 GB/s). Modelos cloud rodaram via OpenRouter ou API direta do provedor.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: center">Tipo</th>
          <th style="text-align: center">Hardware</th>
          <th style="text-align: center">Qualidade</th>
          <th style="text-align: center">Tempo</th>
          <th style="text-align: center">Preço</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Opus 4.7</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">D</td>
      </tr>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">C</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">D</td>
      </tr>
      <tr>
          <td>GPT 5.4 (Codex)</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">B+</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">F</td>
      </tr>
      <tr>
          <td>GLM 5.1</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">A</td>
      </tr>
      <tr>
          <td>GLM 5</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">A−</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">A</td>
      </tr>
      <tr>
          <td>Qwen 3.6 35B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">B+</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B-A3B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3.5 27B Claude-distilado</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">C+</td>
          <td style="text-align: center">B</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Gemini 3.1 Pro</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">B</td>
      </tr>
      <tr>
          <td>Grok 4.20</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">C−</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A+</td>
      </tr>
      <tr>
          <td>Step 3.5 Flash</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">C−</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">A+</td>
      </tr>
      <tr>
          <td>Qwen 3.5 35B-A3B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder Next</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">D+</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3 32B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3 Coder 30B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">D−</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3.6 Plus</td>
          <td style="text-align: center">Commercial</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Kimi K2.5</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">C</td>
          <td style="text-align: center">A</td>
      </tr>
      <tr>
          <td>MiniMax M2.7</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">A</td>
          <td style="text-align: center">A+</td>
      </tr>
      <tr>
          <td>Qwen 3.5 122B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">D</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>DeepSeek V3.2</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Cloud</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">A+</td>
      </tr>
      <tr>
          <td>Qwen 2.5 Coder 32B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Gemma 4 31B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">5090</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">A+</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Gemma 4 31B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">—</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>GLM 4.7 Flash</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">—</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Llama 4 Scout</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">—</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>GPT OSS 20B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">—</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
      <tr>
          <td>Qwen 3 32B</td>
          <td style="text-align: center">OSS</td>
          <td style="text-align: center">Strix</td>
          <td style="text-align: center">F</td>
          <td style="text-align: center">—</td>
          <td style="text-align: center">A+ (grátis)</td>
      </tr>
  </tbody>
</table>
<p>Critério de qualidade: A+ funciona e o código está bem estruturado. A/B funciona com ressalvas pequenas a médias. C roda mas pula requisito do prompt ou tem problema estrutural sério. D quebra na primeira mensagem por API inventada. F não completou o benchmark ou produziu lixo. O GPT 5.4 via Codex caiu de A+ pra B+: a arquitetura é a mais sofisticada do benchmark (injeção de dependência, PORO models, 22 testes), mas a primeira mensagem funciona e o multi-turn quebra por convenção de chamada errada no <code>add_message</code>. Gastou 7.6M tokens (~$16/run) sem atingir o nível de acerto do Opus. &ldquo;Tipo&rdquo; separa modelos commercial (pesos fechados) dos OSS (pesos abertos, mesmo quando você usa via API hospedada). Os Qwens aparecem duas vezes quando rodaram nos dois hardwares, porque os resultados são diferentes o suficiente pra justificar — o Qwen 3.5 35B-A3B no 5090 sobe pro Tier B, no Strix continua no Tier C por causa do tempo de espera. Dos 33 modelos configurados ao longo das duas rodadas, alguns ficam de fora dessa tabela porque nem chegaram a executar (sem quota, runner quebrado, falha de infra, ou timeout antes da primeira mensagem).</p>
<h3>O veredito<span class="hx:absolute hx:-mt-20" id="o-veredito"></span>
    <a href="#o-veredito" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se quer o melhor resultado e não quer pensar: <strong>Claude Opus</strong>. O Opus 4.7 é o novo baseline — 28 testes, API correta, 96.7% de cobertura, FakeChat pattern nos testes. É melhoria incremental sobre o 4.6 (que tinha 16 testes), não uma revolução. Mas não precisa ser revolução. O 4.6 já funcionava, o 4.7 funciona igual e entrega um projeto um pouco mais polido. Se você estava no 4.6, atualizar pro 4.7 é trocar pra um modelo que faz a mesma coisa com mais capricho nos testes e na estrutura.</p>
<p>Um aviso sobre o Sonnet: ele ganhou do Opus nesse benchmark (30 testes vs 16 do Opus 4.6, vs 28 do Opus 4.7). Mas esse benchmark é um app web pequeno e bem definido. Na minha experiência real com projetos maiores, o Sonnet falha quando o raciocínio precisa ir mais fundo. Não estou falando de projetos imensos — basta ir um pouco além desse benchmark (mais controllers, mais integrações, decisões arquiteturais que dependem umas das outras) e o Sonnet começa a perder o fio da meada. O Opus tem teto de output de 128K tokens contra 64K do Sonnet, e o treinamento dele foi especificamente pra tarefas de longo prazo, planejamento multi-etapas e raciocínio profundo sobre código complexo. Num projeto pequeno como o do benchmark, esses músculos ficam parados, e nesse cenário Sonnet ganha por ser mais rápido e mais barato. Mas se você extrapolar isso pra &ldquo;Sonnet é melhor que Opus&rdquo;, vai levar um susto na primeira tarefa que exija raciocínio sustentado. Pode experimentar o Sonnet, é mais barato e pros projetos pequenos funciona. Mas pra projetos reais, você provavelmente vai acabar no Opus de qualquer jeito.</p>
<p>Sobre o GPT 5.4: agora temos dados objetivos. Rodei via Codex CLI com reasoning effort xHigh. A arquitetura que ele gera é a mais sofisticada do benchmark — injeção de dependência, PORO models, session management com TTL. Mas gastou 7.6M tokens (~$16/run, 15x mais caro que Opus) e errou a convenção de chamada do <code>add_message</code> (keyword args em vez de hash posicional), quebrando o multi-turn. Gastou mais, errou igual. Caiu do Tier 1 pro Tier 2. O padrão se repete: acerto de API é memória binária nos pesos. Não escala com budget de tokens nem com reasoning effort.</p>
<p>Se custo importa e você quer sair da Anthropic: <strong>GLM 5 ou GLM 5.1</strong> são as alternativas plug-and-play que funcionam. API correta, mocking nos testes, ~$0.11-$0.13 por run, ~88-89% mais barato que Opus. O GLM 5.1 entregou um projeto mais completo (24 testes, histórico de chat) ao custo de uns 5 minutos a mais.</p>
<p>Se quer evitar vendor lock-in total e tem hardware decente: <strong>Qwen 3.5 35B-A3B</strong> rodando local numa NVIDIA RTX 5090. Cinco minutos de execução a 273 tok/s, projeto Rails que arranca, e o erro de API se conserta em 1-2 follow-ups. Total realista até funcionar: ~15-20 minutos. Bate o Sonnet em custo (zero) e fica perto em tempo total. Essa opção simplesmente não existia na rodada anterior do benchmark, e marca o ponto onde &ldquo;rodar OSS local&rdquo; deixa de ser brincadeira e vira alternativa real. Importante: isso é específico de hardware com banda de memória alta. Numa RTX 4090 deve funcionar parecido. Num laptop com LPDDR5x ou num desktop com DDR4, esquece — você vai esperar 10x mais e o tempo total mata o argumento.</p>
<p>Se quer evitar vendor lock-in mas está em hardware fraco: <strong>GLM 5 ou GLM 5.1</strong> continuam sendo a escolha. São cloud, é verdade, mas a $0.11-$0.13 por run é praticamente preço de eletricidade.</p>
<p>Se quer testar a aposta &ldquo;Claude em casa&rdquo; via distilação: o <strong>Qwen 3.5 27B Claude-distilado</strong> tá lá pra brincar, mas já avisei que ele alucina exatamente as mesmas APIs falsas do Qwen base. Distilação transferiu o estilo do Claude, não o conhecimento factual sobre bibliotecas. Vale como experimento, não como produção.</p>
<p>Sim, talvez com dias de tweaking no llama.cpp, calibrando flags, ajustando prompts, testando builds diferentes, dê pra fazer o Gemma 4 ou outros modelos funcionarem melhor. Pra maioria das pessoas, isso não é realista. A distância entre modelos frontier (Claude, GPT) e modelos open source self-hosted é real. Não é marketing. O gap está diminuindo, mas continua existindo, e a natureza dele mudou: hoje o que falta nos open source é conhecimento factual sobre bibliotecas específicas, não capacidade bruta de raciocínio. Hardware deixou de ser o gargalo, pelo menos pra quem tem GPU recente.</p>
<p>No fim, o que importa é se o código roda. Um modelo pode gerar 3.405 arquivos, escrever 37 testes, produzir um README de 181 linhas, e ainda assim o app não funcionar porque a API que ele usa não existe. Métricas de completude e contagem de testes são necessárias mas não suficientes. O único sinal confiável é se o modelo usa APIs reais corretamente.</p>
<p>O benchmark completo, com código, configuração, prompts e resultados por modelo, está no <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">GitHub</a>.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>llms</category><category>modelos-locais</category></item><item><title>Review: Minisforum MS-S1 Max | AMD AI Max+ 395 com 96GB de VRAM</title><link>https://akitaonrails.github.io/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/03/31/review-minisforum-ms-s1-max-amd-ai-max-395/</guid><pubDate>Tue, 31 Mar 2026 15:00:00 -0300</pubDate><description>&lt;p&gt;Quem acompanha meus posts sobre &lt;a href="https://akitaonrails.github.io/2024/04/03/meu-netflix-pessoal-com-docker-compose/"&gt;home server&lt;/a&gt; sabe que eu rodava tudo num Intel NUC Core i7 com 32GB de RAM. Funcionava. Mas com o crescimento dos modelos de IA open source, o NUC virou um gargalo. Sem GPU dedicada, qualquer inferência de LLM ia pra CPU e ficava inutilizável.&lt;/p&gt;
&lt;p&gt;Eu comprei um Minisforum MS-S1 Max com o novo chip AMD Ryzen AI Max+ 395 por um motivo específico: esse chip suporta até 128GB de RAM unificada, e eu posso alocar 96GB como VRAM pro iGPU. Isso me dá mais VRAM do que qualquer placa gamer comercial, incluindo a RTX 5090 (32GB). E isso muda o que eu consigo rodar localmente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Quem acompanha meus posts sobre <a href="/2024/04/03/meu-netflix-pessoal-com-docker-compose/">home server</a> sabe que eu rodava tudo num Intel NUC Core i7 com 32GB de RAM. Funcionava. Mas com o crescimento dos modelos de IA open source, o NUC virou um gargalo. Sem GPU dedicada, qualquer inferência de LLM ia pra CPU e ficava inutilizável.</p>
<p>Eu comprei um Minisforum MS-S1 Max com o novo chip AMD Ryzen AI Max+ 395 por um motivo específico: esse chip suporta até 128GB de RAM unificada, e eu posso alocar 96GB como VRAM pro iGPU. Isso me dá mais VRAM do que qualquer placa gamer comercial, incluindo a RTX 5090 (32GB). E isso muda o que eu consigo rodar localmente.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-desk.jpg" alt="Minisforum MS-S1 Max na mesa"  loading="lazy" /></p>
<h2>Por que trocar o Intel NUC<span class="hx:absolute hx:-mt-20" id="por-que-trocar-o-intel-nuc"></span>
    <a href="#por-que-trocar-o-intel-nuc" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O NUC serviu bem como servidor Docker por dois anos. Mas a limitação era clara: sem GPU com VRAM suficiente, eu não conseguia rodar LLMs localmente de forma usável. O <a href="https://github.com/akitaonrails/FrankYomik"target="_blank" rel="noopener">Frank Yomik</a>, meu sistema de tradução automática de mangás, precisava de OCR via CPU (lento) e conectava remotamente no Ollama do meu desktop (AMD 7950X3D + RTX 5090) pra tradução. Funcionava, mas significava que meu desktop precisava estar ligado pro servidor funcionar.</p>
<p><img src="https://raw.githubusercontent.com/akitaonrails/FrankYomik/master/docs/sample_translate.png" alt="Frank Yomik - tradução automática de mangás"  loading="lazy" /></p>
<p>Com o Minisforum, o Frank Yomik roda inteiramente no servidor. O worker agora usa ROCm pra OCR com a iGPU, e o Ollama roda local com 96GB de VRAM. Zero dependência do desktop.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-nuc-compare.jpg" alt="Comparação: Intel NUC (esquerda) vs Minisforum MS-S1 Max (direita)"  loading="lazy" /></p>
<p>Na foto dá pra ter uma ideia do tamanho. O NUC é aquele cubinho pequeno na esquerda. O Minisforum é maior, mas ainda é um mini-PC. Cabe na prateleira do rack embaixo do Synology NAS sem problema.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-shelf.jpg" alt="Minisforum instalado na prateleira, ao lado do NAS"  loading="lazy" /></p>
<h2>As specs<span class="hx:absolute hx:-mt-20" id="as-specs"></span>
    <a href="#as-specs" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/blog/2026/03/31/minisforum-fastfetch.png" alt="fastfetch do Minisforum"  loading="lazy" /></p>
<p>O chip é o AMD Ryzen AI Max+ 395: 16 cores / 32 threads Zen 5, com iGPU Radeon 8060S integrada e 128GB de LPDDR5X unificada. No BIOS, eu configurei UMA Frame Buffer Size pra 96GB, o que deixa ~30GB de RAM pro sistema operacional e containers. Mais os parâmetros de kernel pra TTM (sem eles, o ROCm só enxerga 15.5GB mesmo com a alocação do BIOS).</p>
<p>O sistema operacional é openSUSE MicroOS (mais sobre isso no <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">próximo post</a>). O consumo de energia da máquina inteira fica abaixo de 100W, o que é absurdo pra quem está acostumado com GPUs dedicadas que puxam 450W+ sozinhas.</p>
<h2>Minisforum vs meu desktop: benchmarks<span class="hx:absolute hx:-mt-20" id="minisforum-vs-meu-desktop-benchmarks"></span>
    <a href="#minisforum-vs-meu-desktop-benchmarks" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu rodei um <a href="https://github.com/akitaonrails/homelab-docs/tree/master/benchmarks"target="_blank" rel="noopener">conjunto de benchmarks</a> comparando o Minisforum com meu desktop (AMD 7950X3D, 96GB DDR5, RTX 5090 32GB GDDR7). Os resultados são claros.</p>
<h3>CPU<span class="hx:absolute hx:-mt-20" id="cpu"></span>
    <a href="#cpu" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Teste</th>
          <th>7950X3D</th>
          <th>AI Max+ 395</th>
          <th>Vantagem</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prime sieve (single-core)</td>
          <td>0.021s</td>
          <td>0.018s</td>
          <td>Strix Halo +14%</td>
      </tr>
      <tr>
          <td>Float pi (single-core)</td>
          <td>1.335s</td>
          <td>1.706s</td>
          <td>7950X3D +28%</td>
      </tr>
      <tr>
          <td>Multi-core sieve (32 threads)</td>
          <td>0.181s</td>
          <td>0.118s</td>
          <td>Strix Halo +53%</td>
      </tr>
      <tr>
          <td>SHA-256 throughput</td>
          <td>2.714 MB/s</td>
          <td>2.488 MB/s</td>
          <td>7950X3D +9%</td>
      </tr>
      <tr>
          <td>AES-256-CBC throughput</td>
          <td>1.613 MB/s</td>
          <td>1.410 MB/s</td>
          <td>7950X3D +14%</td>
      </tr>
  </tbody>
</table>
<p>Resultados mistos. O AI Max+ 395 é melhor em paralelismo puro (sieve multi-core), provavelmente por conta da latência menor na arquitetura de memória unificada. O 7950X3D ganha em float e crypto por causa dos clocks mais altos e do 3D V-Cache.</p>
<h3>Inferência de LLMs (modelos que cabem nos dois)<span class="hx:absolute hx:-mt-20" id="inferência-de-llms-modelos-que-cabem-nos-dois"></span>
    <a href="#infer%c3%aancia-de-llms-modelos-que-cabem-nos-dois" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui é onde a coisa fica interessante. Pra modelos que cabem nos 32GB da RTX 5090, a comparação é puramente de bandwidth de memória:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho</th>
          <th>RTX 5090 (tok/s)</th>
          <th>Strix Halo (tok/s)</th>
          <th>Vantagem 5090</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>phi4</td>
          <td>9.1 GB</td>
          <td>155.1</td>
          <td>23.2</td>
          <td>6.7x</td>
      </tr>
      <tr>
          <td>qwen3:14b</td>
          <td>9.3 GB</td>
          <td>138.9</td>
          <td>22.6</td>
          <td>6.1x</td>
      </tr>
      <tr>
          <td>phi4-reasoning</td>
          <td>11.1 GB</td>
          <td>130.2</td>
          <td>19.1</td>
          <td>6.8x</td>
      </tr>
      <tr>
          <td>qwen3:32b</td>
          <td>20.2 GB</td>
          <td>66.9</td>
          <td>10.0</td>
          <td>6.7x</td>
      </tr>
  </tbody>
</table>
<p>A RTX 5090 é ~7x mais rápida. A explicação é simples: GDDR7 tem ~1.792 GB/s de bandwidth. LPDDR5X tem ~256 GB/s. A razão (7x) bate quase exatamente com a diferença de velocidade medida (6.7x). Inferência de LLM é um problema dominado por bandwidth de memória. Quem lê pesos mais rápido, gera tokens mais rápido.</p>
<h3>E o prompt processing?<span class="hx:absolute hx:-mt-20" id="e-o-prompt-processing"></span>
    <a href="#e-o-prompt-processing" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>RTX 5090 (tok/s)</th>
          <th>Strix Halo (tok/s)</th>
          <th>Vantagem 5090</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>phi4</td>
          <td>~1.933</td>
          <td>~212</td>
          <td>9.1x</td>
      </tr>
      <tr>
          <td>qwen3:14b</td>
          <td>~1.474</td>
          <td>~155</td>
          <td>9.5x</td>
      </tr>
      <tr>
          <td>qwen3:32b</td>
          <td>~767</td>
          <td>~68</td>
          <td>11.3x</td>
      </tr>
  </tbody>
</table>
<p>Prompt processing é ainda pior: 7-11x mais lento. Faz sentido, porque o prompt precisa ser processado inteiro antes de gerar o primeiro token, e é uma operação ainda mais bandwidth-intensiva.</p>
<h3>Onde o Strix Halo ganha: modelos grandes<span class="hx:absolute hx:-mt-20" id="onde-o-strix-halo-ganha-modelos-grandes"></span>
    <a href="#onde-o-strix-halo-ganha-modelos-grandes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora vem a razão pela qual eu comprei esse PC. Modelos que não cabem na RTX 5090:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho</th>
          <th>Strix Halo (tok/s)</th>
          <th>Notas</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>gpt-oss:20b</td>
          <td>13.8 GB MXFP4</td>
          <td>48.9</td>
          <td>MoE, mais rápido que esperado</td>
      </tr>
      <tr>
          <td>qwen3.5:35b</td>
          <td>23.9 GB</td>
          <td>43.2</td>
          <td>MoE, apenas ~4B params ativos</td>
      </tr>
      <tr>
          <td>qwen3-coder-next</td>
          <td>51.7 GB</td>
          <td>29.5</td>
          <td>MoE, 50GB+</td>
      </tr>
      <tr>
          <td>qwen3.5:122b</td>
          <td>81.4 GB Q4_K_M</td>
          <td>19.2</td>
          <td>122B params, MoE</td>
      </tr>
      <tr>
          <td>glm-4.7-flash:bf16</td>
          <td>59.9 GB</td>
          <td>17.9</td>
          <td>Full precision bf16</td>
      </tr>
      <tr>
          <td>qwen2.5:72b</td>
          <td>47.4 GB Q4_K_M</td>
          <td>4.5</td>
          <td>Dense 72B, bandwidth-limited</td>
      </tr>
  </tbody>
</table>
<p>O qwen3.5:122b com 81GB de pesos rodando a 19 tok/s. Num mini-PC. Isso simplesmente não é possível numa RTX 5090. Na placa da NVIDIA, esse modelo teria que fazer offload de camadas pra RAM do sistema, caindo pra 2-3 tok/s. Na prática, inutilizável.</p>
<p>A diferença entre modelos MoE e densos é brutal. O qwen3.5:35b roda a 43 tok/s porque, apesar de ter 35B de parâmetros totais, só ~4B ficam ativos por token. Um modelo denso de 72B como o qwen2.5:72b precisa ler 40GB+ de pesos por token, e a 256 GB/s de bandwidth, o máximo teórico é ~6.7 tok/s. Os 4.5 medidos representam ~67% de eficiência, que é o esperado pra iGPU (overhead de bus compartilhado e drivers).</p>
<h3>Resumo: quando usar cada máquina<span class="hx:absolute hx:-mt-20" id="resumo-quando-usar-cada-máquina"></span>
    <a href="#resumo-quando-usar-cada-m%c3%a1quina" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Caso de uso</th>
          <th>Melhor máquina</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Chat/coding interativo (modelos &lt;32GB)</td>
          <td>RTX 5090 (6-7x mais rápido)</td>
      </tr>
      <tr>
          <td>Modelos grandes (50GB+)</td>
          <td>Strix Halo (única opção)</td>
      </tr>
      <tr>
          <td>Modelos densos 70B+</td>
          <td>Strix Halo (única opção)</td>
      </tr>
      <tr>
          <td>Full-precision bf16</td>
          <td>Strix Halo (única opção)</td>
      </tr>
      <tr>
          <td>Batch processing com contexto longo</td>
          <td>Strix Halo (mais VRAM pra KV cache)</td>
      </tr>
      <tr>
          <td>API serving com baixa latência</td>
          <td>RTX 5090 (sub-150ms TTFT)</td>
      </tr>
  </tbody>
</table>
<h3>Um bug de ROCm que ainda existe<span class="hx:absolute hx:-mt-20" id="um-bug-de-rocm-que-ainda-existe"></span>
    <a href="#um-bug-de-rocm-que-ainda-existe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Nem tudo funciona. Modelos como deepseek-r1:70b, llama3.3:70b e llama4:scout crasham com um bug no ggml (<code>GGML_ASSERT(ggml_nbytes(src0) &lt;= INT_MAX) failed</code>). O tensor de embedding desses modelos excede 2GB e o kernel de cópia do ROCm usa inteiro de 32 bits pro tamanho. No CUDA (NVIDIA) já foi corrigido, mas no ROCm ainda não. Esperando o fix no Ollama 0.20.0+.</p>
<h2>LPDDR5X vs GDDR7: por que essa diferença<span class="hx:absolute hx:-mt-20" id="lpddr5x-vs-gddr7-por-que-essa-diferença"></span>
    <a href="#lpddr5x-vs-gddr7-por-que-essa-diferen%c3%a7a" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A pergunta que vem é: por que a LPDDR5X é tão mais lenta?</p>
<p>GDDR7 é memória dedicada de GPU. Ela fica soldada na placa de vídeo, conectada por um barramento largo (384 ou 512 bits na RTX 5090) com clocks altos. O único trabalho dela é alimentar a GPU com dados. LPDDR5X é memória unificada que serve pra tudo: sistema operacional, aplicações, e GPU ao mesmo tempo. O barramento é mais estreito e compartilhado.</p>
<p>Na prática: GDDR7 entrega ~1.792 GB/s dedicados pra GPU. LPDDR5X entrega ~256 GB/s que ainda precisam ser divididos entre CPU e GPU. Inferência de LLM é basicamente &ldquo;leia todos os pesos do modelo da memória, multiplique pelo token atual, gere o próximo token, repita&rdquo;. Quem lê mais rápido, gera mais rápido. Não tem atalho.</p>
<p>A vantagem do Strix Halo não é velocidade. É capacidade. 96GB de VRAM num chip de 100W que custa uma fração de uma GPU profissional. A RTX 5090 é 7x mais rápida, mas trava em 32GB. Modelos que não cabem, não rodam.</p>
<h2>As alternativas: quem mais faz isso?<span class="hx:absolute hx:-mt-20" id="as-alternativas-quem-mais-faz-isso"></span>
    <a href="#as-alternativas-quem-mais-faz-isso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se 96GB não é suficiente ou se você quer mais velocidade, as opções são poucas.</p>
<p>O Framework Desktop usa o mesmo chip AI Max+ 395 com até 128GB de RAM. Mesma plataforma, mesma performance, mas com o diferencial de ser modular e reparável (é o Framework, afinal). Na prática é equivalente ao Minisforum em specs e preço.</p>
<p>Acima disso, a alternativa é o Mac Studio com M3 Ultra. O chip M3 Ultra suporta até 512GB de memória unificada, com bandwidth de ~819 GB/s (mais de 3x o Strix Halo). A Apple fabrica os chips de memória no package, então a latência e a bandwidth são superiores. Você poderia potencialmente alocar ~400GB como VRAM e rodar modelos que não cabem em lugar nenhum fora de servidores com GPUs profissionais.</p>
<p>O NVMe interno da Apple também é outro nível: ~7.4 GB/s de leitura sequencial no M3 Ultra, comparado com ~14 GB/s do Crucial T700 (PCIe 5.0). O T700 é mais rápido em throughput bruto, mas a latência do NVMe da Apple tende a ser menor em I/O aleatório por causa da integração com o SoC.</p>
<table>
  <thead>
      <tr>
          <th>Spec</th>
          <th>Minisforum MS-S1 Max</th>
          <th>Mac Studio M3 Ultra (max)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>RAM máxima</td>
          <td>128 GB LPDDR5X</td>
          <td>512 GB unified</td>
      </tr>
      <tr>
          <td>VRAM alocável</td>
          <td>~96 GB</td>
          <td>~400 GB</td>
      </tr>
      <tr>
          <td>Memory bandwidth</td>
          <td>~256 GB/s</td>
          <td>~819 GB/s</td>
      </tr>
      <tr>
          <td>CPU</td>
          <td>Zen 5, 16C/32T</td>
          <td>Apple M3 Ultra, 32C</td>
      </tr>
      <tr>
          <td>GPU compute</td>
          <td>ROCm (gfx1151, experimental)</td>
          <td>Metal (mlx, mature)</td>
      </tr>
      <tr>
          <td>Consumo</td>
          <td>~100W</td>
          <td>~135W</td>
      </tr>
      <tr>
          <td>NVMe</td>
          <td>PCIe 5.0 (slot padrão)</td>
          <td>Custom Apple (~7.4 GB/s)</td>
      </tr>
      <tr>
          <td>Preço (EUA)</td>
          <td>~$1.500-2.000</td>
          <td>~$9.999 (512GB config)</td>
      </tr>
      <tr>
          <td>Preço estimado (Brasil)</td>
          <td>~R$ 12.000-15.000</td>
          <td>~R$ 110.000+ (importação)</td>
      </tr>
  </tbody>
</table>
<p>O preço no Brasil é o elefante na sala. A configuração máxima do Mac Studio custa $9.999 nos EUA. Com impostos de importação (~60% + ICMS estadual), passa dos R$ 110.000. O Minisforum com 128GB sai por R$ 12.000-15.000. A diferença de quase 8x no preço compra muita coisa.</p>
<p>Se você precisa de mais de 96GB de VRAM pra modelos realmente enormes (DeepSeek-V3 com 671B parâmetros cabe em ~400GB Q4, por exemplo), o Mac Studio com 512GB é a única opção consumer. A alternativa seria GPUs profissionais NVIDIA A6000 (48GB VRAM, ~$6.000 cada, e você precisaria de várias em NVLink). Pra tudo que cabe em 96GB, o Minisforum faz o trabalho por uma fração do custo.</p>
<h2>E projetos que prometem rodar LLMs grandes em GPUs pequenas?<span class="hx:absolute hx:-mt-20" id="e-projetos-que-prometem-rodar-llms-grandes-em-gpus-pequenas"></span>
    <a href="#e-projetos-que-prometem-rodar-llms-grandes-em-gpus-pequenas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Existe o conceito de &ldquo;layer offloading&rdquo; que projetos como llama.cpp já suportam. A ideia é: se o modelo não cabe inteiro na VRAM, mantém algumas camadas na GPU e o resto na RAM do sistema. A GPU processa as camadas que estão nela, transfere pro CPU processar o resto, e volta.</p>
<p>Na prática, não funciona bem. O gargalo é o PCIe: a velocidade de transferência entre RAM do sistema e VRAM da GPU é de ~32 GB/s (PCIe 5.0 x16). Cada token gerado precisa transferir dados ida e volta. O resultado é que você cai de 150 tok/s (tudo na VRAM) pra 2-8 tok/s (offload parcial). É lento demais pra uso interativo.</p>
<p>A VRAM é a limitação fundamental porque inferência de LLM é memory-bandwidth-bound, não compute-bound. A GPU tem compute sobrando. O que falta é capacidade de ler os pesos do modelo rápido o suficiente. Quando parte dos pesos está em RAM via PCIe, o pipeline inteiro espera pela transferência.</p>
<p>É por isso que memória unificada (como no Strix Halo ou no Apple Silicon) faz diferença. Não tem PCIe no meio. CPU e GPU acessam a mesma memória física. Os 256 GB/s do Strix Halo são lentos comparados com GDDR7, mas são 8x mais rápidos que ficar fazendo offload via PCIe.</p>
<h2>Avanços em otimização de LLMs (até 2026)<span class="hx:absolute hx:-mt-20" id="avanços-em-otimização-de-llms-até-2026"></span>
    <a href="#avan%c3%a7os-em-otimiza%c3%a7%c3%a3o-de-llms-at%c3%a9-2026" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra entender por que alguns modelos rodam tão melhor que outros no Strix Halo, precisa entender o que mudou no ecossistema nos últimos dois anos.</p>
<h3>Mixture of Experts (MoE)<span class="hx:absolute hx:-mt-20" id="mixture-of-experts-moe"></span>
    <a href="#mixture-of-experts-moe" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você roda modelos locais, MoE é o avanço que mais importa. Um modelo MoE tem parâmetros totais altos (ex: 122B no qwen3.5:122b), mas ativa apenas uma fração deles por token (ex: ~4B). Os pesos inativos ficam na VRAM mas não são lidos a cada token, o que reduz drasticamente a bandwidth necessária.</p>
<p>Nos benchmarks do Strix Halo, modelos MoE rodam 3-10x mais rápido que modelos densos do mesmo tamanho. O qwen3.5:35b (MoE, ~4B ativos) roda a 43 tok/s enquanto o qwen2.5:72b (denso, 72B ativos) roda a 4.5 tok/s.</p>
<h3>DeepSeek e a otimização de treinamento<span class="hx:absolute hx:-mt-20" id="deepseek-e-a-otimização-de-treinamento"></span>
    <a href="#deepseek-e-a-otimiza%c3%a7%c3%a3o-de-treinamento" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O DeepSeek V3 (dezembro 2024) mostrou que era possível treinar modelos de 671B parâmetros com custo uma ordem de magnitude menor que o previsto. Eles combinaram MoE com quantização FP8 durante o treinamento (não só na inferência), treinamento multi-estágio com curriculum learning, e várias otimizações de comunicação entre GPUs. O impacto: todo mundo copiou. Qwen, GLM, MiniMax, todos adotaram variações dessa técnica.</p>
<h3>Quantização: de FP16 pra Q4 sem perder muito<span class="hx:absolute hx:-mt-20" id="quantização-de-fp16-pra-q4-sem-perder-muito"></span>
    <a href="#quantiza%c3%a7%c3%a3o-de-fp16-pra-q4-sem-perder-muito" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quantização comprime os pesos do modelo de 16 bits (FP16) pra formatos menores: 8 bits (Q8), 4 bits (Q4), ou até 2 bits. Um modelo de 70B que ocuparia ~140GB em FP16 cabe em ~40GB em Q4_K_M. A perda de qualidade existe, mas nos formatos modernos (GGUF Q4_K_M, AWQ, EXL2) é pequena o suficiente pra uso prático.</p>
<p>O GGUF (formato do llama.cpp) se tornou o padrão pra inferência local. AWQ e GPTQ são alternativas com calibração mais sofisticada, mas o ecossistema convergiu pro GGUF porque ele funciona em CPU, CUDA e ROCm sem recompilação.</p>
<h3>Destilação: modelos menores que sabem mais<span class="hx:absolute hx:-mt-20" id="destilação-modelos-menores-que-sabem-mais"></span>
    <a href="#destila%c3%a7%c3%a3o-modelos-menores-que-sabem-mais" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Destilação é treinar um modelo pequeno usando as respostas de um modelo grande como professor. O Phi-4 da Microsoft (14B) foi treinado com destilação do GPT-4 e compete com modelos de 70B em vários benchmarks. O Qwen3 fez o mesmo: o qwen3:14b é surpreendentemente capaz pro tamanho.</p>
<h3>Flash Attention e KV Cache otimizado<span class="hx:absolute hx:-mt-20" id="flash-attention-e-kv-cache-otimizado"></span>
    <a href="#flash-attention-e-kv-cache-otimizado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Flash Attention (Tri Dao, 2022) mudou como attention é computada: em vez de materializar a matriz inteira de attention na memória, processa em blocos mantendo os dados no SRAM on-chip da GPU, reduzindo consumo de memória de O(n²) pra O(n). Sem isso, contextos de 128K+ tokens seriam impraticáveis. Já passou pelas versões 2 e 3, com otimizações pra FP8 e operações assíncronas no H100. O PagedAttention (vLLM, UC Berkeley) fez o mesmo pro KV cache durante serving: aplica conceitos de memória virtual ao cache, eliminando fragmentação e melhorando throughput 2-4x.</p>
<p>No Ollama, eu configurei <code>OLLAMA_FLASH_ATTENTION=1</code> e <code>OLLAMA_KV_CACHE_TYPE=q8_0</code> no servidor. O primeiro ativa flash attention, o segundo usa KV cache em 8-bit em vez de fp16, cortando a bandwidth necessária pela metade por token. São otimizações que custam zero em hardware e melhoram throughput mensurável.</p>
<h3>O que Qwen, Kimi, MiniMax e GLM estão fazendo<span class="hx:absolute hx:-mt-20" id="o-que-qwen-kimi-minimax-e-glm-estão-fazendo"></span>
    <a href="#o-que-qwen-kimi-minimax-e-glm-est%c3%a3o-fazendo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Qwen (Alibaba) tem sido consistentemente o melhor custo-benefício em modelos open source. O Qwen3:14b é denso e forte; o Qwen3.5:122b é MoE e roda surpreendentemente bem em 96GB. O GLM-4.7 (Zhipu AI) é notável por oferecer versões bf16 full precision que rodam inteiras em 96GB. O MiniMax experimentou com contextos longos (até 4M tokens). O Kimi (Moonshot AI) focou em context windows grandes com arquiteturas lineares.</p>
<h3>O que roda bem em 96GB de VRAM<span class="hx:absolute hx:-mt-20" id="o-que-roda-bem-em-96gb-de-vram"></span>
    <a href="#o-que-roda-bem-em-96gb-de-vram" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Com 96GB no Strix Halo, os modelos que funcionam bem pra uso diário:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho</th>
          <th>tok/s</th>
          <th>Uso</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>qwen3.5:35b</td>
          <td>24 GB</td>
          <td>43.2</td>
          <td>Uso geral, excelente</td>
      </tr>
      <tr>
          <td>qwen3-coder-next</td>
          <td>52 GB</td>
          <td>29.5</td>
          <td>Código, MoE</td>
      </tr>
      <tr>
          <td>qwen3.5:122b</td>
          <td>81 GB</td>
          <td>19.2</td>
          <td>Pesado mas usável</td>
      </tr>
      <tr>
          <td>glm-4.7-flash:bf16</td>
          <td>60 GB</td>
          <td>17.9</td>
          <td>Full precision</td>
      </tr>
      <tr>
          <td>qwen2.5-coder:32b</td>
          <td>20 GB</td>
          <td>10.2</td>
          <td>Código, denso</td>
      </tr>
      <tr>
          <td>deepseek-r1:32b</td>
          <td>20 GB</td>
          <td>7.4</td>
          <td>Raciocínio</td>
      </tr>
  </tbody>
</table>
<p>Modelos densos de 70B+ (deepseek-r1:70b, llama3.3:70b) ainda estão bloqueados pelo bug do ROCm que mencionei. Quando for corrigido, devem rodar a ~4-6 tok/s, usáveis pra batch mas não pra chat interativo.</p>
<h2>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu comprei o Minisforum pra rodar modelos que não cabem em GPU gamer nenhuma. Pra isso, funciona. Não é rápido. 19 tok/s num modelo de 122B não é a experiência que você tem com Claude ou ChatGPT. Mas é local, é privado, e roda na minha prateleira consumindo menos energia que uma lâmpada antiga.</p>
<p>Pra quem pergunta sobre o Mac Studio: se você tem orçamento, é a melhor máquina pra rodar LLMs locais. 512GB de memória unificada, 819 GB/s de bandwidth, ecossistema Metal/mlx maduro. Dá pra rodar DeepSeek-V3 inteiro em Q4. Mas no Brasil, com importação, passa dos R$ 110 mil. O Minisforum com 128GB por R$ 12-15 mil é a opção realista.</p>
<p>E pra quem acha que dá pra contornar a limitação de VRAM com offloading de camadas: não dá. PCIe é lento demais. O modelo precisa caber inteiro na VRAM pra inferência ser usável. É o motivo pelo qual GPUs gamers com 32GB de GDDR7 ultra-rápida continuam limitadas em tamanho de modelo, e por que a memória unificada do Strix Halo e do Apple Silicon mudou a equação.</p>
<p>No <a href="/2026/03/31/migrando-meu-home-server-com-claude-code/">próximo post</a> eu conto como migrei todo o home server pro Minisforum usando Claude Code, os problemas que encontrei, e como o openSUSE MicroOS se comporta como sistema operacional de servidor Docker.</p>
]]></content:encoded><category>hardware</category><category>modelos-locais</category><category>performance</category><category>reviews</category></item><item><title>AI Agents: GLM 4.7 Flash é realmente tão bom assim?</title><link>https://akitaonrails.github.io/2026/01/22/ai-agents-glm-4-7-flash-e-realmente-tao-bom-assim/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/01/22/ai-agents-glm-4-7-flash-e-realmente-tao-bom-assim/</guid><pubDate>Thu, 22 Jan 2026 13:41:05 -0300</pubDate><description>&lt;p&gt;Recentemente fiz &lt;a href="https://akitaonrails.com/2026/01/11/ai-agents-comparando-as-principais-llm-no-desafio-de-zig/"target="_blank" rel="noopener"&gt;um post comparando várias LLMs&lt;/a&gt;. A boa notícia é que as LLMs comerciais estão bem impressionantes mesmo. Mas a má notícia é que nenhuma LLM open source foi capaz de realizar meu desafio de programação (leia no post anterior pra entender o desafio).&lt;/p&gt;
&lt;p&gt;Também recentemente, a ZAI lançou a versão 4.7 do seu famoso modelo &lt;a href="https://huggingface.co/zai-org/GLM-4.7"target="_blank" rel="noopener"&gt;&amp;ldquo;GLM&amp;rdquo;&lt;/a&gt; que é um modelo de 30 bilhões de parâmetros, encodado em BF16, que precisa na faixa de 60GB de VRAM pra caber inteiro numa GPU. Ou seja, é pesado.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Recentemente fiz <a href="https://akitaonrails.com/2026/01/11/ai-agents-comparando-as-principais-llm-no-desafio-de-zig/"target="_blank" rel="noopener">um post comparando várias LLMs</a>. A boa notícia é que as LLMs comerciais estão bem impressionantes mesmo. Mas a má notícia é que nenhuma LLM open source foi capaz de realizar meu desafio de programação (leia no post anterior pra entender o desafio).</p>
<p>Também recentemente, a ZAI lançou a versão 4.7 do seu famoso modelo <a href="https://huggingface.co/zai-org/GLM-4.7"target="_blank" rel="noopener">&ldquo;GLM&rdquo;</a> que é um modelo de 30 bilhões de parâmetros, encodado em BF16, que precisa na faixa de 60GB de VRAM pra caber inteiro numa GPU. Ou seja, é pesado.</p>
<p>Ainda mais recentemente, ela lançou a versão <a href="https://huggingface.co/zai-org/GLM-4.7-Flash"target="_blank" rel="noopener"><strong>GLM 4.7 Flash</strong></a> que é o mesmo modelo de 30 bilhões de parâmetros, mas quantizado (a grosso modo: &ldquo;comprimido&rdquo; ou &ldquo;truncado e re-normalizado&rdquo;), que cabe numa GPU de pelo menos 32GB, que é o caso da minha <strong>RTX 5090</strong>.</p>
<p>Sim, a GPU caseira mais cara do mercado é o mínimo necessário pra rodar os melhores modelos open source. Isso porque o importante não é o poder de processamento e sim a quantidade de VRAM. Por isso um Mac Studio, com GPU tecnicamente mais &ldquo;fraca&rdquo;, tem vantagem em LLMs porque ela compartilha com a RAM, com máximo teórico de 512GB. Então cabem os maiores modelos sem problema nenhum.</p>
<p>Outras opções são Mini PCs com CPU AMD Ryzen AI Max+ com capacidade de compartilhar até 128GB de VRAM.</p>
<p>O desafio era ver se eu conseguia rodar o GLM 4.7 Flash localmente na minha máquina.</p>
<blockquote>
  <p><strong>TL;DR: GLM 4.7 Flash é possivelmente o melhor LLM open source - com alguns poréns.</strong></p>

</blockquote>
<h3>VLLM vs LM Studio vs Ollama<span class="hx:absolute hx:-mt-20" id="vllm-vs-lm-studio-vs-ollama"></span>
    <a href="#vllm-vs-lm-studio-vs-ollama" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na <a href="https://huggingface.co/zai-org/GLM-4.7-Flash"target="_blank" rel="noopener">página oficial</a> deles tem documentação de como rodar em VLLM e já aviso que não funciona. Ele exige funcionalidades que só tem na branch master dos projetos VLLM e transformers e vai dar uma dor de cabeça com conflitos de versões e resolução de dependências e mesmo assim, no final, eu não consegui fazer ele rodar direito.</p>
<p>LM Studio também não funciona. Ele tem a opção pra baixar o modelo, e eu tentei tunar todos os parâmetros possíveis. Quantização do KV Cache, layers na GPU, CPU threads, etc etc. Mas não importa, o modelo até carrega mas roda absurdamente lento. Tão lento que é inusável. Acho que é porque ele só consegue baixar o modelo BF16, então metade dos layers não cabe na GPU. Portanto, até a data deste post, não dá pra usar nem LM Studio e nem VLLM.</p>
<p>Mas aí eu vi um <a href="https://x.com/ollama/status/2013372316021834086"target="_blank" rel="noopener">tweet</a> dizendo que finalmente Ollama ia suportar GLM 4.7, mas novamente, até a data deste post, ainda não está na versão estável. A única forma de usar é compilando do código fonte as coisas que estão no branch master deles.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git clone https://github.com/ollama/ollama.git
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> ollama
</span></span><span class="line"><span class="cl">cmake -B build
</span></span><span class="line"><span class="cl">cmake --build build
</span></span><span class="line"><span class="cl">go build -v .</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No último comando vai parecer que travou porque nada aparece na tela, mas é porque ele está compilando centenas de arquivos de C++. Só esperar que uma hora acaba. E finalmente dá pra rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OLLAMA_FLASH_ATTENTION</span><span class="o">=</span><span class="m">1</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="nv">GGML_VK_VISIBLE_DEVICES</span><span class="o">=</span>-1 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="nv">OLLAMA_KV_CACHE_TYPE</span><span class="o">=</span>q4_0 <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="nv">OLLAMA_CONTEXT_LENGTH</span><span class="o">=</span><span class="m">45000</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">go run . serve</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esses são os parâmetros pra conseguir caber tudo nos 32GB de VRAM e não ficar fazendo offload pra CPU. 45 mil tokens é o máximo, talvez até um pouco menos pra garantir que o KV Cache vai caber.</p>
<p>Além disso precisa quantizar em 4-bits com <code>q4_0</code>. O normal são floats 16-bits. Pra caber vamos ter que truncar (perder precisão).</p>
<p>No meu caso, eu tenho uma iGPU AMD. Pra evitar que o ollama fique tentando usar ela, tem essa opção <code>GGML_VK_VISIBLE_DEVICES=-1</code>. Isso deve garantir que só a nvidia vai ser usada.</p>
<p>Finalmente, habilitamos <strong>Flash Attention</strong> pra tentar reduzir crescimento de memória à medida que janela de contexto aumenta.</p>
<h3>Trade off: velocidade vs contexto<span class="hx:absolute hx:-mt-20" id="trade-off-velocidade-vs-contexto"></span>
    <a href="#trade-off-velocidade-vs-contexto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O GLM 4.7 Flash é um modelo com 48 layers. Na configuração que mostrei acima, vai caber todos os 48 layers na GPU. Mas uma janela de contexto de 45k <strong>pode</strong> ser muito pequeno dependendo do seu uso. Você mesmo vai ter que testar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123101949_screenshot-2026-01-23_10-09-35.png" alt="log gpu"  loading="lazy" /></p>
<p>Quando cabe tudo na GPU, o processamento é razoavelmente rápido. Um pouco mais lento mas ainda comparável a um Qwen3 Coder ou GPT-OSS. Dá pra saber que está tudo na GPU porque olhando BTOP a CPU está sem processar nada e ao mesmo tempo <code>nvidia-smi</code> mostra que a GPU tá puxando na faixa de 500W dos 600W que ele consegue.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123102147_screenshot-2026-01-22_18-50-42.png" alt="nvidia-smi 500w"  loading="lazy" /></p>
<p>Nos meus testes, dependendo do que estiver fazendo, esses 45k enchem <strong>MUITO RÁPIDO</strong>. Usando ferramentas como OpenCode ou Crush, quando chega perto de encher, eles pedem pra LLM sumarizar o que foi conversado/pesquisado até agora, gravar e reiniciar com o resumo. Mas mesmo assim, os resumos também vão ficando grandes.</p>
<p>Em agentes, ele vai executar comandos no seu sistema (compilar, listar arquivos, pesquisar trechos de código, pesquisar na web) e tudo isso vai consumir contexto. Quanto mais contexto tiver sobrando, melhor.</p>
<p>Eu andei testando com <code>OLLAMA_CONTEXT_LENGTH=65576</code>, faixa dos 65k tokens. Isso vai impedir todos os layers de ficarem na GPU e o Ollama vai ter que ficar fazendo swap, usando CPU. Portanto, o processamento vai ficar bem mais lento. MAS, a janela de contexto vai ser maior. É um trade-off.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123102557_screenshot-2026-01-22_20-51-05.png" alt="btop"  loading="lazy" /></p>
<p>De cara, você vai ber que a CPU vai começar a puxar. Na minha 7850x3d fica puxando 50% constantemente. Veja os tempos das respostas do Ollama como ficou lento:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123102645_screenshot-2026-01-23_10-09-50.png" alt="ollama cpu"  loading="lazy" /></p>
<p>Vai da faixa de 4 segundos pra 90 segundos. 20x mais lento. E isso porque 39 dos 48 layers estão na GPU. Por isso eu acho que não dá pra aumentar mais que 65k. O ideal é manter em 45k mesmo, mas no caso do meu desafio, senti que precisava compensar.</p>
<p>Ou o Crush/OpenCode vai demorar porque a janela acaba rápido demais e toda hora precisa sumarizar o trabalho e perder detalhes do contexto - o que dá mais trabalho em pesquisar a mesma coisa mais de uma vez. Ou preserva mais detalhes do contexto por mais tempo, mas aí todas as respostas ficam mais lentas. É um trade-off bem difícil. E a culpa de tudo é a porcaria dessas placas de video terem tão pouca VRAM.</p>
<p>Se usar um Mac Studio que vai até 512GB ou algum Mini-PC com AMD Ryzen AI Max+ que vai até 128GB, não teríamos que lidar com esse limite. Mas são máquinas que custam acima de BRL 20 mil. Não dá pra justificar. Por esse preço é melhor só pagar créditos na OpenRouter e usar modelos comerciais maiores e mais rápidos como Claude Opus ou GPT 5.2, ou o próprio GLM 4.7 no cloud da ZAI.</p>
<h3>Pull Request no Crush<span class="hx:absolute hx:-mt-20" id="pull-request-no-crush"></span>
    <a href="#pull-request-no-crush" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como side-quest, tive um pequeno problema que tava me incomodando. Um dos problemas de uma janela de contexto pequena, é que de tempos em tempos o Crush ficava parando, mesmo ainda tendo trabalho pra fazer. O motivo é porque o contexto encheu antes dele ter tempo de sumarizar daí quando vai fazer uma chamada de ferramenta (&ldquo;tool calling&rdquo;), que é um XML grande, ele trunca por falta de espaço.</p>
<p>XML truncado é inválido, ele não consegue chamar ferramenta e crasheia o processamento, assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123103238_screenshot-2026-01-22_15-12-36.png" alt="log xml"  loading="lazy" /></p>
<p>E mesmo tentando controlar o limite de uso da memória, eventualmente esse teto vai quebrar e o processamento vai parar por falta de memória:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123103736_screenshot-2026-01-23_10-36-43.png" alt="log nvm"  loading="lazy" /></p>
<p>Seja por XML truncado, seja por OOM do ollama, eventualmente o processamento pára. E como é um processamento lento, é um saco ter que ficar esperando isso acontecer e reiniciar o processo manualmente.</p>
<p>Por isso resolvi ver se tinha como fazer o próprio Crush detectar que parou por causa desses motivos e auto-continuar de onde parou. Abri o próprio Crush em cima do projeto do Crush e pedi pro Claude Opus checar a possibilidade de uma correção.</p>
<p><a href="https://github.com/charmbracelet/crush/pull/1962"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20260123105702_screenshot-2026-01-23_10-56-40.png" alt="pull request"  loading="lazy" /></a></p>
<p>Sim, eu fiz um pull request 100% vibe coded com o próprio Crush pra consertar o Crush 😂 Vamos ver se o povo do Crush vai aceitar. Eu tentei o máximo não fazer um <a href="https://www.youtube.com/watch?v=vrTrOCQZoQE&amp;pp=ygUHYWkgc2xvcNIHCQmHCgGHKiGM7w%3D%3D"target="_blank" rel="noopener">AI Slop</a>.</p>
<p>De qualquer forma, com isso eu consigo deixar o agente rodando horas sem parar até chegar onde eu quero.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><blockquote>
  <p>Este NÃO é o jeito certo!</p>

</blockquote>
<p>Deixando claro que largar um agente fazendo coisas sozinho sem ninguém ver é uma má-prática. No meu caso é um exercício isolado. Eu estou usando meu <a href="https://akitaonrails.com/2026/01/10/ai-agents-garantindo-a-protecao-do-seu-sistema/"target="_blank" rel="noopener">AI JAIL</a> e eu já rodei esse prompt várias vezes como benchmark e sei bem o comportamento de todas as LLMs.</p>
<p>Como disse no meu post anterior, o prompt do meu desafio é cheio de más práticas que você não deve fazer: eu peço muitas coisas complexas ao mesmo tempo, com pouco contexto, de forma vaga e confusa, justamente pra ver como a LLM vai se virar pra resolver. No dia a dia você deve mandar prompts pequenos, tarefas unitárias objetivas e bem espefícas, com boa explicação e contexto, pra que as respostas sejam rápidas e curtas. Um passo de cada vez.</p>
<p>O objetivo do meu desafio é justamente puxar um pouco os limites do senso comum. Com a idéia de que se passar pelo meu desafio, deve conseguir passar por prompts mais simples do dia a dia.</p>
<p>E nesse desafio que só as LLMs comerciais conseguiram passar: Claude Opus, GPT 5.2, Gemini 3. Mas nenhum open source conseguiu, nem Qwen3 Coder, nem GPT-OSS, nem minha primeira tentativa com GLM 4.7 cloud e nem MiniMax v2.</p>
<p>Mas este novo GLM 4.7 Flash me deu esperanças. Ele não entrou em &ldquo;agentic loop&rdquo; como o Qwen3 - que sempre chega num ponto onde ele pára de tentar resolver e só repete &ldquo;já terminei&rdquo;, sem ter terminado.</p>
<p>Também não ficou crasheando a toa. Toda vez que parou o processamento, foi por falta de memória. E mesmo assim, mandando &ldquo;continuar&rdquo; via prompt no Crush, ele consegue pegar de onde parou e continuar.</p>
<p>Cada correção que ele faz tem sentido. Não vi fazendo tangentes fora do escopo como vi o Gemini fazendo muitas vezes. Quando tem dúvidas, consegue pedir um &ldquo;agentic fetch&rdquo; e ir pesquisar na web antes de continuar. Toda vez que faz correções ele sabe executar o comando de build pra ver se tem erros de compilação.</p>
<p>Gostei que tenta corrigir um pequeno erro de cada vez em vez de tentar ficar reescrevendo tudo ou resolvendo mais de um problema ao mesmo tempo.</p>
<blockquote>
  <p>No final, foi o único open source que fez o build funcionar e gerar um binário.</p>

</blockquote>
<p>Não dava pra fazer ele testar o programa porque ele precisaria carregar outra LLM numa GPU que já estava com LLM, então eu parei o teste sem resolver bugs de runtime. E também não pedi pra ele refatorar porque já tinha levado tempo demais. Ele já tinha superado os outros OSS então parei por aqui. Eis o <a href="https://github.com/akitaonrails/qwen-cli-zig/pull/6"target="_blank" rel="noopener">pull request</a> com as correções de build que ele fez. Simples, sem mexer onde não precisa e faz o que foi pedido.</p>
<p>Ou seja, dos open source, foi o que vi o melhor comportamento, comparável ao que vi o Claude Opus ou GPT 5.2 fazendo. Considerando que esses modelos comerciais tem trilhões de parâmetros, rodando nos servidores mais parrudos com NVIDIA H200, eu considero que esse GLM, de só 30 bilhões de parâmetros, rodando numa mísera 5090 local, está se saindo impressionantemente bem.</p>
<p>Os trade-offs são claros: ele é ordens de grandeza mais lento que um Claude Opus. Mas é melhor um modelo lento que chega no mesmo resultado, do que um rápido que entra em loop infinito achando que já resolveu tudo sem ter feito nada, como o Qwen3.</p>
<p>Acho que pra desafios como o meu: prompt gigante, que exige muito contexto pra conseguir resolver, não é bom pra nenhum modelo open source. Mas se eu fosse começar um projetinho pequeno do zero, com tarefas simples e curtas, imagino que o GLM 4.7 resolveria sem nenhuma dor de cabeça, custando zero e preservando minha privacidade ao rodar 100% local e offline.</p>
<p>Recomendo: GLM 4.7 Flash é o melhor modelo OSS do começo de 2026.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>agentes-de-codigo</category></item><item><title>Dissecando um Modelfile de Ollama - Ajustando Qwen3 pra código</title><link>https://akitaonrails.github.io/2025/04/29/dissecando-um-modelfile-de-ollama-ajustando-qwen3-pra-codigo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/29/dissecando-um-modelfile-de-ollama-ajustando-qwen3-pra-codigo/</guid><pubDate>Tue, 29 Apr 2025 02:30:00 -0300</pubDate><description>&lt;p&gt;A notícia quente do dia é o lançamento do novo modelo Qwen3. Eu mesmo &lt;a href="https://www.akitaonrails.com/2025/04/28/testando-o-recem-lancado-llm-open-source-qwen3-com-aider-e-ollama"target="_blank" rel="noopener"&gt;postei a respeito hoje&lt;/a&gt; E já fiquei bem impressionado. Achei que ia deixar por isso mesmo, mas aí vi este tweet:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1jgq3ar5c5l7wfjt9wjfui8l598z" alt="ivan qwen tweaks" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Veja o &lt;a href="https://x.com/ivanfioravanti/status/1916934241281061156"target="_blank" rel="noopener"&gt;texto completo neste link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Mas ele dá dicas de como &amp;ldquo;tunar&amp;rdquo; esse novo modelo, mais pra questões matemáticas e fiquei pensando se tem como tunar pra desenvolvimento de software. Antes que pesquisadores mais avançados me xinguem: essa é minha primeira vez mexendo nos parâmetros de uma LLM, então se quiserem adicionar contexto nos comentários, sejam bem vindos.&lt;/p&gt;</description><content:encoded><![CDATA[<p>A notícia quente do dia é o lançamento do novo modelo Qwen3. Eu mesmo <a href="https://www.akitaonrails.com/2025/04/28/testando-o-recem-lancado-llm-open-source-qwen3-com-aider-e-ollama"target="_blank" rel="noopener">postei a respeito hoje</a> E já fiquei bem impressionado. Achei que ia deixar por isso mesmo, mas aí vi este tweet:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/1jgq3ar5c5l7wfjt9wjfui8l598z" alt="ivan qwen tweaks"  loading="lazy" /></p>
<p>Veja o <a href="https://x.com/ivanfioravanti/status/1916934241281061156"target="_blank" rel="noopener">texto completo neste link</a></p>
<p>Mas ele dá dicas de como &ldquo;tunar&rdquo; esse novo modelo, mais pra questões matemáticas e fiquei pensando se tem como tunar pra desenvolvimento de software. Antes que pesquisadores mais avançados me xinguem: essa é minha primeira vez mexendo nos parâmetros de uma LLM, então se quiserem adicionar contexto nos comentários, sejam bem vindos.</p>
<p>Vamos lá. Primeiro, o que são esses TopP, TopK e tudo mais que o Ivan fala no tweet dele? São parâmetros de <strong>KEY SAMPLING</strong> (sorteio de chaves). É um grupo de parâmetros que controlam <em>COMO</em> o modelo seleciona o <em>PRÓXIMO TOKEN</em> e vai completando o texto (dando a &ldquo;resposta&rdquo;), a partir do seu vocabulário baseado em distribuição de probabilidade que ele calcula. Os mais comuns são justamente <code>temperature</code>, <code>top_p</code>, <code>top_k</code> e algumas vezes <code>min_p</code> ou <code>repeat_penalty</code>.</p>
<p>Eu sempre falo: LLM é um gerador de próxima palavra, um completador de texto baseado em probabilidades. Quando a LLM processa seu input (seu contexto, histórico do chat e a próxima pergunta), ele precisa calcular o &ldquo;score&rdquo; de probabilidade pra cada palavra ou sub-palavra (token) no seu vocabulário inteiro. Imagina um modelo gigante, ele precisa recalcular pra tudo o tempo todo, pra cada próxima palavra.</p>
<p>Parâmetros de sampling dizem pro modelo &ldquo;como escolher o próximo token&rdquo; da lista de probabilidades. Sem esses parâmetros o modelo escolheria um decodificador &ldquo;greedy&rdquo; (ambicioso demais). Significa que ele ia só pegar o token com a maior probabilidade toda vez. E isso leva a um resultado repetitivo e que soa pouco natural. Sampling adiciona um tipo de &ldquo;aleatoriedade controlada&rdquo; (lembram? eu sempre falo que tem aleatoriedade misturado, nunca a resposta é &ldquo;certa&rdquo;), e isso torna a resposta mais &ldquo;interessante&rdquo; pra um ser humano (facilmente enganável). Eis alguns dos parâmetros:</p>
<h3>Temperatura<span class="hx:absolute hx:-mt-20" id="temperatura"></span>
    <a href="#temperatura" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Controla a aleatoriedade, que muitos chamam de &ldquo;criatividade&rdquo;. Essencialmente torna a distribuição mais quente (mais espalhada e mais plana) ou mais fria (com mais picos ao redor dos mesmos tokens). Quanto maior a temperatura (até 1.0) isso aumenta a probabilidade de escoher tokens que tinham probabilidades calculadas menores. Quanto menor a temperatura (até 0.0) menores as chances dos tokens calculados com menor probabilidade, mais &ldquo;greedy&rdquo; vai ser o resultado (menos &ldquo;humano&rdquo;).</p>
<p>Pra código, deixe muito alto e aí que surgem &ldquo;alucinações&rdquo; (que é basicamente aleatoriedade aumentada) e código que não faz sentido. Se for zero, vai ser muito &ldquo;boilerplate&rdquo;, repetitivo, usando padrões muito comuns e pode potencialmente acabar num loop infinito sem resposta (porque ele não consegue puxar nada diferente).</p>
<p>O tweet sugere 0.6 que é moderado. Mas nos meus testes, temperatura tão alta começou já a me dar uns códigos esquisitos. Do nada ele mudava nome de variável. Do nada aparecia um caracter chinês no meio do código (literalmente). Eu acho que 0 é muito baixo, mas 0.1 é suficiente, talvez 0.2.</p>
<p>Isso você pode mudar no arquivo <code>~/.aider.model.settings.yaml</code> antes de carregar o modelo (esses parâmetros podem ser reajustados na inicialização):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ollama_chat/qwen3-dev:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extra_params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">num_ctx</span><span class="p">:</span><span class="w"> </span><span class="m">40960</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>top_p (Nucleus Sampling)<span class="hx:absolute hx:-mt-20" id="top_p-nucleus-sampling"></span>
    <a href="#top_p-nucleus-sampling" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Aqui vai complicando. Esse <code>top_p</code> serve pra selecionar tokens do menor conjunto de tokens cuja probabilidade cumulativa exceda esse limite &ldquo;p&rdquo;. O modelo então puxa samples somente desse conjunto reduzido.</p>
<p>Esse modelo ordena todos os tokens por suas probabilidades em ordem descendente (do maior pro menor). Então começa a adicionar suas probabilidades até a soma ser maior ou igual a <code>top_p</code>. Todos os tokens incluídos na soma, formam o tal &ldquo;nucleus&rdquo; e desse conjunto ele sorteia um próximo token. Então é um sorteio dos tokens com sum(x) maiores probabilidades, mais ou menos. &ldquo;Escolha um dos vencedores, mas não necessariamente O vencedor (que seria greedy)&rdquo;.</p>
<h3>top_k<span class="hx:absolute hx:-mt-20" id="top_k"></span>
    <a href="#top_k" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Considere somente os &ldquo;k&rdquo; tokens mais prováveis. O modelo só sorteia (sample) desse conjunto reduzido dos top K maiores probabilidades de tokens. Isso é realmente dizer &ldquo;escolha um dentre os Top 10 melhores&rdquo;. Quanto maior o K maior a diversidade, quanto menor mais greedy.</p>
<p>E isso é relativo, top_k e top_p agem em conjunto. Essa configuração ajuda a ancorar a seleção aos tokens &ldquo;mais prováveis&rdquo; enquanto ainda permite ao top_p filtrar e tirar os de menor probabilidade. Isso prioriza plausibilidade (top_p) enquanto restringe o número total de tokens sorteados (top_k). Ajuda a manter o resultado focado nas respostas mais possivelmente &ldquo;certas&rdquo; (mas de novo, é um sorteio/sampling).</p>
<h3>min_p<span class="hx:absolute hx:-mt-20" id="min_p"></span>
    <a href="#min_p" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Filtra e tira tokens cuja probabilidade são menores que &ldquo;min_p&rdquo; vezes a probabilidade do token mais provável. Antes do sorteio, o modelo identifica o token com maior probabilidade (P_max), então remove qualquer token com probabilidade (P_token) que é menor que <code>P_max * min_p</code>. O sorteio então acontece a partir da lista que sobra (que vai ser mais filtrada por top_p e top_k).</p>
<p>Isso previne que o modelo escolha tokens que são &ldquo;extremamente improváveis&rdquo; relativo à escolha mais óbvia.</p>
<h3>repeat_penalty<span class="hx:absolute hx:-mt-20" id="repeat_penalty"></span>
    <a href="#repeat_penalty" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Isso desencoraja o modelo de repetir os mesmos tokens que já apareceram recentemente no histórico da conversa ou texto gerado.</p>
<p>Antes do sorteio, a pontuação de probabilidade dos tokens que já foram vistos recentemente são reduzidas, dividindo por essa <code>repeat_penalty</code> (com valores maiores que 1, já que dividir por 1 é ele mesmo, então mantém a mesma probabilidade).</p>
<p>Se deixa 1.0, pode começar a repetir as mesmas respostas ou os mesmos códigos, até os errados. Se for alto demais, muito acima de 1.0, a resposta pode ser pouco natural, já que às vezes precisa mesmo repetir alguma coisa pra re-explicar. Um valor moderado lá pelo 1.1 a menos de 1.2 pode ser mais adequado pra não punir demais o que já está no histórico.</p>
<h3>num_predict (max tokens pra gerar)<span class="hx:absolute hx:-mt-20" id="num_predict-max-tokens-pra-gerar"></span>
    <a href="#num_predict-max-tokens-pra-gerar" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O absoluto máximo de tokens que o modelo vai gerar de resposta. Se passar, vai truncar. Se for um número pequeno demais, especialmente em resposta de código - que pode ser longo - pode truncar demais e deixar resposta incompleta. Se for grande demais vai aumentar muito o processamento e vai exigir muito mais recursos da máquina e mais tempo e pode ser um desperdício.</p>
<p>Um valor de uns 32 mil tokens tem bastante espaço pra tokens. Tem que testar, e varia caso a caso pro tipo de resposta que você espera e o que cada modelo suporta.</p>
<h3>Recomendado<span class="hx:absolute hx:-mt-20" id="recomendado"></span>
    <a href="#recomendado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sabendo tudo isso agora você também consegue ler o tweet do Ivan e entender. Ele  recomenda Temp 0.6, TopP 0.95, TopK 20, MinP 0.</p>
<p>Eu mexi mais um pouco. E eu acho que pra código, Temp tem que ser na faixa de 0.1 ou 0.2. Também acho que vale um &ldquo;repeat_penalty&rdquo; de 1.1 pra evitar repetições desnecessárias, especialmente de código. De novo, tem que testar. São números empíricos e não exatos.</p>
<p>Eu criei um <a href="https://github.com/akitaonrails/qwen3-dev"target="_blank" rel="noopener">projeto no meu GitHub</a> com um arquivo completo de modelo que implementa não só esses parâmetros mas já pré-configura um prompt de sistema pra enviesar o modelo a ser um assistente de código primeiro. Eis meu prompt:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>SYSTEM &#34;&#34;&#34;
You are a highly functional AI assistant specializing in software development tasks.
Respond to queries about coding, algorithms, debugging, system design,
and programming languages/frameworks.
Provide clear, accurate, step-by-step reasoning and functional code examples.
Always format code snippets using markdown triple backticks (```language ... ```).
Aim for comprehensive and correct technical assistance.
Never suggest changing code unrelated to the question topic at hand.
Restrict yourself to the minimum amount of changes that do resolve the problem at hand.
Avoid renaming functions or variables unless absolutely necessary, 
and in doing so make sure if there&#39;s anyone calling the old names, 
for them to be renamed to the new names. Never leave the code in a broken state. 
Pay close attention to correctness, not just answering the quickiest and dirtiest code to solve the problem.
&#34;&#34;&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra fazer esse arquivo, primeiro eu puxei o Modelfile original do Qwen3 com este comando:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ollama show qwen3 --modelfile &gt; Modelfile</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É importante fazer isso porque, pelo que entendi, modelfiles não herdam do anterior. Não é que nem um &ldquo;FROM&rdquo; de um Dockerfile, onde você continua uma nova imagem a partir da base anterior.</p>
<p>Depois de editado com meus ajustes - que já está no meu GitHub, é só criar um novo modelo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ollama create qwen3-dev -f qwen3-dev.modelfile</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E ele vai aparecer no seu ollama local. Veja com <code>ollama list</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ ollama list
NAME                                            ID              SIZE      MODIFIED
qwen3-dev:latest                                fe03dfae5484    9.3 GB    About an hour ago
qwen3:32b                                       e1c9f234c6eb    20 GB     2 hours ago
qwen3:14b                                       7d7da67570e2    9.3 GB    8 hours ago
qwen3:latest                                    e4b5fd7f8af0    5.2 GB    8 hours ago
qwen2.5-coder:7b-instruct                       2b0496514337    4.7 GB    25 hours ago
....</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Viu o <code>qwen3-dev:latest</code>? É nosso novo modelo com ajustes de parâmetros. No aider é só usar ele direto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --watch-files --model ollama_chat/qwen3-dev:latest</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Lembrando de ajustar o <code>.aider.model.settings.yml</code> como já mostrei acima.</p>
<p>Eu não fiz testes longos ainda, mas resolvi brincar com o modelo 14b como base e esses ajustes. O modelo base já me impressionou e pelo menos meus ajustes não quebraram nada. Fiquei fazendo os mesmos refatoramentos e testes unitários no meu projetinho educativo e subi mais um <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/6"target="_blank" rel="noopener">pull request</a></p>
<p>Eu acho que o Gemini 2.5 Pro Exp é um pouco melhor, mas de novo, não por uma margem muito grande. E em alguns casos (como na refatoração), eu acho que prefiro o código gerado pelo Qwen3. Compare com o código que consegui com o Gemini, neste <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/3"target="_blank" rel="noopener">outro pull request</a></p>
<p>Com o Gemini eu precisei explicar menos e ele deu menos erros, mas ambos conseguiram fazer o que eu pedi até o fim e sem quebrar o programa principal. Mas o Qwen3 me parece bom o suficiente pra eu querer usar ele. Com a vantagem que mesmo sendo o modelo médio, de 14 bilhões de parâmetros (que roda na minha VRAM de 24GB com sobra), ele ainda dá resultados muito bons e limpos de código. E melhor: roda MUITO, mas MUITO mais rápido que o Qwen2.5 32b.</p>
<p>Pra quem tem GPUs com menos VRAM, digamos, uma 3070 ou 4070 de 16GB, dá pra usar o modelo menor ainda, de 7b. Eu testei o qwen2.5-coder:7b e tive resultados excelentes, então imagino que o Qwen3 de 7b deve ser ainda melhor e muito mais rápido. Velocidade que é comparável ao Claude ou Gemini, que são muito maiores e rodam em máquina muito mais parruda que uma RTX 4070.</p>
<p>Rodando meu qwen3-dev, no meu monitoramento minha CPU ficava maior parte do tempo em idle e a GPU o tempo todo acima de 90%, consumindo mais quase 400W constantemente, mantendo temperatura de 73 graus (o que é bom).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jv8ub1bc6m70u1agm1nyiooav2mk" alt="btop"  loading="lazy" /></p>
<p>Tive até um pequeno incidente onde meu PC desligou do nada, porque a extensão temporária que eu estava usando não aguentou e morreu. Somado com a CPU e meus outros dispositivos ligados na mesma tomada, estava puxando consistentemente 1000W da parede por horas. Brincar de I.A. usa <strong>MUITA</strong> energia, com ar-condicionado ligado porque só pra dissipar tudo isso, meu apartamento fica 1 a 2 graus (que eu chuto) acima do normal. Dá pra sentir mais quentinho mesmo kkkk</p>
<p>Mas é isso, espero que tenham entendido como que as coisas funcionam de verdade por baixo dos panos. Não tem mágica: é geração de próxima palavra, baseada em sorteio semi-aleatório de probabilidades.</p>
<p>Now you know.</p>
]]></content:encoded><category>llms</category><category>modelos-locais</category><category>tutoriais</category></item><item><title>Seu Próprio Co-Pilot Gratuito Universal que funciona Local: AIDER-OLLAMA-QWEN</title><link>https://akitaonrails.github.io/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen/</guid><pubDate>Fri, 25 Apr 2025 13:40:00 -0300</pubDate><description>&lt;p&gt;No meu &lt;a href="https://www.akitaonrails.com/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; eu mostro como fazer um chat LLM do zero com capacidade pra carregar arquivos de código pra refatoração. Eu demonstro os princípios por trás de ferramentas como Co-Pilot, Cursor ou Windsurf. O resumo é simples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UM BOM PROMPT DE REGRAS&lt;/li&gt;
&lt;li&gt;SCRIPTS que rodam localmente e adicionam mais contexto na sessão do chat.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;É basicamente &amp;ldquo;só&amp;rdquo; isso (claro, mesmo o princípio sendo simples, ainda dá bastante trabalho implementar mesmo). E eu não preciso fazer tudo do zero. Já existe uma alternativa open source que faz exatamente tudo isso, a ferramenta que ficou mais popular nos últimos meses pra desenvolvimento de software, o &lt;a href="https://aider.chat/docs/install.html"target="_blank" rel="noopener"&gt;&lt;strong&gt;AIDER&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>No meu <a href="https://www.akitaonrails.com/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local"target="_blank" rel="noopener">post anterior</a> eu mostro como fazer um chat LLM do zero com capacidade pra carregar arquivos de código pra refatoração. Eu demonstro os princípios por trás de ferramentas como Co-Pilot, Cursor ou Windsurf. O resumo é simples:</p>
<ul>
<li>UM BOM PROMPT DE REGRAS</li>
<li>SCRIPTS que rodam localmente e adicionam mais contexto na sessão do chat.</li>
</ul>
<p>É basicamente &ldquo;só&rdquo; isso (claro, mesmo o princípio sendo simples, ainda dá bastante trabalho implementar mesmo). E eu não preciso fazer tudo do zero. Já existe uma alternativa open source que faz exatamente tudo isso, a ferramenta que ficou mais popular nos últimos meses pra desenvolvimento de software, o <a href="https://aider.chat/docs/install.html"target="_blank" rel="noopener"><strong>AIDER</strong></a></p>
<p>O que eu gosto no AIDER:</p>
<ul>
<li>funciona com praticamente qualquer LLM, fechada ou aberta, graças ao uso do <a href="https://www.litellm.ai/"target="_blank" rel="noopener"><strong>LiteLLM</strong></a> por baixo, que abstrai e organiza toda configuração como tamanho de janela de contexto, temperatura e outros parâmetros sabidos que funciona melhor em cada LLM.</li>
<li>é OPEN SOURCE, então além de gratuito você pode aprender mais lendo o código-fonte, como eu fiz.</li>
<li>não precisa de plugins especiais - e proprietários - pra instalar em cada IDE: ele tem recurso de &ldquo;watch files&rdquo;. Então posso deixar aberto num painel do meu terminal e meu editor favorito no outro painel: o NeoVIM. Dá pra integrar com plugins, mas é opcional. Vai funcionar pra tudo, até pro Notepad, se você for masoquista.</li>
</ul>
<p>Eu tentei um pouco da porcaria de &ldquo;vibe coding&rdquo; somente via interface web do ChatGPT, Gemini, Claude, e vou dizer que todos são uma porcaria. Pra fazer um projetinho que é simples, com não mais que uns 4 arquivos curtos ele deu MUITO TRABALHO. Alguns deles:</p>
<ul>
<li>não demora muito pra começar a misturar conteúdo de um arquivo em outro.</li>
<li>erros que ele cometeu, eu expliquei que estava errado, ele corrigiu, mas logo em seguida repete de novo.</li>
<li>ele não roda nada, então ele chuta muita coisa, como paths de arquivos, versões de bibliotecas</li>
<li>não interessa que o contexto é grande e dá pra subir muitos arquivos pra contexto, é sliding window: ele não dá atenção pra tudo ao mesmo tempo. quanto mais arquivo de código você sobe, mais degrada a qualidade</li>
<li>tem que escrever muito pra explicar o que se quer e, principalmente, porque ele errou e como corrigir.</li>
<li>os créditos são caros, acabam muito rápido, porque se desperdiça metade dos tokens só explicando os erros e subindo os mesmos arquivos mais de uma vez porque você percebe que ele já &ldquo;esqueceu&rdquo;. Eu cheguei no limite do Claude, do Gemini, super rápido (eu pago o primeiro plano pago, não vou pagar o mais caro). Daí tem que esperar algumas horas pra voltar (péssimo).</li>
</ul>
<p>Tudo isso alguém vai comentar <em>&ldquo;Ah, mas pra mim funcionou.&rdquo;</em> Foda-se, não perguntei. Eu disse que PRA MIM foi assim, só aceita. O importante é dizer que aquela história de &ldquo;virtual employee&rdquo; ou &ldquo;substituir totalmente programador&rdquo; ou &ldquo;conseguir fazer um projeto inteiro sem saber programar nada&rdquo; é <strong>BALELA</strong>, <strong>HISTÓRIA DA CAROCHINHA</strong>, <strong>CONTO DE VIGARISTA</strong>, <strong>ILUSÃO DE AMADOR</strong>, etc.</p>
<p>Todas as LLMs são <strong>SÓ MAIS UMA FERRAMENTA</strong> que alguém que realmente entende, como nós programadores, vamos saber usar muito melhor do que qualquer &ldquo;empreendedor amador&rdquo; jamais vai conseguir. E eu vou provar.</p>
<h3>Aider Básico<span class="hx:absolute hx:-mt-20" id="aider-básico"></span>
    <a href="#aider-b%c3%a1sico" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Instalar o Aider é fácil, só precisa ter Python &gt;= 3.12 na sua máquina, coisa que todo Linux moderno tem. A documentação deles é muito boa, <a href="https://aider.chat/docs/install.html"target="_blank" rel="noopener">recomendo ler</a> pra saber o que ele recomenda pós-instalação. Tem muitas dicas.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>python -m pip install aider-install
aider-install</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que ele não diz na documentação é que a primeira coisa pra fazer é baixar <a href="https://github.com/Aider-AI/aider/blob/main/aider/website/assets/sample.aider.conf.yml"target="_blank" rel="noopener">este arquivo de exemplo</a> e colocar em <code>$HOME/.aider.conf.yml</code>. Nele tem uma coisa importante que temos que mexer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c">## Enable/disable auto commit of LLM changes (default: True)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">auto-commits</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Recomendo que leiam esse arquivo, ele está todo comentado e pode ter opções que você queira desligar, mas esse é irritante, porque - por padrão - essa desgraça faz &ldquo;COMMIT AUTOMÁTICO&rdquo; no seu Git, toda vez que ele muda alguma coisa no seu código. Nem me dá chance de revisar. Vai na base do &ldquo;CUNFIA IN NÓIS&rdquo;. Nem fu, desligado.</p>
<p>Esse é o tipo de coisa que um amador não faria, e até entendo. Amadores não sabem organizar commits de git de qualquer jeito, provavelmente as mensagens automáticas do Aider vão ser menos piores. Olha como fica:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/22ji8rp2bbw5lsxwbmpneh5clhcr" alt="Aider Git commit"  loading="lazy" /></p>
<p>Aviso pra júnior: esse é o tipo de coisa que se eu fosse avaliar, ia ganhar ponto negativo se eu visse sendo feito assim. Faça direito: re-cheque as mudanças e organize em commits de verdade. O Aider &ldquo;diz&rdquo; que fez refactor, mas nem sempre ele acerta, mas mesmo assim faz o commit, e aí sobe errado. Se for pra subir tudo automático, mesmo com erro, realmente, não preciso de você.</p>
<p>Mas estou me adiantando, depois de instalar o Aider, precisa configurar as API KEYS de cada serviço que você usa, seja da OpenAI, Claude ou outros. Quem usa ChatGPT no Linux já tem uma variável como <code>export OPENAI_API_KEY=sk-proj-........</code> configurado no <code>.bashrc</code> ou <code>.zshrc</code>.</p>
<p>Feito isso, dá pra escolher qual modelo usar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Change directory into your codebase
cd /to/your/project

# o3-mini
aider --model o3-mini

# o1-mini
aider --model o1-mini

# GPT-4o
aider --model gpt-4o

# List models available from OpenAI
aider --list-models openai/</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isso já vai abrir um chat interativo. Também dá pra, antes de abrir o chat, já mandar carregar arquivos locais assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider README.md init.py utils.py ...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou, de dentro do chat, ele aceita <a href="https://aider.chat/docs/usage/commands.html"target="_blank" rel="noopener">vários comandos</a> que começam com slash &ldquo;/&rdquo;, parecido com chat de IRC. Por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>/add helper.py
/copy # copia a última sugestão de código pro clipboard
/git # roda um comando de git local a partir do chat
/run # roda um comando do seu shell e adiciona o output no chat
/web # vai numa página web e puxa o conteúdo pro chat</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Leia a documentação. Mas o modo mais interessante é o &ldquo;watch-files&rdquo;. Basta estar no diretório do seu projeto e subir assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --watch-files</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele usa a biblioteca <a href="https://pypi.org/project/watchfiles/"target="_blank" rel="noopener">watchfiles</a> de Python pra interceptar chamadas de IO nesse diretório e dar gatilho pra carregar sozinho seus arquivos, assim que você salva no seu editor favorito (por isso falei que não precisa de plugin extra nenhum se não quiser). Já aviso que essa lib não suporta dar watch em mounts de SMB ou NFS (sim, eu testei).</p>
<p>No seu editor, basta criar um comentário perto do código que quer que ele mexa, assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>// can you refactor this return so it has more error checking? AI!
export const getCapitalizedLabel = (name: string): string =&gt; {
  return name
    .replace(/_/g, &#34; &#34;)
    .split(&#34; &#34;)
    .map((word: string) =&gt; word.charAt(0).toUpperCase() &#43; word.slice(1))
    .join(&#34; &#34;)
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Coloque no comentário o que quer que ele faça, e termine com o gatilho &ldquo;AI!&rdquo;. Aí no outro terminal que o Aider está carregado, ele vai perceber que o arquivo foi salvo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&gt;

Added src/utils/getCapitalizedLabel.ts to the chat
Processing your request...
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E vai fazer sua &ldquo;mágica&rdquo; (que eu vou contar o segredo já já). Este é o exemplo do que ele fez (mesmos resultados tanto com o4-mini quanto Qwen2.5, mas é um exemplo bem trivial):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">getCapitalizedLabel</span> <span class="o">=</span> <span class="p">(</span><span class="nx">name</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">name</span> <span class="o">!==</span> <span class="s1">&#39;string&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="k">new</span> <span class="nx">TypeError</span><span class="p">(</span><span class="s1">&#39;Expected a string&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">name</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/_/g</span><span class="p">,</span> <span class="s2">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">word</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="nx">word</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="nx">word</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">word</span><span class="p">.</span><span class="nx">charAt</span><span class="p">(</span><span class="mi">0</span><span class="p">).</span><span class="nx">toUpperCase</span><span class="p">()</span> <span class="o">+</span> <span class="nx">word</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s2">&#34; &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Um editor gráfico como Visual Studio Code da vida, assim que o Aider mudar o arquivo, no editor ele recarrega e você já vê as mudanças. Num NeoVim, o autoread costuma carregar automático também, mas às vezes eu preciso dar o comando &ldquo;:e&rdquo; pra forçar recarregar, mas nada de mais também. E só com isso já temos um workflow de trabalho eficiente.</p>
<p>Meu editor favorito funciona igualzinho, sem plugin deixando pesado nem conflitando com nada. Se eu precisar fazer pair programming, chamo o Aider num outro terminal e pronto, basta escrever idéias em comentários, e ele manda sugestões. Como desliguei o maldito auto-commit, se eu não gostar, basta dar UNDO ou <code>git checkout</code> no arquivo e volta como tava antes.</p>
<p>Mais do que isso. Se eu notar que o modelo <code>o4-mini</code> não tá dando bons resultados, posso mudar pro <code>--model gemini</code> - que costuma ser melhor pra código do que o o4. Ou <code>--model claude-3-opus-20240229</code>. Aliás, falando em Claude, como falei antes <a href="https://aider.chat/docs/llms/anthropic.html"target="_blank" rel="noopener">leia a fucking documentação</a></p>
<p>Fala na documentação que o Aider suporta &ldquo;thinking tokens&rdquo; do Sonnet 3.7. Existe um arquivo de configuração onde podemos fazer &ldquo;fine-tuning&rdquo; pra cada modelo, o <code>$HOME/.aider.model.settings.yml</code>onde podemos adicionar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">anthropic/claude-3-7-sonnet-20250219</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">edit_format</span><span class="p">:</span><span class="w"> </span><span class="l">diff</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">weak_model_name</span><span class="p">:</span><span class="w"> </span><span class="l">anthropic/claude-3-5-haiku-20241022</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">use_repo_map</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">examples_as_sys_msg</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">use_temperature</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extra_params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">extra_headers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">anthropic-beta</span><span class="p">:</span><span class="w"> </span><span class="l">prompt-caching-2024-07-31,pdfs-2024-09-25,output-128k-2025-02-19</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max_tokens</span><span class="p">:</span><span class="w"> </span><span class="m">64000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">thinking</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">type</span><span class="p">:</span><span class="w"> </span><span class="l">enabled</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">budget_tokens</span><span class="p">:</span><span class="w"> </span><span class="m">32000</span><span class="w"> </span><span class="c"># Adjust this number</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">cache_control</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">editor_model_name</span><span class="p">:</span><span class="w"> </span><span class="l">anthropic/claude-3-7-sonnet-20250219</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">editor_edit_format</span><span class="p">:</span><span class="w"> </span><span class="l">editor-diff</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<h3>Reasoning<span class="hx:absolute hx:-mt-20" id="reasoning"></span>
    <a href="#reasoning" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Thinking ou Reasoning como é chamado no marketing da maioria das LLMs comerciais é o pattern de &ldquo;chain-of-thought&rdquo;, que é uma técnica de prompt onde você pede pra LLM não dar uma resposta, mas sim descrever &ldquo;passo a passo&rdquo; como ele chegaria na resposta, e depois repetir a pergunta mandando ele seguir esses passos e daí dando uma resposta.</p>
<p>Na prática tem várias formas de implementar algo parecido com isso. E uma delas é ser Multi Modelo. Especialmente com modelos open source, como DeepSeek-Coder ou Qwen2.5-Coder ou Llama3. Alguns modelos são melhores no reasoning - em explicar verbalmente o que tem que ser feito, mas são ruins em fazer o código em si. E alguns modelos não são bons em explicar as coisas em detalhes, mas foram treinados pra escrever bom código.</p>
<p>É possível usar isso a nosso favor e o Aider tem um recurso que eu acho muito foda - pra casos onde realmente precisa, porque é pesado - que é rodar em <strong>modo arquiteto</strong>, gerar o raciocíniio e depois mudar pra <strong>modo código</strong> em outro modelo, e usar esse raciocínio pra montar um código melhor.</p>
<p>Tem um <a href="https://aider.chat/2024/09/26/architect.html"target="_blank" rel="noopener">artigo inteiro</a> na documentação do Aider explicando isso, mas a motivação foi por causa do modelo OpenAI o1, que é justamente forte pra raciocinar uma explicação do que fazer, mas é ruim em realmente fazer o código. Então, vale a pena ouvir o o1 e deixar ele analisar o problema e descrever em forma de texto, e depois mudar pro Gemini Pro ou Exp e mandar ele escrever o código.</p>
<p>Na prática, basta subir o Aider primeiro em modo &ldquo;Architect&rdquo;:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>pip install -U aider-chat

# Change directory into a git repo
cd /to/your/git/repo

# Work with Claude 3.5 Sonnet as the Architect and Editor
export ANTHROPIC_API_KEY=your-key-goes-here
aider --sonnet --architect</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isso funciona com outros models:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code># Work with OpenAI models, using gpt-4o as the Editor
export OPENAI_API_KEY=your-key-goes-here
aider --4o --architect
aider --o1-mini --architect
aider --o1-preview --architect</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como exemplo, configurando <code>OPENAI_API_KEY</code> e <code>GEMINI_API_KEY</code> posso subir o o4 como arquiteto e o Gemini como o coder:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ aider --watch-files --architect --editor-model gemini
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Using gpt-4o model with API key from environment.
Aider v0.82.3.dev55&#43;g25a30393
Main model: gpt-4o with architect edit format
Editor model: gemini/gemini-2.5-pro-preview-03-25 with editor-diff edit format
Weak model: gpt-4o-mini
Git repo: .git with 184 files
Repo-map: using 4096 tokens, auto refresh
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
architect&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja no status do início que ele aceitou os dois. Agora é só codar do mesmo jeito que antes, mas usando os dois. Obviamente, assim você vai usar muito mais créditos! Fique esperto com isso! Tem <strong>chances</strong> de ter resultados melhores mas <strong>certamente</strong> vai gastar mais créditos, por isso use mais pra códigos mais complexos, onde só um modelo está sofrendo pra conseguir resolver.</p>
<p>Note que existem configurações e limites de tokens, e também note que ao final de cada resposta, o Aider dá uma estimativa de quanto está te custando. Eis primeiro a conta do arquiteto OpenAI o4:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
This should make the function more concise and efficient.


Tokens: 4.4k sent, 246 received. Cost: $0.01 message, $0.01 session.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E eis a SEGUNDA conta do Coder Gemini 2.0 Flash:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
Tokens: 1.9k sent, 136 received. Cost: $0.0037 message, $0.02 session.
Applied edit to src/utils/getCapitalizedLabel.ts</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Gemini Free (gratuito) tem um &ldquo;rate limit&rdquo; (quantidade de requisições permitidas por minuto, super baixa, só 15 RPM, pra API é ridículo, o Aider bate nisso na primeira tentativa).</p>
<p>Não raras as vezes, você vai bater no limite do plano, e isso é um saco:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>The API provider has rate limited you. Try again later or check your quotas.
Retrying in 4.0 seconds...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí não tem jeito, tem que ir no site <a href="">Google AI Studio</a> configurar sua conta de cobrança, adicionar seu cartão e subir pra um plano pago um pouco melhor como o 2.0 Flash, que suporta 2.000 RPM e 4M TPM e o preço não é caro:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ozbz5tvmpyla0huinqa6da2iwb1m" alt="Gemini 2.0 Flash"  loading="lazy" /></p>
<p>Alguns dizem que hoje o Gemini 2.0 tem oferecido resultados de código similares ou superiores ao Claude Sonnet 3.7. Varia de caso a caso, não é nunca uma medida absoluta, mas na minha pequena experiência, ambos são muito bons e eu vejo ambos sendo superiores mesmo ao ChatGPT 4 ou o4. Mas a OpenAI, pra mim, tem mesmo sido melhor em respostas &ldquo;verbais&rdquo;, então a estratégia de separar o papel de &ldquo;arquiteto&rdquo; pra OpenAi e de &ldquo;coder&rdquo; pra Gemini ou Claude, faz MUITO sentido.</p>
<p>No final, aquele código inicial que pedi pra refatorar, nessa combinação de arquiteto/editor ficou assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="kr">const</span> <span class="nx">getCapitalizedLabel</span> <span class="o">=</span> <span class="p">(</span><span class="nx">name</span><span class="o">:</span> <span class="nx">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">string</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">name</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/_/g</span><span class="p">,</span> <span class="s1">&#39; &#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">replace</span><span class="p">(</span><span class="sr">/\b\w/g</span><span class="p">,</span> <span class="kr">char</span> <span class="p">=&gt;</span> <span class="kr">char</span><span class="p">.</span><span class="nx">toUpperCase</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ok, eu não teria pensado nisso desta forma.</p>
<h3>Não usando &ldquo;Créditos de Tokens&rdquo;: OLLAMA<span class="hx:absolute hx:-mt-20" id="não-usando-créditos-de-tokens-ollama"></span>
    <a href="#n%c3%a3o-usando-cr%c3%a9ditos-de-tokens-ollama" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Agora vem a parte mais interessante pra nós, nerds mais hard-core, com máquinas mais parrudas (no mínimo uma RTX 3090, eu uso uma RTX 4090 - o importante é ter <strong>24GB de VRAM</strong>). Dá pra usar modelos menores, que cabem em menos VRAM, mas aí o resultado do código vai ser bem pior, então é melhor ficar no Gemini ou Claude mesmo.</p>
<p>Se tiver a máquina pra isso, com CUDA Toolkit já instalado, é hora da solução de gente grande: instalar Ollama. No meu Manjaro/Arch é assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>yay -S ollama ollama_cuda</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Não deixe de checar configuração de CUDA porque por default o Ollama vai rodar silenciosamente na sua CPU e vai ser tudo ABSURDAMENTE LENTO.</p>
<p>O Ollama funciona tanto como um servidor local http pra APIs, quanto como um cliente de chat interativo, como o meu qwen_cli do post anterior. Ele funciona numa lógica mais ou menos parecida com Docker. Pode ser configurado pra subir como automaticamente como serviço no seu sistema. Veja a <a href="https://wiki.archlinux.org/title/Ollama"target="_blank" rel="noopener">documentação no ArchWiki</a>.</p>
<p>Eu prefiro subir manualmente num terminal, onde eu posso ver o log do que está acontecendo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>OLLAMA_FLASH_ATTENTION=1 OLLAMA_CONTEXT_LENGTH=8192 ollama serve</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem várias configurações com variáveis de ambiente. Dá pra só configurar no seu <code>/etc/profile</code> ou <code>~/.zshrc</code> mas como estou testando, prefiro subir direto na linha de comando. Com isso teremos um servidor web. Pra funcionar com o Aider, novamente, só declarar onde achar ele no seu <code>/.zshrc</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>export OLLAMA_API_BASE=http://127.0.0.1:11434</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, precisamos baixar algum modelo, como meu preferido Qwen 2.5 Coder, que já usei no post anterior:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>ollama pull qwen2.5-coder:32b</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note que estou explicitamente pedindo a versão 32B, mas você pode experimentar uma mais leve como a 7b. Cada modelo suportado pelo Ollama tem <a href="https://ollama.com/library/qwen2.5-coder"target="_blank" rel="noopener">uma página</a> onde descreve detalhes desse tipo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/q76sz7qyqupk8jv3gm1igpk4q0i3" alt="qwen 2.5 coder page"  loading="lazy" /></p>
<p>Dependendo de qual benchmark sintético você &ldquo;acreditar&rdquo; vai ver comparações como esta:</p>
<p><img src="https://ollama.com/assets/library/qwen2.5-coder/bbf378d8-c80e-4ae3-98ab-90111dfbf3e7" alt="comparação"  loading="lazy" /></p>
<p>Na prática, Claude, ChatGPT, Gemini, realmente são já muito bons pra código. No mundo open-source são o Qwen 2.5 Coder, Deepseek-Coder-V2 (que ainda não funciona muito bem no Aider, tem que esperar atualizações). Então, se quiser tudo de grátis, o melhor mesmo por enquanto é o Qwen 2.5 Coder mesmo. E na minha experiência, tem funcionado muito bem. Mas é BEM pesado na versão 32B e as respostas são consideravelmente mais lentas que uma opção paga como Gemini. Então depende muito do seu caso.</p>
<p>Uma vez baixado o modelo, precisa configurar janela de contexto (por padrão é bem pequena, só 2K tokens), <a href="https://aider.chat/docs/llms/ollama.html"target="_blank" rel="noopener">a documentação avisa</a> isso e tem que editar o arquivo <code>$HOME/.aider.model.settings.yml</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">ollama/qwen2.5-coder:32b</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extra_params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">num_ctx</span><span class="p">:</span><span class="w"> </span><span class="m">65536</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que a documentação <strong>NÃO</strong> explica e, por causa disso, eu gastei um tempão vasculhando o código-fonte a as Issues no GitHub é que pra subir modelos open source, precisa usar a opção <code>ollama_chat/</code> e não ollama como ele manda. Ou seja, a documentação diz:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --model ollama/qwen2.5-coder:32b</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas o CERTO é fazer:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --model ollama_chat/qwen2.5-coder:32b</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se não, as respostas vão ser completamente aleatórias, fora de contexto. Preste atenção nisso. Mas sabendo disso, podemos até fazer o que falei antes: misturar modelos! Que tal subir o GPT o4 como arquiteto e deixar ele comandar o editor sendo o Qwen 2.5?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --watch-files --architect --editor-model ollama_chat/qwen2.5-coder:32</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sim, isso é possível. Agora você pode usar um modelo comercial que está mais acostumado, como Gemini ou Claude, tanto nos papéis de arquiteto como editor, e um modelo open source como Qwen 2.5 pra suporte, e pagar menos. Ou de uma vez só usar o Qwen 2.5 e não pagar <strong>crédito nenhum</strong> (só eletricidade, claro, vai consumir <strong>+200W</strong> da parede toda vez, mas são só alguns segundos por vez).</p>
<p>Na minha (pouca) experiência. O Gemini ainda dá códigos melhores que o Qwen, mas não quer dizer que o Qwen seja ruim, como eu disse, vai depender MUITO das suas instruções de prompt (quanto mais detalhado melhor). E falando em instruções, vamos pra parte final:</p>
<h3>O Segredo da Mágica: Prompts<span class="hx:absolute hx:-mt-20" id="o-segredo-da-mágica-prompts"></span>
    <a href="#o-segredo-da-m%c3%a1gica-prompts" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu tiro sarro quando alguém fala em &ldquo;prompt engineering&rdquo;, mas na real, a melhor forma de tirar o máximo de uma LLM é fazer o melhor prompt possível. Não quer dizer o &ldquo;prompt mais longo&rdquo;. É qualidade e não quantidade.</p>
<p>Por isso tanto <a href="https://github.com/microsoft/generative-ai-for-beginners"target="_blank" rel="noopener">Microsoft</a> quanto <a href="https://www.kaggle.com/whitepaper-prompt-engineering"target="_blank" rel="noopener">Google</a> fizeram guias detalhados focaods em prompt. O deles compensa ler e estudar. O que não compensa é pagar cursos de gente aleatória falando &ldquo;prompt engineering&rdquo; como se entendesse alguma coisa.</p>
<p>O Aider suporta uma opção &ldquo;&ndash;verbose&rdquo;, onde ele mostra exatamente o que está mandando pras LLMs. Vamos testar. Logo de cara ele já imprime isso no console:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>❯ aider --watch-files --verbose
Config files search order, if no --config:
  - /tmp/smells/.aider.conf.yml
  - /home/akitaonrails/.aider.conf.yml (exists)
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Too soon to check version: 11.6 hours
Command Line Args:   --watch-files --verbose
Config File (/home/akitaonrails/.aider.conf.yml):
  max-chat-history-tokens:8192
  map-tokens:        4096
  auto-commits:      False
Defaults:
  --set-env:         []
  --api-key:         []
  --model-settings-file:.aider.model.settings.yml
  --model-metadata-file:.aider.model.metadata.json
  ...
  --encoding:        utf-8
  --line-endings:    platform
  --env-file:        /tmp/smells/.env

Option settings:
  - 35turbo: False
  - 4: False
  - 4_turbo: False
  ...
  - upgrade: False
  - user_input_color: #00cc00
  - verbose: True
  - verify_ssl: True
  - vim: False
  - voice_format: wav
  - voice_input_device: None
  - voice_language: en
  - watch_files: True
  - weak_model: None
  - yes_always: None
  ...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que ferramentas como Aider, Cursor, Co-Pilot e outros fazem é ter parâmetros otimizados de todos os LLMs já hard-coded na ferramenta, como nesse caso onde detecta pra usar o4 e já configura com melhores parâmetros (que você ainda pode tunar colocando em <code>$HOME/.aider.model.settings.yml</code>).</p>
<p>Mas quando você manda a primeira pergunta que fica interessante, olha isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rb4hxk4cn181fkkf841snl7ygfja" alt="Aiden Verbose 1"  loading="lazy" /></p>
<p><em>&ldquo;Aja como um desenvolvedor de software. Sempre use boas práticas quando codar. Respeite e use convenções, bibliotecas, etc que já estão presentes nesta base de código. Seja diligente e sem preguiça! Você NUNCA vai deixar comentários descrevendo código sem implementar! Você sempre vai IMPLEMENTAR COMPLETAMENTE o código necessário! &hellip;&rdquo;</em></p>
<p>Lembra o que eu falei no começo do post: PROMPTS DE REGRAS. Esse SYSTEM no começo é um &ldquo;role&rdquo;. Você como usuário é o &ldquo;USER&rdquo; e o modelo é o &ldquo;ASSISTANT&rdquo;, inclusive esse prompt de SYSTEM é gigante, ele vasculha meu projeto e já explica pro modelo a estrutura do meu projeto e inclusive resume alguns arquivos (mostrando só a interface dos métodos, por exemplo, pra dar contexto mas não subir código inteiro).</p>
<p>Tem trechos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
SYSTEM # *SEARCH/REPLACE block* Rules:
SYSTEM
SYSTEM Every *SEARCH/REPLACE block* must use this format:
SYSTEM 1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
SYSTEM 2. The opening fence and code language, eg: ```python
SYSTEM 3. The start of search block: &lt;&lt;&lt;&lt;&lt;&lt;&lt; SEARCH
SYSTEM 4. A contiguous chunk of lines to search for in the existing source code
SYSTEM 5. The dividing line: =======
SYSTEM 6. The lines to replace into the source code
SYSTEM 7. The end of the replace block: &gt;&gt;&gt;&gt;&gt;&gt;&gt; REPLACE
SYSTEM 8. The closing fence: ```
SYSTEM
SYSTEM Use the *FULL* file path, as shown to you by the user.
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Inclusive, se já usou Co-Pilot, sabe que dá pra colocar as convenções do seu projeto em <code>CONVENTIONS.MD</code>. O Aider também vai carregar esse arquivo no prompt. Então coisas como &ldquo;use 4 espaço em vez de tabs, coloque a abertura de {  no final da linha e não começando uma linha nova, etc&rdquo;, tudo isso fica nesse arquivo e tanto Co-Pilot como Aider adicionam essas regras no PROMPT. Não é nenhum outro mecanismo, é PROMPT.</p>
<p>Daí, como USER, ele começa a subir resumo dos meus arquivos no prompt:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
USER Here are summaries of some files present in my git repository.
USER Do not propose changes to these files, treat them as *read-only*.
USER If you need to edit any of these files, ask me to *add them to the chat* first.
USER
USER data_scraper/main.py:
USER ⋮
USER │def main(args: Arguments):
USER ⋮
USER
USER data_scraper/src/arguments.py:
USER ⋮
USER │@dataclass(frozen=True)
USER │class Arguments:
USER │    content_path: Path
USER ⋮
USER │    @staticmethod
USER │    def get() -&gt; &#39;Arguments&#39;:
USER ⋮
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando termina essa parte, o Aider pede confirmação do Modelo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
ASSISTANT Ok, I won&#39;t try and edit those files without asking first.
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que funciona um &ldquo;I.A.  pra software&rdquo;, com uma tonelada de prompts de instruções e um programa cliente que fica fazendo parse das respostas e ativa comandos de verdade que rodam no seu sistema (agentes), como comandos de git ou lint. O resultado desses comandos, o texto, é concatenado no chat pra adicionar ao contexto e daí pede pro modelo/assistente continuar analisando desse ponto em diante.</p>
<p>Um modelo sozinho não faz nada. Quem faz é a ferramenta que carrega o modelo, no caso o Aider, ou o Co-Pilot, ou o Cursor. O programa vem pré-carregado com vários perfis/personas/roles pré-escritos pra adicionar prompts ao modelo. No código-fonte do Aider, temos este exemplo de <a href="https://github.com/Aider-AI/aider/blob/main/aider/coders/architect_prompts.py"target="_blank" rel="noopener">architect_prompts.py</a></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/nlzkq16pcfjwwow5tbibocr1oa1n" alt="architect role"  loading="lazy" /></p>
<p>Olha só, na primeira variável <code>main_system</code> está aquele primeiro prompt que vimos no começo desta seção. Tem vários outros perfis pré-programados que é bom entender a diferença. Cada modelo funciona melhor com determinados tipos de instrução, com diferentes limitações.</p>
<p>Neste outro código <a href="https://github.com/Aider-AI/aider/blob/main/aider/commands.py"target="_blank" rel="noopener">commands.py</a> é onde temos declarado como o Aider consegue fazer coisas como <code>git commit</code> direto no seu projeto ou rodar o linter da sua linguagem:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bdt1a5dqj28bvzhqximdi8j2gx2g" alt="commands"  loading="lazy" /></p>
<p>Nada disso é &ldquo;mágica&rdquo;, é tudo pré-programado e ele - por segurança - só deve conseguir fazer comandos limitados e bem checados. Não queremos comandos que façam coisa demais fora do diretório do projeto (aliás, NUNCA, fora do diretório do projeto). Nem deveria poder rodar muitos executáveis. Nunca se sabe que bugs de segurança podem acontecer quando se roda comandos cegamente.</p>
<p>Se você é de cyber-segurança, é aqui que deveria auditar. Mas essa é a vantagem, o Aider sendo open source, é auditável. Ferramentas proprietárias e fechadas, não. É na base do &ldquo;CUNFIA NO PAI&rdquo;.</p>
<p>Teste o <a href="https://aider.chat/docs/install.html"target="_blank" rel="noopener">Aider</a> hoje e leia todo esse site de documentação. É curto e tem dicas importantes que podem ajudar casos especiais.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/0zeis5fjnkuf2qkmwy7pm6vc62gi" alt="Aider"  loading="lazy" /></p>
<h3>OpenRouter<span class="hx:absolute hx:-mt-20" id="openrouter"></span>
    <a href="#openrouter" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Como já devem ter percebido, o ideal é testar as várias LLMs comerciais porque elas evoluem e cada uma tem pontos fortes e fracos. Mas ficar pagando crédito em cada uma é um saco.</p>
<p>Pra isso existe o <a href="https://openrouter.ai/"target="_blank" rel="noopener">OpenRouter</a> onde você tem uma conta só, uma <code>OPEN_ROUTER_API_KEY</code> só e paga num lugar só ele distribui os créditos pra cada provider como OpenAI ou Claude à medida que você usa. Simplifica o gerenciamento dos seus custos de API, vale muito a pena e, claro, o Aider também sabe usar ele:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>aider --model openrouter/openai/gpt-4o</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Novamente, leiam <a href="https://aider.chat/docs/llms/openrouter.html"target="_blank" rel="noopener">a documentação</a>.</p>
<p>Agora, um <strong>ANTI-PATTERN</strong>. Eu pedi ao Aider, usando o modelo <code>openrouter/google/gemini-2.5-pro-preview-03-25</code>, que teoricamente é a melhor das melhores do momento, pra gerar um arquivo de testes unitários pro qwen_cli.py, do projetinho que eu soltei no GitHub no post de ontem.</p>
<p>Ele falhou miseravelmente. Criou um arquivo de testes, de quase 800 linhas, socado de mocks pra tudo que é canto, testando coisa que não precisa, complicando demais. Tudo bem. Eu tento rodar e solta erros.</p>
<p>Uma funcionalidade legal do Aider é poder rodar de dentro do chat com <code>/run python -m unittest ...</code> e ele oferece pra já jogar os erros dentro do chat. Eu peço pro Gemini consertar. Aí ele sai modificando o arquivo de testes e o arquivo do meu código. Mando <code>/run</code> de novo, mais erros. E eu fiz isso umas 4x. Ele sai &ldquo;consertando&rdquo; código que não precisa, e testes unitários simples (que era só um pequeno typo, só ajustar 1 número), ele larga pra trás. Eu descrevo no prompt <em>&ldquo;você está mexendo onde não precisa e não consertando coisa trivial, como esse númerozinho&rdquo;</em> mando <code>/run</code>, e ele insiste em ignorar e sai mexendo em outros lugares.</p>
<p>Resumo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/q4fiux9vj0d5l6e8p4nmku4rcjma" alt="OpenRouter Credits"  loading="lazy" /></p>
<p>Gastou quase 3 doletas e não resolveu o problema. Vantagem da OpenRouter é que fica tudo centralizado num lugar só. Ele pagou o Google pelo uso do Gemini e desconta dos meus créditos. Tinha pré-carregado com 100 créditos (mais ou menos 100 dólares). E em 30 minutos já foi embora 3 créditos. Faça as contas.</p>
<p><strong>Melhor prática:</strong> o melhor é abrir o código do qwen_cli.py, por exemplo, no meu NeoVIM ou qualquer editor e colocar um comentário só em cima de um único método:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># this method is used to just load a config.json file, create a single unit test and add to test_qwen_cli.py, AI!</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">load_config</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Daí ele faz somente <strong>UM</strong> teste unitário por vez, rodamos com <code>/run</code> e se passar e o código parecer limpo, seguimos pro próximo. Fazer um método de cada vez é muito mais garantido do que mandar <em>&ldquo;faz teste pra TUDO de uma só vez.&rdquo;</em> Aí é garantia que vamos gastar créditos à toa.</p>
<p>Além disso, um método por vez, modelos open source como Qwen 2.5 vão fazer bem e você roda local sem gastar créditos.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Por enquanto, vou adotar o Aider. Agora eu entendo porque ele ficou tão popular este ano. É um projetinho bem feito, aberto, que eu posso explorar, tentar melhorar e aprender mais. Ao mesmo tempo facilita o uso de LLMs comercias e abre opções pra eu usar modelos open source na minha própria máquina, dando melhor uso ao meu RTX 4090.</p>
<p>Programador ruim é quando alguém fala que teve trabalho no ChatGPT e ele fica dando resposta enlatada como &ldquo;já testou Claude, já testou Manus&rdquo; ou então &ldquo;ah, se tivesse usador Cursor seria melhor&rdquo;. Pára de ser esse NPC, é cansativo e demonstra absoluta PREGUIÇA.</p>
<p>Programador BOM é quando passa por uma jornada como a que acabei de descrever neste post: aprende os pontos fortes, os pontos fracos, como CONTROLAR, como SUBVERTER, como criar CONTINGÊNCIAS e, no final, sai com uma solução MELHOR do que de todo mundo.</p>
<p>É pra isso que virei programador, não pra ser outdoor grátis repetindo propaganda do Sam Altman.</p>
]]></content:encoded><category>agentes-de-codigo</category><category>modelos-locais</category><category>llms</category></item><item><title>Hello World de LLM: criando seu próprio chat de I.A. que roda local</title><link>https://akitaonrails.github.io/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local/</guid><pubDate>Fri, 25 Apr 2025 00:20:00 -0300</pubDate><description>&lt;p&gt;Ando brincando BASTANTE com os diversos derivados de GPT como o próprio ChatGPT, Claude Sonnet, Gemini e depois talvez eu faça um outro post sobre minha experiência, mas hoje eu queria brincar um pouco mais a fundo e fazer um experimento nível &amp;ldquo;Hello World&amp;rdquo; pra que vocês, programadores, entendam como que funciona por baixo dos panos.&lt;/p&gt;
&lt;p&gt;Eu subi o projeto no GitHub, se chama &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI"target="_blank" rel="noopener"&gt;Tiny Qwen CLI&lt;/a&gt; e foi feito somente pra PROPÓSITOS EDUCACIONAIS. Pra uma ferramenta de verdade, pesquise o &lt;a href="https://aider.chat/"target="_blank" rel="noopener"&gt;Aider&lt;/a&gt;. Talvez eu fale dele em outro post depois.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ando brincando BASTANTE com os diversos derivados de GPT como o próprio ChatGPT, Claude Sonnet, Gemini e depois talvez eu faça um outro post sobre minha experiência, mas hoje eu queria brincar um pouco mais a fundo e fazer um experimento nível &ldquo;Hello World&rdquo; pra que vocês, programadores, entendam como que funciona por baixo dos panos.</p>
<p>Eu subi o projeto no GitHub, se chama <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI"target="_blank" rel="noopener">Tiny Qwen CLI</a> e foi feito somente pra PROPÓSITOS EDUCACIONAIS. Pra uma ferramenta de verdade, pesquise o <a href="https://aider.chat/"target="_blank" rel="noopener">Aider</a>. Talvez eu fale dele em outro post depois.</p>
<p>O conceito que quero ensinar: GPTs não são mágica.</p>
<h3>Conceitos<span class="hx:absolute hx:-mt-20" id="conceitos"></span>
    <a href="#conceitos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Assistam a minha <a href="https://www.youtube.com/watch?v=UDrDg6uUOVs&amp;list=PLdsnXVqbHDUeowsAO0sChHDY4D65T5s1U&amp;pp=gAQB"target="_blank" rel="noopener">playlist de I.A.</a> pra entender como um GPT funciona. Depois de assistido vamos entender.</p>
<p>Embaixo de todo Gemini, ChatGPT, Claude ou qualquer outro aberto como LLAMA da Meta, existe um arquivo binário gigante que é o resultado das horas e horas de treinamento com petabytes de dados.</p>
<p>O treinamento consiste - BEM A GROSSO MODO - em tokenizar as palavras dos textos (não é bem isso, mas pense quebrar em sílabas, o certo são n-grams). Depois transformar em vetores (como você aprendeu na facultade, uma lista de coordenadas, que costumam ser <strong>FP</strong> ou ponto flutuante, FP-32, 32-bits pra ser mais preciso). O treinamento consiste em ir mudando esses vetores de lugar dentro de um espaço hiper-dimensional (muito mais que 3 dimensões como estamos acostumados, pense milhares ou milhões).</p>
<p>Esse posicionamento de vetores vai mudando à medida que vamos dando mais textos. Ele vai &ldquo;encontrando relacionamentos&rdquo; entre diferentes tokens. Chegamos num ponto onde cada possível palavra foi codificada como vetores e esse posicionamento lhes dá &ldquo;significados&rdquo;. É onde vem o famoso exemplo que se pegarmos a palavra &ldquo;REI&rdquo; e substrairmos de &ldquo;HOMEM&rdquo; depois somarmos com &ldquo;MULHER&rdquo; vamos encontrar o vetor de &ldquo;RAINHA&rdquo;.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>REI - HOMEM &#43; MULHER = RAINHA</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso não é acidente, é porque o treinamento conseguiu &ldquo;posicionar&rdquo; esses vetores de tal forma que conseguimos fazer &ldquo;contas&rdquo; com esses vetores e encontramos significado.</p>
<p>Estou sendo ABSURDAMENTE GROSSEIRO, considere que isso tudo são dezenas de papers e eu estou resumindo em meia dúzia de parágrafos. O importante é entender que isso custa MUITO TEMPO, porque cada novo token que entra no treinamento precisa varrer todos os outros tokens que já foram calculados e recalcular uma porção enorme deles, o tempo todo!</p>
<p>Horas e horas, dias e dias depois, processando terabytes e terabytes de textos, uma hora chegamos ao final com um enorme arquivo de vetores, um <strong>VSM</strong> ou Vector Space Model.</p>
<p>Se entrarmos num site que cataloga e armazena modelos pré-treinados, na <a href="https://huggingface.co/meta-llama/Meta-Llama-3-70B/tree/main"target="_blank" rel="noopener">página da Meta pro LLAMA 3 70B</a>, vemos isto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/34kujh0nzamxau15gqarr0rhp7v7" alt="Huggingface Meta"  loading="lazy" /></p>
<p>Temos o arquivão particionado em 30 arquivos menos de uns 4.5 GB cada um, totalizando uns <strong>135 GB</strong>, pra um arquivo de 70B ou seja, 70 bilhões de parâmetros. Parâmetros não são &ldquo;neurônios&rdquo; nem nada parecido com isso. São pesos e viéses dos cálculos, armazenados no banco de dados - eu explico isso nos meus videos, assistam.</p>
<p>Existem versões maiores, um GPT o4 tem 1.8 trilhões de parâmetros. São números impressionantes quando se fala, mas na prática, muito acima de 300B, no uso geral, não faz <em>tanta</em> diferença quanto se pensa. Talvez em perguntas muito específicas. Significa que sim, tem muito mais relacionamentos armazenados, mas não necessariamente isso se traduz em respostas proporcionalmente melhores. Só que pode acabar sendo mais prolixo do que o normal. De novo, estou sendo grosseiro, mas é só pra baixar as expectativas. Parem de se impressionar com números grandes se você não sabe o que eles significam.</p>
<p>Muito mais coisa influencia a qualidade das respostas, não só parâmetros: qualidade e quantidade de dados de treinamento, arquitetura do modelo, metodologia do treinamento, tamanho da janela de contexto, fine-tuning, alinhamentos e treinamentos de segurança (que costumam piorar as respostas em nome de &ldquo;segurança&rdquo;).</p>
<p>Modelos menores, com arquiteturas mais eficientes, dados de treinamento de mais qualidade, focado em <strong>tarefas específicas</strong> (é melhor um modelo menor especializado do que um modelo gigante que tenta saber tudo - <em>Jack of all Trades, Master of None</em>).</p>
<p>Ser um arquivo maior e ter mais parâmetros só significa uma coisa: vai ser proporcionalmente mais pesado pra rodar e provavelmente não vai caber na memória tudo de uma vez e precisa ficar fazendo <em>switching</em> de pedaços (como um <em>bank switching de memória</em>, pra quem é programador).</p>
<p>Neste projeto de Hello World, eu escolhi - aleatoriamente - usar o modelo <a href="https://github.com/QwenLM/Qwen2.5-Coder"target="_blank" rel="noopener"><strong>Qwen 2.5 Coder 70B</strong></a>. Esses modelos abertos como Llama costumam estar disponíveis em vários tamanhos. Eu escolhi o 70B porque cabe nos 24GB de VRAM que eu tenho na minha GPU RTX 4090. Se sua GPU for menor, escolha modelos com tamanho menor como os de 0.5B, 3B, 14B, 32B, e sempre prefira modelos especializados (como esse que é &ldquo;Coder&rdquo;). E sempre leia a página do projeto, que costuma ter exemplos de código e fine-tuning documentados. Não existe um código geral que funciona pra tudo, cada modelo tem características diferentes.</p>
<p>O Qwen 2.5 vem dividido em 6 arquivos de 5 GB cada, então precisa baixar <strong>30GB</strong> se quiser brincar com a versão &ldquo;extra-large&rdquo;.</p>
<p>O código pra isso é até que bem simples, porque a comunidade de I.A. já empacotou todas as ferramentas que se precisa em bibliotecas fáceis de usar, normalmente em Python (libs em Python que falam com libs de mais baixo nível em C na real), como o <code>transformers</code> que faz o trabalho pesado de carregar esses arquivos na memória e gerenciar.</p>
<p>Esses arquivos costumam ter extensões como &ldquo;.safetensors&rdquo; que é meio genérico ou &ldquo;.pt&rdquo; ou &ldquo;.pth&rdquo; que é mais específico pra formato PyTorch, que é uma biblioteca Python de redes neurais feita pela Meta. É concorrente do Tensorflow do Google.</p>
<p>Tensors é o nome genérico pra vetores ou matrizes. Um número escalar é um Tensor de ordem 0, um vetor é um Tensor de ordem 1, uma matriz é um Tensor de ordem 2 e assim por diante. Não temos palavras pra ordens acima de 2, então pode falar Tensor de ordem 3, ordem 4 e infinitamente pra milhões de ordens numa LLM.</p>
<p>CPUs são hardwares genéricos feitos pra lidar primariamente com tensors de ordem 0: números escalares inteiros. Operações como soma, adição, multiplicação e tudo mais é de um inteiro pra outro inteiro. Números reais (com decimal) são &ldquo;simulados&rdquo;, separando um número inteiro chamado &ldquo;expoente&rdquo; de outro inteiro chamado &ldquo;mantissa&rdquo; (o que você chama de &ldquo;decimal&rdquo;) separado por um ponto, o &ldquo;ponto flutuante&rdquo;. Mas essencialmente estamos calculando inteiros e truncando precisão. Quanto mais bits, mais precisão.</p>
<p>GPUs são hardware especializados em calcular Tensors, em particular matrizes. Eles foram originalmente feitos pra recalcular efeitos em imagens de video, como um video game em movimento. Frames por segundo. Um frame é formado por linhas e colunas de pixels: um tensor de ordem 2.</p>
<p>Em vez de fazer um loop com &ldquo;for&rdquo; e ir calculando pixel a pixel, é mais eficiente passar uma matriz inteira pra multiplicar com outra matriz, um &ldquo;kernel&rdquo;, pra obter algum efeito como sombra, luz, mudança de cor, distorções e muito mais. Programas que fazem isso costumamos chamar de **shaders&quot;.</p>
<p>Um PyTorch ou Tensorflow se conecta a um GPU usando uma biblioteca que lhes dá acesso a APIs pra, em vez de passar imagens, passar matrizes numéricas. A NVIDIA tem CUDA, a AMD tem ROCM, a Apple tem Metal, existe o projeto geral Vulkan que tenta falar com todos. Mas o mais avançado e o mais usado ainda é o CUDA, porque de fato foi a NVIDIA que saiu na frente anos atrás evangelizando que GPUs poderiam ser usadas pra mais do que shaders de imagens.</p>
<p>Isso tudo dito, arquivos como esses &ldquo;.safetensors&rdquo; são carregados pela biblioteca <code>transformers</code>, que chama o <code>torch</code> e manda o CUDA carregar na VRAM da GPU, onde ele pode processar. O código mais básico pra isso é mais ou menos assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">transformers</span> <span class="kn">import</span> <span class="n">AutoTokenizer</span><span class="p">,</span> <span class="n">AutoModelForCausalLM</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">device</span> <span class="o">=</span> <span class="s2">&#34;cuda&#34;</span> <span class="c1"># the device to load the model onto</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Now you do not need to add &#34;trust_remote_code=True&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">TOKENIZER</span> <span class="o">=</span> <span class="n">AutoTokenizer</span><span class="o">.</span><span class="n">from_pretrained</span><span class="p">(</span><span class="s2">&#34;Qwen/Qwen2.5-Coder-32B&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">MODEL</span> <span class="o">=</span> <span class="n">AutoModelForCausalLM</span><span class="o">.</span><span class="n">from_pretrained</span><span class="p">(</span><span class="s2">&#34;Qwen/Qwen2.5-Coder-32B&#34;</span><span class="p">,</span> <span class="n">device_map</span><span class="o">=</span><span class="s2">&#34;auto&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">eval</span><span class="p">()</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Criamos um Tokenizer e um Model. O Tokenizer vai &ldquo;tokenizar&rdquo; o que você perguntar no chat e todo texto que passar pro contexto da sessão. Essa tokenização é &ldquo;mapeada&rdquo; com o modelo carregado pra gerar uma &ldquo;embedding&rdquo;, que é a representação em vetores do seu texto, normalizado (compatível) pro modelo carregado. Diferentes modelos convertem seu texto em embeddings diferentes. Um embedding criado com Qwen não serve pra rodar direto com Llama.</p>
<p>Uma vez tendo esses dois objetos, já podemos fazer um chat:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># tokenize the input into tokens</span>
</span></span><span class="line"><span class="cl"><span class="n">input_text</span> <span class="o">=</span> <span class="s2">&#34;#write a quick sort algorithm&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">model_inputs</span> <span class="o">=</span> <span class="n">TOKENIZER</span><span class="p">([</span><span class="n">input_text</span><span class="p">],</span> <span class="n">return_tensors</span><span class="o">=</span><span class="s2">&#34;pt&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Use `max_new_tokens` to control the maximum output length.</span>
</span></span><span class="line"><span class="cl"><span class="n">generated_ids</span> <span class="o">=</span> <span class="n">MODEL</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="n">model_inputs</span><span class="o">.</span><span class="n">input_ids</span><span class="p">,</span> <span class="n">max_new_tokens</span><span class="o">=</span><span class="mi">512</span><span class="p">,</span> <span class="n">do_sample</span><span class="o">=</span><span class="kc">False</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># The generated_ids include prompt_ids, so we only need to decode the tokens after prompt_ids.</span>
</span></span><span class="line"><span class="cl"><span class="n">output_text</span> <span class="o">=</span> <span class="n">TOKENIZER</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">generated_ids</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">model_inputs</span><span class="o">.</span><span class="n">input_ids</span><span class="p">[</span><span class="mi">0</span><span class="p">]):],</span> <span class="n">skip_special_tokens</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Prompt: </span><span class="si">{</span><span class="n">input_text</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">Generated text: </span><span class="si">{</span><span class="n">output_text</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Com o <code>input_text</code> (o que você digitou no chat) tokenizado e convertido, podemos pedir ao modelo pra <strong>COMPLETAR</strong> esse texto, e ele começa gerando um novo token atrás do outro, usando a tal arquitetura de ATENÇÃO, encontrando a melhor próxima palavra que conseguir. Uma hora ele termina e devolve em <code>output_text</code> e pronto, isso é sua &ldquo;resposta&rdquo;. Como eu sempre falo, GPT é isso: um GERADOR DE TEXTO.</p>
<h3>Tokens e Janelas de Contexto<span class="hx:absolute hx:-mt-20" id="tokens-e-janelas-de-contexto"></span>
    <a href="#tokens-e-janelas-de-contexto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Uma das coisas mais irritantes dos serviços pagos de LLM como ChatGPT ou Claude é a limitação da quantidade de tokens que podemos usar. Eles acabam muito rápido se você está realmente trabalhando todos os dias. E não são exatamente baratos só pra brincar. Sim, sim eu sei, depende do serviço. Eu posso usar Cursor e ter créditos mais baratos de Claude do que usando diretamente Claude. Também vou ter respostas melhores se usar direto a API em vez de usar o chat web, etc.</p>
<p>Isso é papo pra outro dia, mas é minha motivação pra sempre ter uma opção open source à disposição e saber como &ldquo;tunar&rdquo; pra mim. Qwen 2.5 pode não ser melhor que Claude ou Cursor - já explico porque. Mas é de &ldquo;grátis&rdquo; e roda na minha máquina local (zero preocupação sobre privacidade).</p>
<p>Veja meu projetinho &ldquo;hello world&rdquo; funcionando:</p>
<div class="video-container">
    <video controls>
        <source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/qwen_cli2.mp4">
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/qwen_cli2.mp4)
    </video>
</div>
<p>Note que ele não serve só pra dar respostas. Em um momento eu peço pra ele ler um arquivo meu local, e ele consegue puxar o código e fazer a análise. Já já explico como fazer isso.</p>
<p>O ponto agora é que ele tem janela de contexto suficiente pra carregar coisas longas como código fonte de arquivos. Eu não testei nada muito pesado, mas esse modelo de 70B ocupa fácil quase 20GB na minha GPU:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ykbdo2ekv12woxbx964um0osphk0" alt="nvidia-smi"  loading="lazy" /></p>
<p>Pra configurar, eu fiz um bloco de configuração assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">DEFAULT_CONFIG</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;model_repo&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen2.5-Coder-14B-Instruct&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;model_dir&#34;</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">Path</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&#34;MODELS_DIR&#34;</span><span class="p">,</span> <span class="s2">&#34;/models&#34;</span><span class="p">))</span> <span class="o">/</span> <span class="s2">&#34;Qwen2.5-Coder-14B-Instruct&#34;</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;quantization&#34;</span><span class="p">:</span> <span class="s2">&#34;8bit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;max_context_tokens&#34;</span><span class="p">:</span> <span class="mi">120000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;max_new_tokens&#34;</span><span class="p">:</span> <span class="mi">10000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;temperature&#34;</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;model_download_timeout&#34;</span><span class="p">:</span> <span class="mi">1800</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;helpers_dir&#34;</span><span class="p">:</span> <span class="s2">&#34;helper_functions&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O importante, primeiro, é que estou escolhendo usar cálculos com FP8 (float-point 8-bits), embora esse modelo acho que seja de FP-16. Se deixar 16-bits, as respostas são muito mais demoradas, no nível que dá pra sentir. Tipo, uma palavra por segundo. Entendi que quem faz essa conversão on-the-fly é a biblioteca <code>bitsandbytes</code> do Python e precisa ter instalado.</p>
<p>Outra parte importante são os logs que eu deixo à mostra na hora de carregar o modelo, você vai ver o seguinte se tentar rodar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Qwen attention is NOT SDPA-compatible, or SDPA is not available. Trying xFormers...
xFormers is available. Enabling it for attention.
Sliding Window Attention is enabled but not implemented for `sdpa`; unexpected results may be encountered.
2025-04-25 02:34:27,638 [INFO] accelerate.utils.modeling: We will use 90% of the memory on device 0 for storing the model, and 10% for the buffer to avoid OOM. You can set `max_memory` in to a higher value to use more memory (at your own risk).
Loading checkpoint shards: 100%|██████████████████████████████████████████████████████████| 6/6 [00:07&lt;00:00,  1.29s/it]
SDPA is available and (hopefully) being used!</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O <code>transformers</code> dá um aviso de que &ldquo;Sliding Window Attention está habilitado mas não implementa SDPA&rdquo;. E eu faço um truque com <code>xformers</code> pra tentar habilitar (no final ele fala &ldquo;SDPA está disponível&rdquo;).</p>
<p>O que é <strong>SLIDING WINDOW ATTENTION</strong>. A grosso modo, a idéia toda desta geração de GPT é o conceito de ATENÇÃO, do paper <a href="https://arxiv.org/abs/1706.03762"target="_blank" rel="noopener">&ldquo;Attention is all you Need&rdquo;</a>, que foi o pontapé que deu início a tudo que temos hoje relacionado a GPT e LLM.</p>
<p>O conceito importante é que as respostas parecem tão &ldquo;boas&rdquo; porque ele usa esse processo de &ldquo;atenção&rdquo; pra gerar as respostas.</p>
<p>No começo, na era do GPT 2, a atenção era bem &ldquo;focada&rdquo; num contexto pequeno. Faixa de 1024 tokens, que é quase nada. Dá pra fazer perguntas curtas, mas jamais cabe código-fonte.</p>
<p>Daí fomos evoluindo, você já viu o Gemini que fala que aguenta 1 milhão de tokens. Mesmo esse Qwen 2.5, diz que aguenta 1 milhão de tokens também. A grosso modo pense que cada 1 token são 4 letras, em média.</p>
<p>Um código-fonte, em média, tem linhas de 40 caracteres (alguns mais longos, alguns mais curtos). Então isso dá uns <strong>10 tokens por linha</strong>. Um arquivo médio tem umas 500 linhas x 10 tokens/linha, isso já dá uns 5 mil tokens.</p>
<p>Um projetinho pequeno inteiro, com uns 20 arquivos x 5.000 tokens, já são uns 100.000 tokens. Lógico, é uma média. Mas veja que menos que 100 mil tokens e não tem com ter um projeto todo no contexto.</p>
<p><strong>&ldquo;Ah, então se usar um Gemini ou esse Qwen, cabe 200 arquivos??&rdquo;</strong></p>
<p>Mais ou menos. Aqui entra Sliding Window. Pense que mesmo tendo um contexto gigante, <strong>não tem como dar atenção pra tudo ao mesmo tempo</strong>, então ele pesquisa o que você está perguntando no presente, e &ldquo;desliza a janela de atenção&rdquo; só até uma parte do contexto que parece mais relevante. Por isso que mesmo carregando um monte de material, ele vai manter em memória, mas <strong>só vai dar atenção pra um pedaço de cada vez</strong>, isso é SLIDING WINDOW ATTENTION.</p>
<p>Pra fazer isso temos a funcionalidade de &ldquo;SCALED DOT PRODUCT ATTENTION&rdquo; ou SDPA, que foi o que o <code>transformers</code> reclamou que talvez não tivesse, que é uma das formas de fazer Sliding Window Attention.</p>
<p>No meu código, pra carregar o modelo e tentar usar SDPA eu tentei isto:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">xformers.ops</span>  <span class="c1"># Test if xFormers is installed</span>
</span></span><span class="line"><span class="cl"><span class="n">model_config</span><span class="o">.</span><span class="n">attention_implementation</span> <span class="o">=</span> <span class="s2">&#34;flash_attention_2&#34;</span>  <span class="c1"># Or &#34;memory_efficient&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="s2">&#34;xFormers is available. Enabling it for attention.&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">torch</span><span class="o">.</span><span class="n">nn</span><span class="o">.</span><span class="n">functional</span><span class="p">,</span> <span class="s2">&#34;scaled_dot_product_attention&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;SDPA is available and (hopefully) being used!&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E por isso, no final, ele termina com &ldquo;SDPA is available&rdquo;. &ldquo;Parece&rdquo; que funcionou. Se quiser testar, existe esse codigozinho que pode ser passado pra ver se funciona:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">torch</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="ow">not</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">torch</span><span class="o">.</span><span class="n">nn</span><span class="o">.</span><span class="n">functional</span><span class="p">,</span> <span class="s2">&#34;scaled_dot_product_attention&#34;</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;SDPA not available&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">exit</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">q</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">randn</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="s2">&#34;cuda&#34;</span> <span class="k">if</span> <span class="n">torch</span><span class="o">.</span><span class="n">cuda</span><span class="o">.</span><span class="n">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="s2">&#34;cpu&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">k</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">randn</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">v</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">randn</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span><span class="o">.</span><span class="n">to</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">device</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">output</span> <span class="o">=</span> <span class="n">torch</span><span class="o">.</span><span class="n">nn</span><span class="o">.</span><span class="n">functional</span><span class="o">.</span><span class="n">scaled_dot_product_attention</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;SDPA works in isolation!&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;SDPA fails in isolation: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Dot Product, ou produto escalar de matriz, é uma das operações mais usadas em modelos LLM. Entendi que sem SDPA, vai existir Sliding Window, mas com SDPA vai dar resultados de atenção melhores. Quanto melhor? Difícil quantificar, só entendi que é melhor.</p>
<p>Mas essa é uma das técnicas de porque hoje em dia tem como ter contextos gigantes de 1 milhão de tokens: porque não precisa dar atenção pra tudo ao mesmo tempo, mas sim janelas em posições diferentes a cada nova pergunta. Entenderam o truque?</p>
<p>E com isso, naquela minha config eu posso tentar aumentar o <code>max_context_tokens</code> (que deixei só 120 mil tokens) e o <code>max_new_tokens</code> que é o limite de tokens por resposta (que deixei em 10 mil tokens). Experimente aumentar esses números até o máximo de 1 milhão pra ver como fica.</p>
<p>**O contexto só importa dentro da janela deslizante de contexto. É bom passar bastante contexto. Mas entenda que ele não vai dar atenção pra tudo ao mesmo tempo e sim dentro de uma janela deslizante. **</p>
<p>Outro parâmetro importante é a <code>temperature</code>, que deixei baixo em 0.1. Quanto maior, mais &ldquo;criativo&rdquo; ele vai ser nas respostas. Quanto menor, mais &ldquo;exato&rdquo; tende a ser. Também é outro parâmetro que vale a pena explorar.</p>
<h3>Prompts e &ldquo;Agentes&rdquo;<span class="hx:absolute hx:-mt-20" id="prompts-e-agentes"></span>
    <a href="#prompts-e-agentes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A melhor forma de ter respostas melhores é iniciando toda nova sessão, seja via API, seja no chat Web, descrevendo uma &ldquo;persona&rdquo;, como você quer que o GPT responda. Quanto mais descritivo for, melhor. Por exemplo, eu tenho este trecho no meu programa:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">build_system_prompt</span><span class="p">(</span><span class="n">tool_prompts</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">base</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;You are Qwen2.5 Coder, a highly skilled AI assistant specializing in software development.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Your capabilities include code analysis, explanation, error detection, and suggesting improvements.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">tools_section</span> <span class="o">=</span> <span class="s2">&#34;TOOLS:</span><span class="se">\n</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">tool_prompts</span><span class="p">)</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n\n</span><span class="s2">&#34;</span> <span class="k">if</span> <span class="n">tool_prompts</span> <span class="k">else</span> <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">rules_section</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;IMPORTANT RULES:</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;1. You MUST use the appropriate tool when necessary.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;2. You MUST NOT reveal the tool commands to the user.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;3. After a tool is used, continue the conversation as if you have direct access to the content.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;4. If a file fails to load, inform the user clearly.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;5. Do NOT ask for file/URL content directly; use tools.</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;6. Once you&#39;ve executed [LOAD_FILE ...], you MUST immediately use the loaded content. &#34;</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;Never say you cannot read it — if you see [LOAD_FILE &lt;path&gt;] then you now *have* it.</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">base</span> <span class="o">+</span> <span class="n">tools_section</span> <span class="o">+</span> <span class="n">rules_section</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Esse é um exemplo curto até mas já estabeleço que tipo de conversa eu quero ter com ele e quais regras eu quero que ele siga. Não sempre, mas na maioria das vezes, ele tende a seguir. Lembre-se: quanto maior ficar o chat, maior o contexto, mais vai deslizar a janela, e mais longe vão ficando essas regras, até chegar um ponto onde ele começa a &ldquo;esquecer&rdquo; de agir como eu pedi.</p>
<p>Entendendo isso, tem outro truque que eu adicionei. Nesse projeto tem um diretório chamado <a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/"target="_blank" rel="noopener">helper_functions</a> onde temos scripts como <code>load_file.py</code> ou <code>fetch_url.py</code>. No código anterior note como ele concatena alguma coisa no meio do prompt, chamada &ldquo;TOOLS&rdquo;. Quando o chat inicia, eu imprimi na tela e você vai ver esse trecho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>...
TOOLS:
[BATCH_LOAD args] – If the user asks to read, load or analyze all the files from a relative path, such as ./src or similar,
[LOAD_FILE args] – Whenever the user asks to read, load, analyze some code and provides a relative path, such as ./file.py or utils/utils.py or similar,
[FETCH_URL args] – Whenever the user asks to read, load, research or consult a URL,
...</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou seja, se em algum momento, eu pedir:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hiz90dk6owf2otjfixiftjxavm4n" alt="qwen_cli fetch_url"  loading="lazy" /></p>
<p>esse &ldquo;can you read a website at https://www&hellip;.&rdquo; vai fazer o modelo seguir a regra do prompt inicial e digitar no chat:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>[FETCH_URL https://www...]</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Meu programa de chat tem um &ldquo;parser&rdquo;, que fica monitorando o histórico por essas palavras-chave especiais:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">parse_special_commands</span><span class="p">(</span><span class="n">response</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">List</span><span class="p">[</span><span class="nb">tuple</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="n">pattern</span> <span class="o">=</span> <span class="sa">r</span><span class="s1">&#39;\[([A-Z_]+)\s+([^\]]+)\]&#39;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[(</span><span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">(),</span> <span class="n">m</span><span class="o">.</span><span class="n">start</span><span class="p">(),</span> <span class="n">m</span><span class="o">.</span><span class="n">end</span><span class="p">())</span> <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="n">pattern</span><span class="p">,</span> <span class="n">response</span><span class="p">)]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É um parser bem primitivo, usando REGEX pra pegar esses trechos. É aqui que eu digo que meu projeto é educacional. Eu imagino que num Co-pilot, Cursor, etc, tenha um parser muito mais robusto. Eu expliquei como se faz parsers com ANTLR no meu canal, lembra? Um dos videos é o <a href="https://www.youtube.com/watch?v=_7nISfpofec&amp;pp=ygUTYWtpdGFvbnJhaWxzIHBhcnNlcg%3D%3D"target="_blank" rel="noopener">&ldquo;Eu fiz um servidor de SQL?&rdquo;</a>. Parsers de verdade não se fazem com REGEX, mas pra provas de conceito, serve.</p>
<p>Daí no loop do chat tem este trecho:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">response_text</span> <span class="o">=</span> <span class="n">QwenSession</span><span class="o">.</span><span class="n">_tokenizer</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">generated</span><span class="p">,</span> <span class="n">skip_special_tokens</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Other special commands</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">cmd_type</span><span class="p">,</span> <span class="n">cmd_arg</span><span class="p">,</span> <span class="n">_</span><span class="p">,</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">parse_special_commands</span><span class="p">(</span><span class="n">response_text</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">cmd_type</span> <span class="ow">in</span> <span class="n">helper_functions</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">result</span> <span class="o">=</span> <span class="n">helper_functions</span><span class="p">[</span><span class="n">cmd_type</span><span class="p">](</span><span class="n">cmd_arg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">result</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">history</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;system&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="n">result</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="bp">self</span><span class="o">.</span><span class="n">history</span><span class="o">.</span><span class="n">append</span><span class="p">({</span><span class="s2">&#34;role&#34;</span><span class="p">:</span> <span class="s2">&#34;user&#34;</span><span class="p">,</span> <span class="s2">&#34;content&#34;</span><span class="p">:</span> <span class="s2">&#34;Please continue the analysis using the loaded file.&#34;</span><span class="p">})</span>
</span></span><span class="line"><span class="cl">            <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;✅ [</span><span class="si">{</span><span class="n">cmd_type</span><span class="si">}</span><span class="s2">] processed &#39;</span><span class="si">{</span><span class="n">cmd_arg</span><span class="si">}</span><span class="s2">&#39;&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">chat</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">prompt</span><span class="p">,</span> <span class="n">helper_functions</span><span class="p">,</span> <span class="n">max_new_tokens</span><span class="p">,</span> <span class="n">temperature</span><span class="p">,</span> <span class="n">stream</span><span class="p">,</span> <span class="n">hide_reasoning</span>
</span></span><span class="line"><span class="cl">            <span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ele decodifica os tokens gerados, passa pro parser, e se tiver um comando especial, ele chama <code>helper_functions</code> que dinamicamente vai chamar o <code>fetch_url.py</code> que é um script besta que vai usar <code>urllib</code> pra carregar a página.</p>
<p>Com o conteúdo carregado ele concatena de volta no <code>self.history</code> que é o histórico do chat, usando <code>.append</code> e manda uma mensagem - como se fosse eu, o &ldquo;user&rdquo; - pedindo pro modelo continuar, agora que tem o conteúdo da página.</p>
<p>E como podem ver na foto de tela acima, ele consegue me dar o resumo da página do GitHub que eu passei. É assim que os diversos GPTs conseguem puxar informação mais atual dos sites, que não existiam na época do treinamento do modelo pré-treinado.</p>
<p>Outro exemplo, eu posso pedir pra ele carregar um arquivo meu de código e pedir pra ele analizar, ou corrigir bugs, ou refatorar como eu quiser:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lt891apoaebrmx0y4k7ngtk2l5ol" alt="qwen_cli load_file"  loading="lazy" /></p>
<p>É parecido com a função de &ldquo;upload de arquivos&rdquo; que tem no ChatGPT. De novo:</p>
<ul>
<li>o modelo vai seguir as regras do prompt inicial e vai digitar o comando <code>[LOAD_FILE ...]</code></li>
<li>meu <code>parser_special_commands</code>, com seu REGEX, vai encontrar esse comando e separar os argumentos</li>
<li>o loop do chat vai chamar <code>helper_functions</code> e mapear pro script <code>load_file.py</code>, que é um script besta que lê arquivos</li>
<li>o conteúdo do arquivo é concatenado de volta no <code>history</code> do contexto e o modelo agora pode continuar respondendo em cima desse conteúdo.</li>
</ul>
<p>Entendam, aqueles arquivos &ldquo;.safetensors&rdquo; são <strong>READ-ONLY</strong>. Nada é escrito ali. Não interessa qual. Todo LLM é <strong>FECHADO PRA ESCRITA</strong>. Nada do que você escrever no chat é gravado no modelo.</p>
<p>Lembra que eu falei que quantidade de &ldquo;parâmetros&rdquo; não determina a qualidade do modelo? O que <strong>MAIS</strong> determina qualidade é a <strong>QUALIDADE DO MATERIAL DE TREINAMENTO</strong>. E chats, até certo ponto, são úteis como amostra de como o modelo deve responder, mas 90% ou mais eu afirmo que é puro e completo <strong>LIXO</strong>. Todo chat que escrevemos é lixo e não presta pra treinar, ele só iria PIORAR o modelo final e deixar mais pesado com informação inútil.</p>
<p>Pode escrever o &ldquo;Muito Obrigado&rdquo; ou &ldquo;Por Favor&rdquo; que quiser. Tudo é descartado ao final, nada é gravado, e nenhum ChatGPT, Gemini, Claude, Deepseek ou Qwen ou Llama jamais vai saber nada do que você conversou com ela. São informações totalmente <strong>SEPARADAS E DESCARTÁVEIS</strong>.</p>
<p>Tudo que eu digitei nesses exemplos nas fotos de tela como meu chat-zinho de Qwen é descartado no momento que digito &ldquo;bye&rdquo; ou dou &ldquo;Ctrl+D&rdquo; pra sair. Isso que chamamos de &ldquo;sessão&rdquo; ou &ldquo;histórico&rdquo;. É só um arquivo de texto desnecessário pro modelo. Só serve pra nós, humanos, como anotação pra usar depois, se quiser. O meu programinha nem tem a função de gravar esse histórico. Quando sair, está tudo limpo. Então, pode xingar no meu chat à vontade.</p>
<p>Tem dois pontos importantes nesta seção:</p>
<ul>
<li>quanto melhor forem as regras que você descrever no começo, melhor tende a ser o resultado das respostas. Isso vale pra qualquer LLM.</li>
<li>você pode &ldquo;combinar&rdquo; com o modelo pra cuspir &ldquo;pseudo-comandos&rdquo; e ter um programa externo que faz o parsing e executar os comandos de verdade, como eu fiz com meus &ldquo;helpers&rdquo;. É isso que chamamos de agentes. Agente de LLM é um programinha que devolve algum conteúdo de volta ao histórico de contexto da sessão.</li>
</ul>
<p>Aonde um Cursor ou Co-pilot são melhores do que esse meu programinha educacional? Eles já vem pré-configurados com DEZENAS de regras pré-prontas que eles testaram bastante pra dar resultados melhores, e contam com uma biblioteca de DEZENAS de pequenos scripts - como esses meus helpers - pra puxar informação do seu projeto ou da sua IDE pra melhorar o contexto.</p>
<p>Mas o segredo é isso: <strong>PROMPT DE REGRAS</strong> e <strong>SCRIPTS DE CONTEXTO</strong>.</p>
<p>Se for resumir o ecossistema inteiro de Agentes ou MCPs ou seja lá como se chama hoje em dia, é essa combinação de fatores pra melhorar as respostas.</p>
<p>Outro truque é utilizar MAIS DE UMA LLM. Lembra que falei que LLMs especializadas são melhores? Além de Qwen 2.5 Coder, que dizem que é bom pra código, já existia antes o WizardCoder, Code Llama, StarCoder, agora tem o Deepseek Coder e vários outros, alguns até especializados em linguagens específicas. Você pode experimentar qual dá resultados melhores pro seu caso.</p>
<p>Se salvar a sessão em texto, pode descarregar uma LLM, carregar outra LLM, re-tokenizar e re-carregar no contexto dessa outra LLM e tentar um resultado diferente - se alguém quiser implementar essa funcionalidade no meu projetinho, Pull Requests são bem vindos.</p>
<p>Como falei no começo, é o que faz softwares como o <a href="https://aider.chat/"target="_blank" rel="noopener">Aider</a>. Eu não brinquei o suficiente com ele ainda, mas ele se conecta com ChatGPT, Gemini, Claude e qualquer outro e você pode habilitar quantos quiser. Acho que suporta usar modelos locais como o Qwen também, então já existem ferramentas que fazem isso. E essa é a terceira perna pra melhores respostas: MULTI-LLM.</p>
<h3>Próximos Passos<span class="hx:absolute hx:-mt-20" id="próximos-passos"></span>
    <a href="#pr%c3%b3ximos-passos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em vez de ficar só impressionado com o monte de ferramentas que existem, entendam essas peças fundamentais. Saber como as coisas funcionam sempre dá mais opções pra forjar alternativas que funcionam melhor pra casos específicos. Você, que diz ser programador, deveria estar explorando essas opções - e não só ficar se conformando com as limitações e esperando alguém resolver seu problema. Arregaça as mangas e resolve você mesmo. Não foi pra isso que virou programador???</p>
]]></content:encoded><category>llms</category><category>modelos-locais</category><category>agentes-de-codigo</category></item><item><title>Mudando roupas usando I.A. (ComfyUI)</title><link>https://akitaonrails.github.io/2025/04/23/mudando-roupas-usando-i-a-comfyui/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/23/mudando-roupas-usando-i-a-comfyui/</guid><pubDate>Wed, 23 Apr 2025 17:45:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atualização:&lt;/strong&gt; este post está obsoleto. Hoje uso &lt;a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"&gt;&lt;strong&gt;akitaonrails/aitrepreneur-docker&lt;/strong&gt;&lt;/a&gt;, que roda as stacks do Aitrepreneur localmente em Docker com modelos e workflows pré-carregados. Veja o README para detalhes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No &lt;a href="https://www.akitaonrails.com/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games"target="_blank" rel="noopener"&gt;post anterior&lt;/a&gt; mostrei como gerar character sheets pra desenvolvimento de games. Mas alguns me perguntaram &amp;ldquo;e se eu quiser roupas específicas?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hold my Beer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tem como, é um workflow separado que depois dá pra adicionar ao workflow anterior se quiser (fica de lição de casa). Basta pegar o personagem gerado no primeiro grupo de nodes e passar por este outro workflow que troca roupas. (ou fazer o trabalhinho extra de gerar loras e ativar com palavras-chave no prompt também, não existe só um jeito).&lt;/p&gt;</description><content:encoded><![CDATA[<blockquote>
  <p><strong>Atualização:</strong> este post está obsoleto. Hoje uso <a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"><strong>akitaonrails/aitrepreneur-docker</strong></a>, que roda as stacks do Aitrepreneur localmente em Docker com modelos e workflows pré-carregados. Veja o README para detalhes.</p>

</blockquote>
<p>No <a href="https://www.akitaonrails.com/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games"target="_blank" rel="noopener">post anterior</a> mostrei como gerar character sheets pra desenvolvimento de games. Mas alguns me perguntaram &ldquo;e se eu quiser roupas específicas?&rdquo;.</p>
<p><strong>Hold my Beer</strong></p>
<p>Tem como, é um workflow separado que depois dá pra adicionar ao workflow anterior se quiser (fica de lição de casa). Basta pegar o personagem gerado no primeiro grupo de nodes e passar por este outro workflow que troca roupas. (ou fazer o trabalhinho extra de gerar loras e ativar com palavras-chave no prompt também, não existe só um jeito).</p>
<p>Diferente do workflow do Mick - que é pago - este outro é aberto, peguei em algum Reddit e coloquei na pasta de workflows do meu projeto de Docker como &ldquo;garmente-replacement-idm-vton.json&rdquo;, só carregar no ComfyUI e brincar. Vamos ver o resultado. Eis duas imagens, o resultado não é perfeito, mas veja se descobre qual dos dois é o original e qual é o gerado pela I.A.:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/6b56mdoiwf82xdh7cdhdc7tg0ic2" alt="Hemsworth 1"  loading="lazy" /></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/w0s9nthw6jt86en8wl09m67ryzpc" alt="Hemsworth 2"  loading="lazy" /></p>
<p>Resposta ao final do post kkkk</p>
<p>Mas vamos lá, o workflow completo se parece com isto:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xbgu7dzrjzflhfsld6jtrl5jc3ia" alt="workflow"  loading="lazy" /></p>
<p>No estágio atual da minha setup tem um glitch que não consegui resolver. Eu acho que é a extension JNodes que está abrindo um popup infernal no top esquerdo da interface, que não fecha de jeito nenhum. Por enquanto estou abrindo o Inspector e deletando o Node do HTML direto mesmo kkkk gambiarra mas funciona:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p8q4hmwjmi4opsu8u045jxrpma2v" alt="JNode bug"  loading="lazy" /></p>
<p>Se alguém souber como consertar, agradeceria muito. Tem um alert de uma extension que parece que tá defasada, esse front-end alguma coisa, mas é só ignorar. Tinha que ser front-end-alguma-coisa &hellip;</p>
<p>Enfim. neste primeiro grupo, é simples, basta carregar uma imagem o mais limpa e clara possível da roupa nova que quer usar. E depois a foto original onde quer que essa roupa seja aplicada. Ele não faz milagre, se a foto for de alguém com paletó e você quiser mudar pra regata, às vezes ele consegue, mas não espere que os braços e torso sejam bons, ele vai redesenhar tudo como achar que precisa, ou vai acabar colocando a regata em cima do paletó, enfim, é I.A., quanto mais claro for o objetivo, melhor o resultado. Se a nova roupa é um paletó, idealmente o original era outro paletó, vai encaixar melhor.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2owpldebsc91y6vfsayscdkuqtui" alt="Load Images"  loading="lazy" /></p>
<p>Já avisando que os modelos do IDM-VTON pesam realmente uma <strong>TONELADA</strong>. Ele sozinho baixa nada menos que <strong>30GB</strong> de modelos (já incluso no meu setup) e vai exigir mais de 20GB de VRAM, então abaixo de uma RTX 3090 de 24GB, não vai rodar. Tem como rodar online, no site do Hugging Face. Assista este tutorial que mostra como:</p>
<!-- youtube video unavailable (deleted/blocked): WL59FqL0L-s -->
<p>Esse é o canal do <strong>Aiconomist</strong>. Ele mostra como rodar online e como fazer o setup na sua máquina <a href="https://youtu.be/pFYqPf1Z7x8?si=Ryu5vxPmKGr4zhWx"target="_blank" rel="noopener">neste outro video</a> que usei de referência pra fazer meu workflow. Assinem o canal dele, vale a pena, os tutoriais são bem explicadinhos e tem mais detalhes que não vou explicar neste post.</p>
<p>Isso dito, tem um bug que ainda não resolvi. Depois que roda o workflow, o modelo fica pendurado na VRAM da GPU e se tento rodar de novo, ele EMPERRA. Aí tenho que desligar o container de Docker e reiniciar pra recuperar a memória. Tentei um node de Clear VRAM GPU mas não funcionou. Se alguém souber como consertar, agradeço também.</p>
<p>O próximo passo é recorrente em vários workflow: extrair camadas de informações da foto original. Conseguir uma máscara da roupa que queremos substituir e informações da pose original:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/kgs8jj5m7cgt2nx4kv8yazfpbfg5" alt="máscara e pose"  loading="lazy" /></p>
<p>É por isso que workflows de ComfyUI são poderosos quando você começa a entender como processo é dividido. Essas partes são reusáveis e diferentes modelos dão diferentes resultados dependendo dessas controlnets que se adiciona.</p>
<p>Aí chegamos na parte principal: ligar todas essas informações no node de IDM-VTON, que vai reposicionar a nova roupa em cima da foto original:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7xnxbrcy22trdctbf5a1dhh0rqjw" alt="IDM-VTON"  loading="lazy" /></p>
<p>Note que o resultado não é muito bom, fica bem &ldquo;fake&rdquo; mesmo. E tem como ajustar parâmetros pra melhorar. Mas o caso de uso não é usar pra trocar por roupa <strong>específica</strong> mas sim por roupa <strong>similar</strong>. Já vou explicar, mas antes, uma tangente.</p>
<p>O repositório da extension original desse Node é <a href="https://github.com/TemryL/ComfyUI-IDM-VTON"target="_blank" rel="noopener">este</a> Mas se tentar instalar, ele quebra. Parece que ele está um pouco defasado com o ComfyUI e precisa rodar numa versão mais velha. Mas isso é um saco. Então, eu resolvi fazer um <a href="https://github.com/akitaonrails/ComfyUI-IDM-VTON"target="_blank" rel="noopener"><strong>FORK</strong></a> com as correções necessárias pra rodar na versão mais nova (as versões mais novas dos pacotes de Python diffusers, transformers, huggingface_hub, quebram o IDM-VTON).</p>
<p>Aliás, o gerenciamento de dependências de Python é bem RUIM. Mas do ComfyUI é ainda pior, porque diferentes extensions (que instalam dependências de Python globalmente) podem estar em diferentes estágios de desenvolvimentos. Toda vez que o ComfyUI em si muda, todo mundo precisa atualizar rápido. Por isso o ideal é não instalar a versão da branch master do ComfyUI, sempre uma versão estável (marcada numa tag) ou duas versões pra trás, pra garantir. Mas aí tem extensions novas que exigem você estar na versão mais nova. Como falei, é um pesadelo pra gerenciar. Sendo programador, eu consigo &ldquo;gambiarrar&rdquo; quando dá erro, mas usuários normais vão sofrer um pouco.</p>
<p>Feito o rant. No passo anterior a primeira parte do fluxo acaba. Quando bate nesse node Image Sender, o processo pára - não sei porque. E precisa clicar no botão &ldquo;RUN&rdquo; uma segunda vez. Daí o outro node, Image Receiver, vai receber a nova imagem. Acho que vou só apagar esses dois nós e ligar direto o output &ldquo;image&rdquo; do node Run IDM-VTON Inference e ligar direto no próximo node que são o Image Composite Masked e o VAE Encode.</p>
<p>Vamos voltar um passo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jj950u7842rkc9lxniz3uoztbszv" alt="Nova foto, nova roupa"  loading="lazy" /></p>
<p>Nesse próximo passo, no segundo &ldquo;RUN&rdquo;, ele pega a imagem com a nova roupa, mas baixa qualidade e carrega um novo checkpoint (no caso o juggernaultXL mas poderia ser qualquer outro como o próprio SDXL, FLUX ou dreamsharper). O modelo vai determinar a nova foto, que é o que se gera no final, na direita.</p>
<p>Note que a nova foto tem qualidade de roupa muito melhor, mas a cara é completamente diferente. Ele usou as informações da foto original, manteve a pose, a proporção do corpo, até o fundo, mas o rosto ele não consegue manter. Não tem problema, porque podemos ajudar.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/bw9sram1maspiob2ubo4r904oriy" alt="mix photos"  loading="lazy" /></p>
<p>Lembra que no passo anterior conseguimos a máscara (preto e branco) da posição exata da roupa? Podemos aumentar a máscara e aplicar um Gaussian Blur (pra depois ele mesclar bonitinho na foto original) e usar isso pra recortar da foto gerada com o rosto diferente, usando o modelo <a href="https://github.com/tencent-ailab/IP-Adapter/"target="_blank" rel="noopener"><strong>IPAdapter</strong></a> (que sozinho são outros 30GB de modelos - falei que é pesado).</p>
<p>Se não entendi errado, IP Adapter é o responsável por conseguir gerar novas imagens COERENTES entre si, com o mesmo rosto, ou mesmas roupas, mas em outras posições, por exemplo, um tipo de geração condicionada. Com ele é possível controlar BASTANTE a coerência de novas imagens, gerar uma personagem em diferentes posições, em diferentes fundos e sempre mantendo as características da personagem original.</p>
<p>O IP Adapter vai usar a foto original, manter o rosto, usar as máscaras e misturar com a foto com o rosto diferente, tirando só a roupa e depois juntar tudo no Image Composite Masked que é o Node final do processo.</p>
<p>No final, podemos comparar a primeira tentativa de colocar a roupa nova (que ficou ok mas feita, e a roupa nova, que ficou diferente mas mais bonita) e tudo integrado na foto original, mantendo o mesmo rosto.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uawsqhhr6d8kii8getzkq6ebxgh9" alt="image compare"  loading="lazy" /></p>
<p>Como falei, as qualidades são afetadas pela escolha do modelo, as configurações do KSampler, número de passos, cfg e tudo mais. Tem que experimentar e gerar várias até chegar no resultado que te agrada mais. É assim que se aprende mais também. Não é só clicar uma vez e magicamente já ter o resultado. Um bom artista experimenta dezenas de vezes.</p>
<p>A imagem final talvez não seja adequada pra ser capa da Vogue (embora eu já tenha visto muita capa oficial BEM duvidosa). Vai depender da necessidade. Em particular é bom pra prototipação. &ldquo;Como será que fica essa roupa vestida na minha modelo?&rdquo; Em vez de chamar a modelo, mandar vestir, ou gastar uma hora Photoshopando, em minutos dá pra experimentar várias roupas diferentes e ver se a modelo combina com a roupa, sem precisar gastar tempo. Pra um fotógrafo só mostrar pro cliente, antes de gastarem dinheiro com estúdio, ajudantes, equipamento, etc. Dá pra visualizar rapidamente e discutir detalhes muito mais rápido.</p>
<p>É nesse tipo de coisa que ferramentas como essa podem ajudar muito e diferenciar um artista do outro, não substituindo o artista, mas criando mais opções. E o principal: todos esses workflows são modificáveis, você reprograma e combina com outros como quiser. O limite é a criatividade.</p>
]]></content:encoded><category>midia-generativa</category><category>modelos-locais</category></item><item><title>Usando I.A (ComfyUI) pra gerar NPCs em desenvolvimento de games</title><link>https://akitaonrails.github.io/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games/</guid><pubDate>Wed, 23 Apr 2025 16:25:00 -0300</pubDate><description>&lt;p&gt;Eu andei atualizando BASTANTE meu projetinho de Comfyui com Docker Compose. Eu acho que é o setup mais completo e testado de todos, com a vantagem de ser em Docker, então é repetitível com zero setup. Leia &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener"&gt;meu post&lt;/a&gt; pra mais detalhes sobre como ele funciona.&lt;/p&gt;
&lt;p&gt;Resolvi documentar neste post sobre um dos workflow mais complexos que eu testei que funciona nesse meu setup: como desenvolver Character Sheets de personagens aleatórios, em qualquer estilo, seja realista, seja estilo pixar, seja estilo cartoon, o que você quiser. O resultado é um sheet como no exemplo abaixo. Em várias poses, várias iluminações, várias expressões de emoção, com índice de cor, tudo gerado do absoluto zero pela &amp;ldquo;I.A.&amp;rdquo;. Em meia hora de processamento na minha RTX 4090.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Eu andei atualizando BASTANTE meu projetinho de Comfyui com Docker Compose. Eu acho que é o setup mais completo e testado de todos, com a vantagem de ser em Docker, então é repetitível com zero setup. Leia <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"target="_blank" rel="noopener">meu post</a> pra mais detalhes sobre como ele funciona.</p>
<p>Resolvi documentar neste post sobre um dos workflow mais complexos que eu testei que funciona nesse meu setup: como desenvolver Character Sheets de personagens aleatórios, em qualquer estilo, seja realista, seja estilo pixar, seja estilo cartoon, o que você quiser. O resultado é um sheet como no exemplo abaixo. Em várias poses, várias iluminações, várias expressões de emoção, com índice de cor, tudo gerado do absoluto zero pela &ldquo;I.A.&rdquo;. Em meia hora de processamento na minha RTX 4090.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/469eyg4a4pl3ry0z12twg74nlbc8" alt="NPC Design Sheet"  loading="lazy" /></p>
<p>Imagine que você é estag em estúdio de jogos. Seu trabalho: criar 100 personagens de fundo (NPCs). No fim tem que gerar 100 desses. Mas só porque é personagem secundário, não pode fazer de qualquer jeito, tem que ser todos coerentes, em todas as poses. Imagina quantos dias você iria levar. E se desse pra automatizar? É pra esse tipo de cenário que I.A. é interessante. Gaste mais tempo nos personagens principais, faça manualmente, mas não perca tempo nos secundários e não baixe a qualidade final de tudo.</p>
<p>Eu não fiz esse workflow, quem fez foi o Mickmumpitz. Assista o video inteiro dele pra saber como funciona esse workflow, não vou repetir o que ele já explica:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/grtmiWbmvv0"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Também não vou compartilhar o workflow dele, porque ele tem um Patreon pago (é super barato, só USD 10 por mês), que te dá acesso a baixar o pacote de workflow inteiro dele <a href="https://www.patreon.com/posts/free-workflows-120405048"target="_blank" rel="noopener">neste link</a></p>
<p>Mas só isso não adianta, os requerimentos que você precisa:</p>
<ul>
<li>uma máquina parruda com pelo menos uma RTX 3090 com 24GB de VRAM. Importante aqui é VRAM, os modelos usados fácil ultrapassam uso de 20GB. CPU é menos importante, eu uso uma 7950X3D mas uma 7800 já daria conta.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/730safwxqkrsgawu4liw42scoul3" alt="GPU VRAM"  loading="lazy" /></p>
<ul>
<li>caso não tenha máquina assim, dá mais trabalho mas você pode alugar uma na <strong>RUNPOD</strong>. Eles tem várias configurações de GPU, mas você vai precisar saber configurar um pouco Linux ou escolher Windows e fazer o tutorial do Mickmumptiz tudo na mão (vai por mim, você vai sofrer se fizer assim).</li>
<li>precisa ter pelo menos <strong>500GB</strong> de espaço disponível e internet boa pra baixar. Na primeira vez que meu Docker rodar, ele baixa automaticamente todas as extensions de ComfyUI e todos os modelos pra tudo. Dá pra editar o arquivo <code>models.conf</code> antes e remover modelos que sabe que nunca vai usar. Mas só este workflow exige pelo menos uns 30GB de modelos, não tem como fugir disso.</li>
<li>esse workflow é particularmente <strong>PESADO</strong> porque são várias funcionalidades (que podemos desligar partes, pra customizar pro nosso fluxo de trabalho). Mas pra 1 personagem, o fluxo inteiro pode levar <strong>30 MINUTOS</strong>. Mesmo numa RTX 4090 como a minha.</li>
</ul>
<p>Se fez tudo certo até este ponto, tem ComfyUI rodando com todas as dependências já baixadas e comprou o Workflow. Quando carregar vai ter que consertar algumas coisas manualmente.</p>
<p>Primeira coisa é que vai ver um erro em vários Nodes em vermelho, todos de <strong>Ultimate SD Upscale</strong>, basta clicar com o botão direito do mouse em cada um e escolher &ldquo;Fix Recreate&rdquo; que vai consertar automaticamente:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/djblur5g36mw9denng0ht0vvksym" alt="Fix Upscale"  loading="lazy" /></p>
<p>O ComfyUI exige que cada slot de output de um Node se ligue no slot de input de outro Node compatível, mas frequentemente os workflow ficam tão grandes que tem fiozinho conectando coisa espalhado pra tudo que é canto e fica bem difícil de saber o que liga no que. Literalmente fica <strong>macarrônico</strong>.</p>
<p>Mas existe uma extension (que eu já deixei pré-instalado) chamado <a href="https://github.com/chrisgoringe/cg-use-everywhere"target="_blank" rel="noopener">cg-use-everywhere</a>. Toda extension é um projetinho do GitHub, clonado no sub-diretório <code>custom_nodes</code>. Ele permite conectar a saída de Node nesse node <strong>Anything Elsewhere</strong>, digamos a saída &ldquo;clip&rdquo; do Node &ldquo;Load Clip&rdquo;. Agora, todo outro Node que tiver como input o campo &ldquo;clip&rdquo; e não tiver nada conectando explicitamente nele, vai usar esse Anything que foi criado. Funciona super bem.</p>
<p>Só nesse workflow do Mick que, sei lá porque, todos os Anywheres estão quebrados. Mas é fácil consertar. Basta procurar esses Nodes, apagar, criar novos e reconectar. Tem vários logo no começo, anote quem liga em cada um, apague e crie um novo pra reconectar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/kjjo7u6g58drww1v5f8v88h1iis1" alt="anywhere initial"  loading="lazy" /></p>
<p>Tem também alguns no grupo 02 de Upscale:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qrkdxs6qjdtmdjs3c463oxetbgyy" alt="anywhere upscale"  loading="lazy" /></p>
<p>Nodes pequenos, só com o título, estão COLAPSADOS. Só clicar com o botão direito e escolher EXPAND pra ver os detalhes. É outra forma de organizar a bagunça.</p>
<p>Por último, tem que rechecar o nome correto dos modelos em todos os Nodes de &ldquo;Load&rdquo; alguma coisa logo no começo do workflow (ainda bem que ele organizou esses nodes tudo junto):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/y1u93ujz3kq4hz3pik5q4wcgapoa" alt="model path"  loading="lazy" /></p>
<p>No workflow dele, o Node vem procurando por &ldquo;FLUX/flux1-dev-fp8.safetensors&rdquo; mas aí você vai na lista (que puxa o que está mesmo no diretório &ldquo;models/checkpoints&rdquo; na sua máquina) e escolhe o que tem o mesmo nome. Isso acontece frequentemente porque diferentes pessoas organizam sub-diretórios diferentes pros modelos. Mas depois de configurar é só salvar e pronto. Tem que fazer isso pro checkpoint, controlnet, ic-light, etc.</p>
<p>Os erros do ComfyUI não são amigáveis. São stacktraces de Python, que não são amigáveis porque ele mostra que linha do código deu pau, mas não mostra o que tinha nas variáveis passadas pra função que deu pau. Daí não dá pra saber onde foi. Mas normalmente na interface web, o Node fica em vermelho, daí você checa e normalmente é porque esqueceu de baixar o modelo ou escolher o certo da lista.</p>
<p>Última coisa nesse workflow é baixar a image de OpenPose da página de Patreon dele, que linkei no começo do post, e carregar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uajcc6ocnr5llzv8wzoowl85c5o3" alt="openpose image"  loading="lazy" /></p>
<p><strong>OpenPose</strong> é um formato de arquivo padrão que descreve poses, formato aberto que pode ser editar em várias ferramentas. E tem diversos modelos pré-treinados pra extrair poses nesse formato a partir de imagens, então nem precisamos editar na mão. Podemos baixar várias poses pré-prontas e abertas neste site:</p>
<p><a href="https://openposes.com/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ntvaf9vba1rugg11qn3n14u2t69e" alt="Poses Sites"  loading="lazy" /></a>
Vou repetir várias vezes. mas <strong>assistam o video tutorial do Mick</strong> onde ele explica como mandar gerar personagens do zero ou usar <strong>suas próprias fotos</strong> com suas próprias poses pra continuar o fluxo a partir disso. O workflow dele, depois que roda, tem esta cara (com zoom lá embaixo):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/jqu4o2c5cenci6au2g8bbny064mm" alt="complete workflow"  loading="lazy" /></p>
<p>Na primeira parte, tem a configuração e checagem manual dos modelos. Só tem que arrumar uma vez e salvar. Depois sempre vai funcionar igual. Nesse primeiro menu também dá pra ligar e desligar alguns dos grupos de Nodes, por exemplo, pra pular a primeira etapa de gerar personagem do zero e poder usar suas próprias fotos:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/s72mrc1ib5o173c9l74vmpsn3js3" alt="menu and setup"  loading="lazy" /></p>
<p>Se optar por gerar personagem do zero, tem em verde uma caixa de prompt pra digitar como quer que seja sua personagem, em que estilo. Prompts de Texto são decodificados por modelos de <strong>CLIP</strong> (Contrastive Language Image Pre-Training), que são específicos pra determinadas categorias de UNETs (como SDXL pra Stable Diffusion ou o melhor e mais popular FLUX agora). Parte do aprendizado é começar a aprender quais sub-modelos (clips, clip_vision, vaes, loras, etc) são compatíveis com quais unets (unet, checkpoints, diffusion_models). Não pode misturar qualquer coisa. Grosseiramente, é meio que se você escolhe fazer um texto em alemão, todos os plugins tem que ser em alemão.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/37oe4dyqr5t0in9ad98bocli6u3w" alt="Character Generation"  loading="lazy" /></p>
<p>Não vou mostrar o workflow completo porque nem cabe, mas dá pra ver em verde os prompts, no meio onde escolhemos que pode queremos (tá no Patreon dele), e em cima a image gerada pelo modelo <strong>flux1-dev-fp8</strong>, um dos melhores e mais populares por ter excelente qualidade e não ser tão pesado (fp8 em vez de fp32, por exemplo).</p>
<p>Isso é uma das vantagens de usar ferramentas fora dos Dalle ou Midjourney da vida: podemos controlar todos os aspectos da geração da imagem não só com prompts de texto, mas com prompts de imagens, como o OpenPose.</p>
<p>Outro detalhe. Expliquei no meu post anterior sobre KSampler ou Samplers em geral, que são o motor que faz todo o trabalho de juntar todos os modelos, imagens, prompts, controlnet, vaes e produz a imagem final. Os parâmetros do KSampler como steps, cfg, denoise, scheduler e mais <strong>DEPENDEM</strong> do modelo escolhido. Tem modelos bem documentados, tem modelos mal documentados.</p>
<p>Por exemplo, digamos que você leu numa thread de Reddit sobre esse outro modelo mais otimizado chamado <a href="https://civitai.com/models/215418?modelVersionId=273102"target="_blank" rel="noopener">&ldquo;TurboVisionXL&rdquo;</a>, que está disponível no site Civit.ai:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/hpcj5e8g0hxvefelvszwf48vsnpf" alt="TurboVisionXL"  loading="lazy" /></p>
<p>Realmente é um bom modelo mas e agora? O que eu configuro no KSampler pra ele? Felizmente se scrolar a página (sempre leia a documentação original!!) vai encontrar estas recomendações:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/57f9vyfh4w9j8bduaomc3aow2sks" alt="TurboVisionXL documentation"  loading="lazy" /></p>
<p>Viu só? Ele explica o que configurar e quais são limitações conhecidas. Mais embaixo vai ter comentários de outras pessoas que podem dar mais dicas ou compartilhar erros que você já tenha visto. É uma enorme comunidade, precisa aprender a participar dela.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7avk9tl1pop1eexkuu3h8epgs2t5" alt="MultiView"  loading="lazy" /></p>
<p>Mas voltando da tangente, no segundo grupo de nodes do Mick, ele pega a primeira imagem em &ldquo;Pose T&rdquo; e passa por um processo de &ldquo;Multi View&rdquo; usando Diffuser MV que faz isso automaticamente. É a mesma pose, mas como se a câmera desse um 360 graus ao redor dela, pegando ângulos diferentes e, principalmente, gerando novas imagens coerentes com a original (veja como o modelo é usado até pra inventar as costas da personagem).</p>
<p>Nesse passo também ele extrai só o rosto da primeira imagem pra fazer outros tratamentos em paralelo. Reconhecimento e extração de rostos é super comum. Todo sistema de segurança usa, Google Photos usa pra organizar álbuns dinâmicos. E vale você aprender pra usar em seus produtos. É um processo razoavelmente leve e rápido hoje em dia e tem vários algoritmos e modelos pré-prontos pra usar. Nesse caso acho que nem usa I.A. propriamente dito, é um algoritmo mesmo.</p>
<p>Outro detalhe: modelos de geração de imagens normalmente trabalham em <strong>BAIXA RESOLUÇÃO</strong>. Normalmente abaixo de 1024x1024. Não espere gerar imagens 4K logo de cara. Mas não tem problema, porque a solução é primeiro gerar uma image de baixa resolução e depois passar por outro modelo de I.A. especializado em <strong>UPSCALING</strong> como o RealESRGAN que já falei no meu outro post sobre <a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">upscaling de anime</a></p>
<p>Diferente do que faz um Photoshop ou qualquer editor de imagens, se tentar aumentar uma imagem, ele só vai pegar um pixel e tentar duplicar, quadruplicar os pixels ao redor. Mas isso costuma só deixar a imagem maior mas borrada (blurred), porque ele não tem como saber os detalhes, e nem vai tentar &ldquo;inventar&rdquo; detalhes.</p>
<p>Pra &ldquo;inventar&rdquo; detalhes, precisa de um modelo pré-treinado que aprendeu o que desenhar. É como funciona também o DLSS 3 da NVIDIA pra games, onde o jogo renderiza em baixa resolução, como 720p e o DLSS, em tempo real, redesenha a nova tela em 4K. Não fica perfeito, mas jogando a 60fps, você raramente vai notar a diferença. Só vai perceber que ficou mais <strong>NÍTIDO</strong>.</p>
<p>Por isso também, se você usar sua própria foto, ou foto de pessoas conhecidas, vai ver que a nova foto depois de upscaling ficou <strong>levemente diferente</strong>, não é mais a mesma pessoa, porque o modelo <strong>REDESENHA</strong> detalhes que não existiam. É um processo de interpretação e não de descobrir detalhes &ldquo;escondidos&rdquo; (isso não existe, só na ficção de CSI).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2he2fgmus8vmp3wfp95s3qbh7ztf" alt="Upscale 2"  loading="lazy" /></p>
<p>O workflow do Mick vai um passo além e tem uma segunda passada de upscaling, focando em consertar somente o rosto om um Node de Face Detailer, feito especificamente pra consertar e melhorar rostos. E aqui entramos em território de cirurgia plástica e harmonização mesmo kkkk o rosto final vai ser diferente do original. Mas pra NPC e personagens secundários não importa, ninguém vai saber a diferença mesmo, basta não estar deformado, zarolho ou algo assim que salta à vista.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/3d40a8n9b32mmkub5l8c04oou37h" alt="Emotions"  loading="lazy" /></p>
<p>Existem Nodes especializados em expressões, como o Expression Editor (PHM) que parte de uma única foto de rosto e consegue gerar novas fotos nas mais variadas emoções - que podemos configurar exatamente, numericamente, como queremos. Não vou mostrar, mas note que as imagens geradas estão em baixa qualidade, mas o próximo passo do workflow é justamente fazer upscaling delas também.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cxdua4dfalu6d68tpaleljie5y1e" alt="Lighting"  loading="lazy" /></p>
<p>Esse passo é interessante. Primeiro a parte de cima, com os vários cenários. Eles foram todos gerados via os prompts em verde abaixo deles, prompts que o Mick deixou mas podemos editar como quisermos OU trocar o node de geração e colocar de Load Image pra carregar nossas próprias imagens de fundo, de fotos de verdade.</p>
<p>O objetivo desse passo é gerar os rostos sob diferentes configurações de luz. Muita gente pensa que &ldquo;luz&rdquo; em jogos é só &ldquo;acender a luz vermelha&rdquo; e todo mundo vai ficar vermelho automaticamente. Em jogos novos, com <strong>RAY TRACING</strong> sim, por isso é SUPER PESADO. Mas em jogos feitos pra máquinas antigas, jogos indie, não dá pra exigir isso de todo mundo.</p>
<p>Daí se usam &ldquo;truques&rdquo; pra falsificar coisas como luzes e sombra. É o processo de <strong>BAKING</strong> onde literalmente &ldquo;estampamos a luz na textura&rdquo; dos objetos e personagens. Com a luz global ligada ou desligada, eles sempre vão estar &ldquo;pintados&rdquo; na luz colorida da cena. Pra mudar de cena, carregam novas texturas com outras luzes &ldquo;baked&rdquo;. Pra programadores, pense em baking como <strong>Caching</strong>. Pra que toda vez recalcular tudo dinamicamente, se a luz da cena nunca muda? Economize esse passo e já deixe &ldquo;cacheado&rdquo;.</p>
<p>Voltando, esse passo usa a tecnologia <a href="https://github.com/lllyasviel/IC-Light"target="_blank" rel="noopener"><strong>IC-Light</strong></a> que é outro projeto open source que vale aprender, mesmo se não se interessa por I.A. Ele consegue manipular a luz de qualquer imagem ou foto. Você, como fotógrafo, pode mudar a luz caso tenha mudado de idéia, em pós-produção. Ferramentas da Adobe ou BlackMagic, acho que já incluem tecnologia similar, mas é bom saber que existe open source e dá pra integrar no seu produto.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qup2n6vadpc7qrtgokbqbfkpvmjg" alt="final sheet"  loading="lazy" /></p>
<p>Depois de gerar os rostos com diferentes luzes, obviamente tem outro passo de upscaling - que estou pulando de mostrar - e vai pro grupo de nodes final que junta tudo num &ldquo;sheet&rdquo;, uma &ldquo;folha do personagem&rdquo;. O desenvolvedor de jogos pode usar direto num jogo 2D (pense um visual novel game da vida), ou pode servir de base pra UV Mapping de texturas de um modelo 3D simples com Armature pra animar e já tem alguns rostos de emoções. Bom pra colocar no cenário de fundo, pedestres andando na rua, fazendo compras, sentados na praça ou algo assim.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/cgv0qp3slipgw4nrlgnrserl1n8s" alt="Loras"  loading="lazy" /></p>
<p>Além disso, o tutorial do Mick descreve como, é possível criar <strong>LORAS</strong> (Low-Rank Adaptation). Se quiser conseguir gerar personagens com estilo ou mesmo rostos específicos (sem intervenção &ldquo;artística&rdquo; da I.A.), você escolhe um modelo como FLUX ou SDXL e &ldquo;em cima&rdquo; dele adiciona um segundo sub-modelo compatível com informações sobre essa pessoa ou personagem.</p>
<p>Pra isso você tira várias fotos limpas e bem iluminadas da pessoas, nos mais diferentes ângulos, nem precisa de muito, só uma dúzia é suficiente. Pra cada imagem cria prompts que descrevam bem, e é isso. Tem ferramentas pra gerar esses Loras localmente ou sites que fazem isso (assista o video).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/tk8tbtrr1ealm4c4eu26alk63xlh" alt="Creating Prompts"  loading="lazy" /></p>
<p>Falando em prompts, em vários workflows existem passos pra descrever prompts detalhados de imagens geradas dinamicamente. Em vez de exigir que o usuário (você) faça isso manualmente, é possível usar um modelo de decoding, que decodifica imagens em prompts de texto. O modelo mais usado pra isso é o <strong>FLORENCE 2</strong> da Microsoft.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/dwr87ajtkj7w15ftsy86l5u4hz44" alt="Florence 2"  loading="lazy" /></p>
<p>Esse box de texto embaixo é read-only, você não precisa digitar manualmente. O Node de Florence 2 Run vai ler a imagem e gerar esse texto. Veja como ele descreveu a primeira imagem da mulher na pose de &ldquo;T&rdquo;. Fazendo testes, pro seu caso, você determina se só isso é suficiente, se não for basta colocar um segundo Node de prompt e concatenar os dois antes de mandar pra outros nodes.</p>
<p>Enfim, tem MUITA coisa pra explorar só nesse workflow. O Mick fez um excelente trabalho tanto no workflow quanto no video pra explicar e vale a pena pagar os 10 dólares pra contribuir. Existem dezenas de workflows assim disponíveis na Web, basta procurar. E meu setup está cada vez mais complexo. Pretendo ficar ajustando ele pra cada novo workflow interessante que esbarrar. Já tem vários pré-testados na pasta &ldquo;workflows&rdquo; do meu projeto pra você brincar.</p>
]]></content:encoded><category>midia-generativa</category><category>games</category><category>modelos-locais</category></item><item><title>[Akitando] #142 - Entendendo COMO ChatGPT Funciona - Rodando sua Própria IA</title><link>https://akitaonrails.github.io/2023/06/19/akitando-142-entendendo-como-chatgpt-funciona-rodando-sua-propria-ia/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/06/19/akitando-142-entendendo-como-chatgpt-funciona-rodando-sua-propria-ia/</guid><pubDate>Mon, 19 Jun 2023 17:47:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/O68y0yRZL1Y"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Não, AGI não está próximo de acontecer.&lt;/p&gt;
&lt;p&gt;Quais são os limites da atual tecnologia de transformadores generativos pré-treinados, vulgo GPT?
O que são os tais &amp;ldquo;modelos&amp;rdquo; que eles usam?
O que são os tais &amp;ldquo;parâmetros&amp;rdquo;.
Porque as IAs parecem humanas nas respostas?
IAs vão substituir programadores??
Como é possível conseguir rodar um clone de ChatGPT na sua própria máquina, totalmente offline?&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/O68y0yRZL1Y"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Não, AGI não está próximo de acontecer.</p>
<p>Quais são os limites da atual tecnologia de transformadores generativos pré-treinados, vulgo GPT?
O que são os tais &ldquo;modelos&rdquo; que eles usam?
O que são os tais &ldquo;parâmetros&rdquo;.
Porque as IAs parecem humanas nas respostas?
IAs vão substituir programadores??
Como é possível conseguir rodar um clone de ChatGPT na sua própria máquina, totalmente offline?</p>
<p>Vamos responder tudo isso e muito mais.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:01:07 - CAP 01 - Minha IA PESSOAL e OFFLINE - text-generation-webui</li>
<li>00:06:37 - CAP 02 - Introdução a MODELOS - Como IA Aprende?</li>
<li>00:10:09 - CAP 03 - Auto-Corretor de Teclado de Celular - Cadeias de Markov</li>
<li>00:15:34 - CAP 04 - 170 TRILHÕES de parâmetros - Maior que nosso cérebro?</li>
<li>00:20:28 - CAP 05 - De Markov a IA - O que são Tensors?</li>
<li>00:30:53 - CAP 06 - A Revolução da Auto-Atenção - O que são Transformers?</li>
<li>00:35:24 - CAP 07 - Hardware pra Treinar IA - NVIDIA</li>
<li>00:42:09 - CAP 08 - Parâmetros e Sinapses são equivalentes? - Quantização</li>
<li>00:49:42 - CAP 09 - Estamos próximos de AGI? IAs são Inteligentes?</li>
<li>00:52:10 - CAP 10 - Controlando Criatividade de IAs - Temperatura</li>
<li>01:00:27 - CAP 11 - A Avó do ChatGPT - Eliza</li>
<li>01:04:29 - CAP 12 - IAs que fazem Imagens - Diffusion</li>
<li>01:09:31 - CAP 13 - Discutindo PROMPTs - LangChain</li>
<li>01:13:08 - CAP 14 - IAs vão matar programadores? Quais os Limites?</li>
<li>01:22:15 - CAP 15 - Virando &ldquo;Programador 10x&rdquo; com auxílio de IAs - Copilot</li>
<li>01:23:47 - CAP 16 - CONCLUSÃO - O começo da BOLHA</li>
<li>01:28:18 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li><a href="https://ai.facebook.com/blog/large-language-model-llama-meta-ai/"target="_blank" rel="noopener">Introducing LLaMA: A foundational, 65-billion-parameter language model (facebook.com)</a></li>
<li><a href="https://github.com/NVIDIA/Megatron-LM"target="_blank" rel="noopener">NVIDIA/Megatron-LM</a></li>
<li><a href="https://github.com/oobabooga/text-generation-webui"target="_blank" rel="noopener">oobabooga/text-generation-webui</a></li>
<li><a href="https://huggingface.co/TheBloke/Wizard-Vicuna-30B-Uncensored-GPTQ"target="_blank" rel="noopener">TheBloke/Wizard-Vicuna-30B-Uncensored-GPTQ</a></li>
<li><a href="https://www.youtube.com/playlist?list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi"target="_blank" rel="noopener">Neural networks - YouTube</a></li>
<li><a href="https://towardsdatascience.com/gpt-4-will-have-100-trillion-parameters-500x-the-size-of-gpt-3-582b98d82253"target="_blank" rel="noopener">GPT-4 Will Have 100 Trillion Parameters — 500x the Size of GPT-3 | by Alberto Romero | Towards Data Science</a></li>
<li><a href="http://introtodeeplearning.com/"target="_blank" rel="noopener">MIT Deep Learning 6.S191 (introtodeeplearning.com)</a></li>
<li><a href="https://www.youtube.com/watch?v=i9ikne_9iEI&amp;t=1646s"target="_blank" rel="noopener">DF Direct Special: Starfield Tech Breakdown - 30FPS, Visuals, Rendering Tech + Game Impressions - YouTube</a></li>
<li><a href="https://www.youtube.com/watch?v=It9D08W8Z7o&amp;t=102s"target="_blank" rel="noopener">NVIDIA Made a CPU.. I’m Holding It. - YouTube</a></li>
<li><a href="https://twitter.com/miolini/status/1634982361757790209"target="_blank" rel="noopener">(2) Artem Andreenko 🇺🇦 on Twitter: &ldquo;I&rsquo;ve sucefully runned LLaMA 7B model on my 4GB RAM Raspberry Pi 4. It&rsquo;s super slow about 10sec/token. But it looks we can run powerful cognitive pipelines on a cheap hardware. https://t.co/XDbvM2U5GY&rdquo; / Twitter</a></li>
<li><a href="http://scihi.org/emile-borel-infinite-monkey-problem/"target="_blank" rel="noopener">Émile Borel and the Infinite Monkey Problem | SciHi Blog</a></li>
<li><a href="https://github.com/codeanticode/eliza"target="_blank" rel="noopener">GitHub - codeanticode/eliza: The classic Eliza psychologist program, implemented as a Processing library.</a></li>
<li><a href="https://www.instagram.com/stories/highlights/18020038894467644/"target="_blank" rel="noopener">Stories • Instagram</a></li>
<li><a href="https://huggingface.co/TheBloke"target="_blank" rel="noopener">TheBloke (Tom Jobbins) (huggingface.co)</a></li>
<li><a href="https://python.langchain.com/docs/get_started/introduction.html"target="_blank" rel="noopener">Introduction | 🦜️🔗 Langchain</a></li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Este é um episódio que eu tava tentando evitar fazer. Primeiro porque acho que vai ser um dos temas onde parte do video vai acabar ficando obsoleto meio rápido, já que as tecnologias de IA estão acelerando e mudando bastante ainda. Segundo, porque eu mesmo nunca trabalhei com IA. Maioria das coisas que explico neste canal, já me envolvi em projetos reais de alguma forma, por isso não é só teoria, é experiência. Mas IA, eu brinquei, mas nunca trabalhei nem fiz pesquisa acadêmica, nem nada disso, por isso nunca me senti adequado pra explicar.</p>
<p>Mas considerando que quase a totalidade de videos feitos sobre o assunto hoje são de pessoas com menos conhecimento ainda, fazendo afirmações cada vez mais absurdas; até eu certamente consigo fazer muito melhor. Pra variar, os oportunistas já saíram lançando cursos e afins. No fim do video vou explicar porque todos são uma droga e você deve evitar. Também vou explicar de novo porque ela não vai substituir programadores. Hoje vou explicar o que de fato é um ChatGPT e onde estamos quando se fala em IA. Então vamos lá.</p>
<p>(&hellip;)</p>
<p>Esses dias resolvi brincar de IA, o objetivo era ter o meu próprio ChatGPT, rodando offline, totalmente local, na minha máquina, sem conectar com nenhuma API de terceiros como da OpenAI. Isso ficou fácil porque agora existem diversos esforços da comunidade de código aberto, em particular projetos de Open LLM, ou Large Language Models, que é a categoria de IA onde se encontra um GPT.</p>
<p>Esses esforços ganharam força quando a Meta decidiu abrir seu modelo Llama em fevereiro de 2023. As big tech tem diversos modelos prontos, cada um pra algum uso específico, desde respostas gerais ou mais focados em código e assuntos específicos. Por exemplo, a OpenAI tem os modelos GPT 3.5, GPT 4, Codex. A Microsoft tem o Zero, ou Megatron em conjunto com a NVIDIA. O Google tem modelos como o BERT, Palm, LaMDA, Minerva e outros.</p>
<p>A Meta, de novo, tem o OPT, Galactica, METALM, LLaMA. Alguns desses modelos são citados em papers mas são fechados, como o novo BARD do Google ou o próprio GPT 4 da OpenAI. Mas o lançamento do LLaMA ao público foi um evento importante e a Meta parece que liberou vários outros como o OPT e Galactica. A primeira parte do video de hoje vai ser entender o que diabos são esses modelos.</p>
<p>Isso foi em fevereiro, este video tá saindo em junho, só 4 meses. E de lá pra cá a comunidade usou o LLaMA pra criar derivados mais otimizados e menores pra rodar em computadores menos parrudos. Surgiram diversas variantes como Alpaca, Vicuna, GPT4ALL, Koala, Dolly e dezenas de outros. Literalmente dezenas. O site HuggingFace, que é um repositório de modelos e ferramentas pra inteligência artificial, tem listado uns 200 modelos diferentes.</p>
<p>O que dá pra fazer com esses modelos abertos? Deixa eu mostrar. Eu queria um ChatGPT pessoal, offline, que ninguém sabe o que tô conversando com ele, sem filtros, sem controle, sem nada. Só eu e a ferramenta. Existem vários projetos abertos que implementam interface web similar ao ChatGPT, onde posso digitar perguntas, ver as respostas da IA, e ficar conversando com ela, sem precisar estar conectado na internet. Olha só esse exemplo.</p>
<p>(&hellip; screencast text-generator &hellip;)</p>
<p>Se assistiram meu episódio sobre games em máquina virtual, sabem que tenho uma máquina que alguns consideram &ldquo;parruda&rdquo;, um AMD 5950X de 16 cores, 32 threads, rodando a uns 4 gigahertz por core. Mais 64 gigabytes de RAM DDR4. Uma GPU NVIDIA RTX 3090 com 24 gigabytes de memória GDDR5. Fora meu NAS Synology de 60 terabytes, conectado em rede 10 gigabits. Meus testes foram feitos numa máquina virtual QEMU rodando Ubuntu normal, com passthrough de PCI pra ter acesso direto à GPU NVIDIA. Todos os detalhes sobre essa QEMU eu mostrei no video de Games em Máquina Virtual.</p>
<p>Sem nenhum motivo em particular, escolhi o projeto aberto &ldquo;text-generation-webui&rdquo; que é uma aplicação web escrita em Python. Ela simula a interface web de chat do ChatGPT. Por baixo, carrega bibliotecas como bitsandbytes, pytorch e outros que vou explicar depois. Daí podemos baixar modelos e fazer ele carregar um deles. No caso estou rodando Vicuna 30b quantizado. De novo, já vou explicar o que isso significa. Dizem que tem qualidade parecida com GPT 4 mas pra mim pareceu mais um GPT 3. As respostas que consigo no GPT 4 ainda são mais completas do que nesse Vicuna.</p>
<p>Mas mesmo assim é impressionante. Estão vendo? Estou conversando de boas. E não está conectando com nenhum serviço online de ninguém, nem da Microsoft, nem da OpenAI, nem do Google. Tudo rodando offline, local, dentro da minha máquina virtual. Vou repetir, porque quando postei sobre isso no Instagram, muita gente ficou confusa. Sim: dá pra rodar um programa similar ao serviço de ChatGPT da OpenAI num computador normal. E não se trata de uma demonstração hello world, realmente funciona. Não precisa de um monte de servidores parrudos pra conseguir isso. Ué, mas eu achava que precisava ter o tamanho de uma Microsoft ou Google pra fazer isso, o que mudou?</p>
<p>A exigência de hardware depende da complexidade do modelo. Como falei, estou usando Vicuna de 30 bilhões de parâmetros, mas existem modelos menores, como o próprio Vicuna de 7 bilhões de parâmetros, ou a Ada da Microsoft, de só 350 milhões. Se o modelo for pequeno o suficiente, é possível até rodar num bom smartphone Android ou um Raspberry Pi com um upgrade de RAM. O tamanho do modelo é um dos fatores que pode afetar a qualidade das respostas, portanto quanto menor o modelo, mais simples seriam as respostas. Simplifiquei sobre &ldquo;tamanho&rdquo;, guardem essa informação que já vou explicar mais.</p>
<p>Deixa recapitular um pouco. O que diabos é isso de modelo? Honestamente, eu mesmo não sei dizer em todos os detalhes, pra isso precisaria ter estudo e treinamento em inteligência artificial, em particular redes neurais. Muita gente aprendeu isso em optativas ou iniciação científica na faculdade de ciências da computação. Não vou tentar dar explicação acadêmica, mas sim dar uma intuição pra maioria de vocês conseguirem ter uma imagem na cabeça. Acadêmicos, sejam compreensivos, e se quiserem complementar, sintam-se a vontade nos comentários abaixo.</p>
<p>Pense em redes neurais como uma simulação do aprendizado que acontece no nosso cérebro. Sabemos que temos neurônios. Aprendizado e memória acontecem quando temos sinapses, conexões desses neurônios, ou comunicação, ou melhor &ldquo;ativação de um neurônio&rdquo;, que é como se ele escolhesse um caminho dentre muitos. Um neurônio pode ter milhares de conexões, dizem que umas 7 mil. De novo, explicação grosseira. Mas é mais ou menos isso que temos em redes neurais. Assista os videos do canal 3Blue1Brown sobre redes neurais pra entender em mais detalhes.</p>
<p>No caso específico de texto, poderíamos pensar que a forma de fazer computadores aprenderem a interpretar e gerar texto seria cadastrando regras gramaticais, ortográficas, vocabulário, dicionários, e assim ele conseguiria construir frases gramaticalmente corretas. Mais ou menos como você pensa que é o jeito certo de aprender uma língua nova como inglês ou francês num curso qualquer. Esse parece ser o jeito intuitivo, certo?</p>
<p>Mas se assistiu meu video de Como eu Aprendi Inglês e a minha live com o Matt sobre aprender japonês, já sabemos que não é assim. Não se trata de decorar dúzias de regras. Nem no aprendizado em geral, nem em inteligência artificial. Não existem &ldquo;regras&rdquo; como um monte de &ldquo;ifs&rdquo;. Pense por dois segundos: quando foi a última vez que você escreveu um texto 100% gramaticalmente e formalmente correto? Concorda que um texto assim seria super estranho? Sem gírias, sem maneirismos, sem coloquialismos, com palavras consideradas rebuscadas. Exatamente o que associamos com um robô falando.</p>
<p>Já falei isso nesses outros videos, mas vou repetir: como você aprendeu português? Foi lendo um livro do tamanho da Bíblia, lotado de regras gramaticais? Quando tinha 1 ou 2 anos de idade? Como um bebê que nasceu nos Estados Unidos aprendeu inglês? Como um bebê que nasceu na China aprendeu chinês? Nenhum deles usou nenhum livro, nenhum curso, nenhuma regra.  Simplesmente passaram um tempão ouvindo os pais e pessoas ao redor e começaram a repetir o que ouviam. Bem errado no começo.</p>
<p>Todo mundo vai dando feedback. Quando o bebê tinha uma intenção, sei lá, dizer que está com fome. Ele tentava juntar palavras que já tinha ouvido antes e que parecia descrever o que queria. Se os adultos ao redor dessem comida, é o feedback que o que falou parece que faz sentido. E assim ele vai associando as combinações de palavras com comportamentos. Vai refinando seu aprendizado. Fazendo novas sinapses, novas conexões. Pouco a pouco melhorando a comunicação e se fazendo entender melhor. </p>
<p>A grosso modo é como seria o que chamamos de treinamento supervisionado, no mundo de inteligência artificial. Nós não programamos regras gramaticais nem cadastramos palavras num banco de dados manualmente e ficamos fazendo &ldquo;ifs&rdquo; pra montar frases. Em vez disso começamos com um corpo de dados gigante. Por exemplo, todos os artigos da Wikipedia, todos os códigos abertos disponíveis no GitHub, todos os papers acadêmicos disponíveis publicamente, todos os livros digitalizados num Google Books. Bastante texto. Dezenas ou centenas de gigabytes de texto puro.</p>
<p>Pra entender isso, deixa eu fazer uma tangente e explicar um conceito relacionado que não é, em si só, inteligência artificial, mas faz parte da matéria. Na faculdade de ciência da computação, se aprende sobre processos estocásticos, que estuda aleatoriedade, probabilidades, ou melhor, a evolução de um sistema ou fenômeno ao longo do tempo de forma probabilística. Ele descreve o comportamento de um sistema ou quantidades que mudam aleatoriamente ao longo do tempo. Em particular quero falar de Cadeias de Markov.</p>
<p>Não estou falando que o ChatGPT não é uma Cadeia de Markov, é só pra ilustrar um ponto. Cadeias de Markov é uma das formas de representar e analisar sequências de eventos, ou estados, onde a probabilidade de transicionar de um estado pra outro depende somente do estado atual, é um sistema sem memória ou backtracking, sem considerar a sequência de todos os estados anteriores, só o último. Em resumo é um conjunto de estados e probabilidades de transição. Se pareceu grego, vamos ver um exemplo prático.</p>
<p>Digamos que em vez de ter gigabytes de textos como descrevi antes, nosso corpo de treinamento sejam só 3 frases em português: &ldquo;Eu gosto de comer maçãs.&rdquo;, daí &ldquo;Ela gosta de jogar tênis.&rdquo; e finalmente &ldquo;Ele prefere ler livros.&rdquo;. Podemos construir um modelo baseado nessas frases, onde os estados são palavras, ou tokens, e as transições entre estados representam a probabilidade de mover de um token pra outro. Vamos construir esse modelo simplificado.</p>
<p>Os tais estados podem ser só as palavras, tokenizadas ou quebradas a partir dessas frases, daí teríamos essa lista de palavras, &ldquo;Eu&rdquo;, &ldquo;gosto&rdquo;, &ldquo;de&rdquo; etc. </p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>1. Eu
2. gosto
3. de
4. comer
5. maçãs
6. Ela
7. gosta
8. jogar
9. tênis
10. Ele
11. prefere
12. ler
13. livros</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>As transições são as probabilidades. Esses são exemplos, mas digamos que a transição do token, ou estado &ldquo;Eu&rdquo; pro estado &ldquo;gosto&rdquo; é probabilidade 1, ou seja, 100%. Mas a transição do estado &ldquo;de&rdquo; pra &ldquo;comer&rdquo; é 0.5 ou 50% porque poderia ser pra &ldquo;jogar&rdquo; que é 0.5 também. Pra ficar claro, na primeira frase temos &ldquo;de comer&rdquo;, mas na segunda frase tempos &ldquo;de jogar&rdquo;, por isso a partir do token &ldquo;de&rdquo; temos duas possibilidades, 50% de chance pra cada.</p>
<p>Como temos poucas frases de treino, as transições são quase 100% de uma palavra pra outra, porque esse modelo só conhece 3 frases. Num treinamento de verdade com gigabytes de textos, teríamos trilhões de possibilidades diferentes e probabilidades pequenas e fracionadas, como 0.001234 bla bla. </p>
<p>Finalmente, digamos que começamos a digitar um texto e queremos que esse modelo continue completando a frase pra gente. Podemos usar o modelo da cadeia de Markov pra prever o que seria a palavra mais provável baseado nas probabilidades de transição que vimos na lista anterior. Por exemplo, começo digitando &ldquo;Eu&rdquo; e a probabilidade da próxima palavra ser &ldquo;gosto&rdquo; é 100%, então é isso que ele dá de previsão. Se digitar &ldquo;Ele&rdquo;, a probabilidade segundo a lista é 100% pra &ldquo;prefere&rdquo;.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>- De &#34;Eu&#34;: gosto (1.0)
- De &#34;gosto&#34;: de (1.0)
- De &#34;de&#34;: comer (0.5), jogar (0.5)
- De &#34;comer&#34;: maçãs (1.0)
- De &#34;maçãs&#34;: Eu (1.0)
- De &#34;Ela&#34;: gosta (1.0)
- De &#34;gosta&#34;: de (1.0)
- De &#34;de&#34;: jogar (0.5), ler (0.5)
- De &#34;jogar&#34;: tênis (1.0)
- De &#34;tênis&#34;: Ela (1.0)
- De &#34;Ele&#34;: prefere (1.0)
- De &#34;prefere&#34;: ler (1.0)
- De &#34;ler&#34;: livros (1.0)
- De &#34;livros&#34;: Ele (1.0)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Por causa de smartphones, todo mundo já viu isso em ação de verdade. É a funcionalidade de auto-correção que tem em todo teclado. Olhe neste exemplo, começo digitando uma palavra e o teclado sozinho vai sugerindo a próxima palavra, e podemos só aceitar a sugestão. E ele vai sozinho completando a frase. Claro, se ficar fazendo só assim, a frase vai ficando meio sem sentido nenhum, mas ele consegue gerar uma frase que mais ou menos parece um humano que escreveu, não acha?</p>
<p>Vou repetir: isso é uma explicação simplificada, tem várias outras técnicas em cima de cadeias de Markov, mesmo pra um tecladinho simples de iOS ou Android. Mas em linhas gerais, pense que em vez de 3 frases, o modelo desses teclados foi pré-treinado com milhares de frases. O modelo é essa lista de combinações de palavras e as probabilidade da próxima palavra, dada uma palavra anterior. Essas probabilidades é o que chamamos de &ldquo;pesos&rdquo;. E mais importante: em nenhum momento usamos quaisquer regras hard-coded de gramática ou ortografia ou ifs ou templates. Ele vai completando a frase puramente usando esses pesos, aprendidos no treinamento, nada mais.</p>
<p>Agora vamos voltar pro ChatGPT ou pra minha versão local do text-generation com Vicuna. Vocês nunca acharam estranho que as respostas sempre demoram e ele vai escrevendo uma palavra de cada vez? Alguns poderiam achar que é só uma animação arbitrária pra fazer parecer que o ChatGPT é uma pessoa digitando. Mas deixa eu rodar uma versão fora da interface Web, na linha de comando mesmo. Prestem atenção.</p>
<p>(&hellip; screencast command line fastchat &hellip;)</p>
<p>Pra ficar mais claro, vou colocar do lado o monitoramento da minha GPU, a ferramenta da nvidia chamado &ldquo;nvidia-smi&rdquo; que faz o monitoramento dos recursos sendo usados na GPU. Notem que durante a composição da resposta, a GPU está em uso constante, processando alguma coisa, sem parar. Não sei porque usa só 50% do processamento disponível, mas de qualquer forma, a resposta não é instantânea. Não é uma animação feita só pra fazer graça, é que ele demora isso mesmo palavra a palavra. Cada palavra nova que vai aparecendo está gastando processamento da GPU. Se levou 5 segundos pra dar a resposta, foi 5 segundos que a minha GPU ficou processando sem parar.</p>
<p>Conseguem ver as similaridades entre o auto-corretor do seu tecladinho de celular e o processo de resposta do ChatGPT? Internamente ele está fazendo algo similar a procurar probabilidades na cadeia de Markov. Mas claro, o modelo de GPT, LLaMA, Vicuna, Bard, e outros, é mais complicado que um mero modelo de Markov. Vamos entender.</p>
<p>Quando as primeiras notícias anunciando o ChatGPT saíram, gerou muita confusão, que persiste até hoje. Por exemplo, quando o ChatGPT 4 foi anunciado, eles mencionam, &ldquo;uau, o ChatGPT 3.5 tinha 175 bilhões de parâmetros mas o ChatGPT 4 tem incríveis 100 a 170 TRILHÕES de parâmetros&rdquo;. A única coisa que os jornalistas e vocês entenderam foi &ldquo;uau, bilhões pra trilhões de &hellip; whatever &hellip; trilhões é absurdamente maior que bilhões, então o GPT novo é milhões de vezes melhor e o GPT 5 vai ser mais milhões de vezes melhor&rdquo;. E é assim que todo mundo mente e se auto-engana com números, sem saber o que significa.</p>
<p>Esse meu Vicuna, rodando localmente na minha máquina, tem meros 30 bilhões de parâmetros. Puuuts, quer dizer que ele deve ser pelo menos 5 vezes pior que o ChatGPT antigo, né? Nem chega aos pés do GPT 4. Só que se olhar alguns artigos que descrevem o Vicuna menor, o de 13 bilhões de parâmetros, muitos declaram que rodando diversos testes, os mesmos que a própria OpenAI usa pra avaliar o GPT deles, dizem que o Vicuna 13B chega a 90% do nível de qualidade do GPT 4 ou Google Bard. E o Vicuna, sendo derivado do LLaMA do Facebook, nesses mesmos testes, ultrapassa o LLaMA original. Como pode isso?</p>
<p>Antes de mais nada, o que diabos são esses tais parâmetros? No contexto de machine learning, parâmetros se referem aos pesos que o modelo aprende durante o processo de treinamento. De forma simplificada e grosseira, lembra a lista de probabilidades de transição de estados da cadeia de Markov que mostrei no exemplo? Aquilo poderíamos chamar de parâmetros. No nosso treinamento com só 3 frases, gerou um modelo de 16 parâmetros. Pense numa lista como aquela só que bilhões de linhas. 13 bilhões ou 30 bilhões no caso do Vicuna, ou 170 trilhões no caso do ChatGPT 4.</p>
<p>E lembra como no exemplo, dado uma palavra, podemos ir na lista de probabilidades e ver qual poderia ser a próxima palavra? A grosso modo, é como GPT ou Vicuna fazem. Só que em vez de considerar só a palavra anterior e procurar a próxima, ele vê quais foram as palavras anteriores e leva todas em consideração pra tentar prever a próxima palavra. Lembra como minha GPU fica processando sem parar enquanto monta a resposta? É isso que  está fazendo: pesquisando no modelo pela próxima palavra, mas  considerando parte ou todo o texto anterior, incluindo as palavras que ele mesmo sugeriu, vai ficando cada vez mais pesado. Por isso demora.</p>
<p>Antes que o pessoal acadêmico me crucifique, é melhor eu me corrigir aqui. Eu repeti várias vezes &ldquo;a grosso modo&rdquo;, &ldquo;a grosso modo&rdquo;, porque o modelo do GPT não são probabilidades de pares de palavras, daquele jeito bonitinho como no exemplo. Intuitivamente, poderíamos pensar que quando se quebra um texto longo, teríamos conjuntos de &ldquo;palavras&rdquo;. Mas vamos recordar a metáfora do bebê aprendendo?</p>
<p>Todo mundo já deve ter percebido que um bebê não ouve um adulto falar uma palavra e sai repetindo bonitinho igualzinho, certo? Parte da diversão é justamente ver ele falando errado no começo e ir ajustando. O bebê tenta reproduzir o que ele &ldquo;acha&rdquo; que ouviu. Daí você dá o feedback negativo e ele vai tentando de outras formas, até uma hora acertar. No mundo de machine learning e deep learning, podemos usar isso como metáfora. No treinamento ele não isola palavras,  isola &ldquo;patterns&rdquo; de funcionalidades, ou features, do material de treinamento. </p>
<p>Fora de IA, no mundo de full-text search, ou em processamento de linguagem natural, temos ferramentas como Elasticsearch ou Apache Solr, que expliquei no episódio do Twitter. Eles não quebram os textos indexando palavras, mas sim grams. Quem já leu a documentação deve ter visto o termo n-gram, que são sequências de &ldquo;n&rdquo; itens ou tokens. Pode ser uma palavra inteira, mas pode ser só parte de uma palavra. Uma sequência de só uma letra seria um unigram, duas letras um bigram, três letras, trigram e assim por diante.</p>
<p>Quando indexamos texto, é mais útil indexar n-grams do que palavras inteiras, é o que nos permite fazer coisas como achar palavras &ldquo;parecidas&rdquo; ou que &ldquo;soam&rdquo; parecido, que tem mesmo sufixo ou mesmo prefixo, como conjugação de verbos. Mesma coisa com o teclado de auto-correção, ele indexa n-grams. Por isso você digita a pesquisa tudo errado num Google, mas ele diz no resultado &ldquo;você quis dizer X&rdquo;. O mundo de indexação de textos é todo baseado no conceito de n-grams. Vale a pena estudar isso depois.</p>
<p>Quer dizer que o tal modelo do GPT são pesos em cima de n-grams? Infelizmente também não é assim fácil. Eu mencionei n-grams só pra explicar como podemos dividir palavras de outras formas não intuitivas. Agora, o problema com sinapses no nosso cérebro ou redes neurais é que os pesos não são aplicados em cima de idéias discretas, como palavras ou letras ou imagens inteiras. De novo, pra entender tecnicamente como redes neurais funcionam, procure material de universidades como do MIT, Stanford ou lugares assim, e de novo, acadêmicos, peguem leve comigo.</p>
<p>No fim do dia, um modelo é como se fosse um banco de dados, contendo probabilidades ou pesos de um elemento pra outro elemento. Parecido com o exemplo da auto-correção. Mas o principal é que não são necessariamente palavras, não são também só n-grams, pode ser qualquer tipo de pattern que foi identificado no aprendizado. Pode ser uma letra pra uma palavra. Pode ser um bigram pra um trigram. Pode ser muitas coisas. Por exemplo, poderia ser &ldquo;ef&rdquo; pra &ldquo;yw&rdquo; probabilidade 0.01234. O que isso significa?</p>
<p>Isoladamente não significa absolutamente nada. Ela só vai fazer sentido dentro de uma rede de pesos. A probabilidade final é uma composição de múltiplos passos pelos nós dessa rede. O modelo não é uma lista, provavelmente é mais como um espaço vetorial multi-dimensional, tipo matrizes de matrizes. Eu expliquei espaços vetoriais no episódio do Twitter também. Similaridade de Cosseno, Álgebra Linear, lembram? De novo, pra visualizar, não pense num modelo como sendo uma lista, como um array simples. Pense como um array de arrays, de arrays. Multi-dimensional.</p>
<p>Se multi-dimensional não é intuitivo pra vocês, pense num elemento simples, tipo uma variável de tipo inteiro. Isso é o que chamamos de valor escalar, com zero dimensões. Representa um único valor. Agora, um array de escalares, como um array de inteiros. Isso é vetor, uma lista de uma única dimensão. Em seguida, em vez de escalares, se eu fizer um array de arrays, onde cada elemento do array é outro array, e esse array for unidimensional, agora temos uma matriz, que é um retângulo, ou uma grade de valores escalares.</p>
<p>Finalmente, e se esse array interno também tiver arrays como elementos? Agora temos um array de arrays de arrays,  tridimensional. E podemos ir adiante, esse último array também pode ter arrays de elementos, que tem arrays de elementos, que tem arrays de elementos, aí temos matrizes multi-dimensionais ou mais corretamente, tensores de alto ordenamento ou tensores n-dimensionais.</p>
<p>Aliás, tudo isso que eu expliquei são tensors, sabe, do tal Tensorflow do Google? Um escalar é um tensor de zero dimensões, um vetor é um tensor de uma dimensão, uma matriz é um tensor de duas dimensões e acima disso é um high-order tensor ou n-dimensional tensor. Então, voltando pro tal modelo do GPT ou Vicuna, não pense como no exemplo simples do auto-corretor, que foi somente um vetor, um array unidimensional de pesos. Pense que esses pesos estão estruturados em tensores n-dimensionais.</p>
<p>Todos esses conceitos que vim explicando não são a ponta do iceberg. São a raspa da ponta do iceberg. Pode parecer que quero dizer que um ChatGPT não é mais que uma cadeia de Markov, só que maior. Não é isso, é só uma metáfora pra explicação. Deixa eu tentar explicar qual foi a tal &ldquo;revolução&rdquo; que permitiu o salto de um mero auto-corretor pra um ChatGPT. Mas entenda: redes neurais e deep learning existem faz décadas. A comunidade de ciências da computação vem fazendo descobertas e refinando as tecnologias faz muito tempo. Não foi do nada que isso apareceu.</p>
<p>Seguindo o exemplo do auto-corretor, deve ser fácil de perceber um dos problemas: ele só usa a palavra anterior pra tentar descobrir qual a próxima palavra. Por isso muito rapidamente a frase fica sem sentido. Dá impressão que foi um humano que escreveu, mas um humano bem burro. É diferente de um punhado de palavras completamente aleatórias, mas as frases que gera, são bem inúteis. Quanto maior tentar fazer a frase, pior vai ficar.</p>
<p>Claro, o certo é que a próxima palavra leve em consideração não só a palavra anterior, mas todas as palavras anteriores, pra manter a coerência. É isso que poderíamos chamar de backtracking ou recurrency. Isso tem que ser levado em conta durante o treinamento. Não basta quebrar o texto em palavras e só fazer o peso da palavra seguinte. Tem que ser o peso da palavra seguinte dado as palavras anteriores. É aí que nasce coisas como RNN ou Recurrent Neural Networks.</p>
<p>RNNs foram desenhados pra lidar com sequências de tamanhos variados, como sentenças, dados de séries de tempo, sinais de discurso. Eles conseguem processar inputs, como texto, um passo de cada vez e ao mesmo tempo mantendo um estado interno escondido que mantém informações dos passos anteriores. Ou seja, ele mantém memória durante o aprendizado. É como a gente aprende também.</p>
<p>Uma coisa é aprender, por exemplo, a palavra &ldquo;foda&rdquo;. Dependendo do contexto pode significar coisas diferentes. Pode ser que signifique &ldquo;puts, que foda esse macbook novo&rdquo; ou seja, positivo. Ou pode ser &ldquo;puts, que trampo foda de difícil&rdquo;, ou seja, negativo, e várias outras variações. Precisamos de contexto, e contexto precisa de memória pra gerar pesos diferentes pra contextos diferentes. Consegue imaginar o trampo de processar pensando dessa forma?</p>
<p>RNNs usam técnicas como BPTT ou Backpropagation Through Time, literalmente propagação reversa através do tempo, pra computar gradientes e atualizar os parâmetros do modelo. Então não é um processamento que você pega um texto, lê só uma vez do começo ao fim e já gera um modelo, escrito linearmente do começo ao fim. Tem que ficar voltando pra trás no modelo pra ajustar. E já que é pra dificultar, em todo paper de IA por aí vamos esbarrar nesse termo &ldquo;gradiente&rdquo;. Deixa eu resumir.</p>
<p>Gradiente se refere à derivada da loss function, função de perda, ou de custo. Puts Akita, eu nunca vou usar Cálculo na vida, é perda de tempo. Bom, eis um pequeno exemplo. Pra que serve derivada? Ela serve pra medir a taxa de mudança de uma função. Em resumo, a derivada nos diz como o resultado de uma função muda à medida que fazemos pequenas modificações nos valores de entrada. Lembra de física cinemática no colegial? Fórmula pra saber o espaço ao longo do tempo? Fórmula de Velocidade ao longo do tempo? A de velocidade é derivada da fórmula de espaço, porque velocidade é a taxa de mudança da fórmula de espaço ao longo do tempo. Aceleração é a taxa de mudança da fórmula de velocidade. </p>
<p>Em particular, gradientes em cálculo de múltiplas variáveis é um vetor que aponta na direção da ascendente mais íngreme da função. Isso é super importante em otimização de algoritmos, como gradient descent, ou descida de gradiente, que iterativamente atualiza os parâmetros pra encontrar a função de custo mínimo. Dado que gradientes nos ajudam a entender a taxa de mudança de resultados de uma função, podemos usar pra encontrar os pontos de máxima e mínima, pra otimização. E esse conceito é importante em otimização de machine learning.</p>
<p>Em Machine Learning tem um troço que chamam de &ldquo;loss function&rdquo; também conhecido como função de custo, que é uma função matemática que quantifica a discrepância entre resultados previstos de um modelo com os valores de verdade. Lembrando, uma das formas de treinar é dar um monte de dados de treinamento, daí pedir pro modelo devolver respostas a várias perguntas e ver se as respostas estão corretas. Justamente pra calibrar o aprendizado. Lembra do bebê aprendendo a falar e olhando pra nossa cara pra ver se a gente entendeu? Tipo isso.</p>
<p>O objetivo do tal treinamento é ver quão bem um modelo performa determinadas tarefas. A escolha de qual função de custo usar depende do problema que queremos resolver, como regressão, classificação, geração de sequências. Por exemplo, em tarefas de regressão, uma função de custo popular é o MSE ou mean squared error, erro quadrado mediano, ou o MAE que é mean absolute error, erro absoluto mediano. Em tarefas de classificação tem custo de entropia cruzada e assim por diante. O importante é entender que treinamento não é um troço aleatório. Tem funções de métrica e controle pra calibrar e otimizar.</p>
<p>Durante o treinamento, os parâmetros do modelo são ajustados pra minimizar essa função de custo usando algoritmos de otimização como o &ldquo;gradient descent&rdquo;. Agora vocês entendem uma das formas que cálculo influencia a qualidade do treinamento de um modelo de rede neural. Pra hoje, pense em rede neural como uma caixa preta, que nem uma função que você programa na sua linguagem de programação. Ela tem variáveis de entrada e algum retorno. A entrada seria as tais toneladas de textos pro treinamento. Daí no meio, nessa caixa preta, esses dados são processados de alguma forma, e o retorno vai ser o tal modelo multi-dimensional.</p>
<p>Essa etapa no meio é o processamento dos textos e materiais de treinamento que passamos. Processos como tokenização que é quebrar o texto em listas de sequências de palavras. São uma série de transformações pra massagear esses dados em diversas camadas escondidas. Essas transformações envolvem cálculos, por exemplo, soma ponderada. Cada neurônio de uma camada escondida recebe inputs de uma camada anterior. A cada input é assinalado um peso. O neurônio computa a soma ponderada dessas entradas, onde os pesos determinam a significância da contribuição de cada input pra saída do neurônio.</p>
<p>Daí a soma ponderada pode ser passada pra uma função de ativação, que introduz não-linearidade e determina a saída da função. Não linearidade e sistemas complexos é um assunto gigantesco, nem vou tentar explicar. Mas pra ter intuição pense assim: você tá acostumado a pensar em sistemas lineares. Por exemplo, se 1 litro de gasolina dá pra andar 15 quilômetros, então 10 litros de gasolina vai dar 150 quilômetros. Mas não-linearidade é que nem tentar prever o tempo. </p>
<p>Só porque temos 80% de humidade no ar e no passado vimos que isso indicava uma chuva de, sei lá, 20 milímetros. Não quer dizer que se eu medir hoje 80% vai dar os mesmos 20 milímetros. Pode ser 40. Pode ser zero. Tem uma rede de outras variáveis, algumas mensuráveis, algumas desconhecidas, uma influenciando a outra. Variáveis minúsculas podem amplificar resultados completamente inesperados.</p>
<p>É o famoso caso da Borboleta de Lorenz. Aquela história que uma borboleta bate as asas no Brasil e tem um tsunami no Japão. Não foi a borboleta que causou o tsunami, não é linear, não tem causalidade direta. Mas quer dizer que essa minúscula contribuição, somado a milhares de outras, &ldquo;pode&rdquo; ter causado o tsunami. </p>
<p>Depois parem pra ler sobre Teoria do Caos, é fascinante, e o mundo real é cheio de efeitos não-lineares. E levamos isso em conta em redes neurais. Nosso cérebro tem aprendizado não-linear, e é o que tentamos simular com redes neurais. Agora, Deep Learning, como o próprio nome diz é aprendizado profundo, e profundidade se refere a várias camadas de aprendizado. Aquela função caixa preta que eu mencionei? Imagine várias delas em série, uma chamando a outra, várias camadas de profundidade. Lógico, explicação simplificada, mas só pra dar uma noção.</p>
<p>Enfim, a parte importante é que processar texto em deep learning usando técnicas como RNN e BPTT seria absurdamente caro, pra manter toda a memória e fazer todo esse backtracking na força bruta. É aí que entra o famoso paper do Google, &ldquo;Attention is All You Need&rdquo;. Literalmente &ldquo;Atenção é Tudo que Você Precisa&rdquo;, publicado por Vaswani e equipe em 2017. É o paper que introduz a arquitetura de Transformers, os famosos transformadores que permitiram esta geração de LLMs como GPT.</p>
<p>Em pouquíssimas palavras, ele introduz o mecanismo de self-attention, ou auto-atenção, também conhecido como atenção escalada de produto escalar. Eu expliquei produto escalar no contexto de espaços vetoriais no episódio do Twitter. Não vou explicar de novo. Mas esse mecanismo permite o modelo pesar a importância de diferentes posições na sequência de entrada, possibilitando capturar efetivamente as dependências longas. Eu sei, é difícil de entender isso, e também de explicar. Mas lembra como RNN precisa manter um estado em memória pra lembrar o contexto? Self-attention é uma otimização disso.</p>
<p>Em vez de ser recurrent neural network, ele passa a poder usar feed forward neural network. Da forma como eu entendo, em vez de um processo onde você dá um passo pra trás antes de poder dar um passo pra frente, agora é só passos pra frente, feed forward. O que possibilita isso é o tal mecanismo de auto-atenção. Elimina a necessidade de recorrência ou convolução. E ainda permite paralelizar o processamento. Antes, como o passo seguinte dependia do passo anterior, tinha que ser feito em série, em sequência linear. Parte da dificuldade de conseguir rodar coisas em paralelo é eliminar as dependências que amarram o passo seguinte com o passo anterior. Isso vale não só pra IA mas qualquer coisa.</p>
<p>Tornando o processo feed forward, evitando convolução, podemos paralelizar o processamento. O que levaria, chutando, um ano pra treinar, poderia ser feito em um mês. O importante é entender que essa arquitetura de transformers é uma otimização massiva. É mais ou menos o tipo de impacto que você vê num desenvolvimento web comum quando coloca um índice numa tabela gigante, ou quando coloca um cache na frente do banco de dados e ganha 5x ou 10x a performance. Independente de como funciona no detalhe, o importante é entender que foi um salto grande. </p>
<p>Toda hora eu fico falando que os acadêmicos vão me matar vendo essas minhas explicações grosseiras, mas eu mesmo fico doído de ficar toda hora falando &ldquo;a grosso modo&rdquo;, &ldquo;simplificando&rdquo;, &ldquo;em metáfora&rdquo;, porque cada parágrafo que falei até agora são dúzias de papers e formalidades matemáticas. Estou tentando trazer um pouco desse vocabulário, pra vocês entenderem que não é um chute do nada, mas também reduzir em poucas palavras que ajudem a dar uma intuição. Pra maioria de nós, os detalhezinhos não importam tanto. Tem mais valor ter a noção desse processo, em linhas gerais, pra entender que não é mágica. Principalmente: qual o limite dessa mágica.</p>
<p>Mas com tudo isso que falei, vamos tentar entender o que é o ChatGPT então. Como é um projeto proprietário, fechado e secreto da OpenAI, temos que acreditar nas informações que eles disponibilizaram. Então sempre leiam isso com vários quilos de sal. Isso dito, parece que o treinamento foi baseado num corpo de aproximadamente 570 gigabytes de texto. Quais textos exatamente? Não sabemos, mas eles mencionam Wikipedia, artigos de pesquisa e papers, websites e outras formas de conteúdo escrito na web, com um limite até 2021. Isso é arbitrário.</p>
<p>Pessoalmente, achei pouco texto, eu teria chutado mais. Mas 570 gigabytes só de texto puro é bastante coisa na real. Por exemplo, a Wikipedia inteira dá um total de 21 gigabytes, e isso eu acho que é contando com o HTML que monta as páginas. Se filtrar e limpar só os textos puros vai ser bem menos. Mas digamos que seja 21 gigabytes. Precisaria de mais de 30 Wikipedias inteiras pra completar os 570 gigabytes de dados de treino. É um volume respeitável.</p>
<p>Esse tanto de texto, dizem que deu um total de 300 bilhões de &ldquo;palavras&rdquo;, entre aspas, mas acho que o jornalista entendeu errado. Um dicionário de inglês, como o Merriam Webster online não tem meio milhão de palavras. Acho que são 300 bilhões de tokens, que incluem palavras, mas também n-grams como falei antes, e seja lá quais outros patterns o deep learning identificou nesse material. Daí passa por semanas fazendo todo o processo que falei de transformação. Esse processo, que levaria meses, agora parece que dura mais ou menos um mês rodando em não sei quantos servidores usando hardwares como os agora famosos NVIDIA Grace-Hopper, os GH100.</p>
<p>Lembra que falei que, internamente, não estamos lidando com valores escalares e sim com tensores multi-dimensionais? CPUs como um Intel ou AMD que roda no seu PC, mesmo os M1 ou M2 da Apple, são chips com instruções feitas pra cálculos em cima de valores escalares. Uma função de soma pega dois valores inteiros de 64 bits e cospe um resultado inteiro de 64 bits. Eu explico como isso funciona nos episódios de emuladores como o do Super Mario, com processadores de 8-bits, depois dêem uma olhada.</p>
<p>CPUs modernas incluem instruções pra lidar com vetores, instruções SIMD, Single Instruction, Multiple Data, literalmente uma instrução pra múltiplos dados. Começou com as instruções MMX dos primeiros Pentium nos anos 90. Hoje temos conjuntos de instruções como SSE4 ou AVX-512. Pra ter a intuição, em vez de uma função que recebe um inteiro, pense numa outra função que recebe dois arrays, soma os dois arrays, e cospe um array resultante, tudo numa única instrução.</p>
<p>GPUs, diferente de CPUs, não tem capacidade de rodar qualquer programa genérico. Lembram do meu episódio de Turing Complete? A grosso modo, uma máquina de Turing é basicamente qualquer programa. Em particular, pra ser Turing Complete, pra ser um computador moderno, ele precisa ser capaz de rodar um programa que consegue simular ser um computador. Como exemplo mais óbvio pense uma máquina virtual. Ele nem precisa conseguir rodar na prática, mas tem que ter a capacidade teórica. Num CPU ARM M2 da Apple, é possível simular uma CPU Intel usando o Rosetta. E esse programa de Intel roda achando que tá num PC de verdade. Isso é possível porque um CPU ARM M2 é Turing Complete.</p>
<p>Já uma GPU não tem essa capacidade. Diferente de CPUs, que são genéricos, e podem simular qualquer coisa, mesmo que lento, uma GPU é um hardware especializado, pra executar um conjunto pequeno de tarefas bem definidas. Uma GPU não consegue rodar um sistema operacional genérico, nem simular ser outra GPU, tipo um AMD Radeon tentar simular ser um NVIDIA RTX. Não funciona assim. Quando existe camada de abstração, quem cuida disso é a CPU. A GPU é boa numa única coisa: fazer cálculos de vetores e matrizes.</p>
<p>Uma CPU Intel costuma ter sei lá, 8, 16, 32 núcleos com 2 threads cada, rodando a 4 ou 5 gigahertz hoje em dia. Mesmo chips de servidores como um Intel Xeon ou AMD EPIC não tem muito mais que isso de cores. Já uma GPU é diferente. Uma novíssima RTX 4090 tem nada menos que 16 mil núcleos pra shading de CUDA, 128 núcleos pra ray tracing e nada menos que 512 núcleos exclusivos só pra tensors. Uma GPU, diferente de uma CPU, tem milhares de núcleos que rodam em clocks baixos como 1 gigahertz, pra funções altamente especializadas.</p>
<p>O que eu falei que é o resultado do treinamento de deep learning? Um modelo de tensors n-dimensionais. O que foi feito pra calcular tensors multi-dimensionais? GPUs. Processar áudio, processar video, processar polígonos ou voxels tridimensionais, é tudo processamento de matrizes multi-dimensionais. Uma tela de computador ou do seu smartphone, como é representado? Num monitor Full HD, é um array de 1080 colunas, onde cada elemento é um array de 1920 elementos pra cada linha. Se eu quiser escurecer essa imagem inteira? Pode ser uma substração em cada valor desses arrays. Fazemos isso adicionando, ou subtraindo uma matriz por outra matriz, que chamamos de um &ldquo;filtro&rdquo; ou kernel.</p>
<p>Numa CPU, você programaria como um loop nas colunas e outro loop nas linhas pra calcular a nova cor, pixel a pixel, seria 1920 vezes 1080 operações, ou mais de 2 milhões de operações. Numa GPU eu passo a matriz inteira e ele calcula tudo numa única operação. De novo, não vou conseguir entrar em detalhes, mas essa é a diferença fundamental.</p>
<p>De curiosidade, um dos maiores problemas dessa arquitetura de CPU controlando GPU é o compatilhamento de memória. Em PCs modernos a CPU tem um conjunto de RAM e a GPU tem um conjunto de VRAM separados. A CPU prepara os dados e tem que mandar pra GPU processar. Daí uma vez calculado, a CPU precisa puxar o resultado de volta pra própria RAM. No frigir dos ovos, essa comunicação é um gargalo.</p>
<p>Não é raro vermos jogos, por exemplo, que perde frames mesmo a GPU não estando em 100%, mas se olhar a CPU, ela que tá em 100%, então vira um gargalo e a GPU fica um tempo parado esperando. Por isso desde a nona geração de consoles de videogames como PS5 e Xbox Series X se falou tanto em loads instantâneos e tecnologias de melhorar esse gargalo, como o Microsoft DirectStorage.</p>
<p>Também é por isso que a estratégia da Apple com os chips M1 e M2 é ser um SoC ou System on a Chip; um único chip que embute CPU, GPU e RAM tudo junto, pra minimizar ao máximo esse gargalo. Juntar tudo no mesmo lugar garante uso mais eficiente de memória e caminho mais curto de comunicação, ajudando a evitar gargalos.</p>
<p>Na velocidade que estamos hoje, a distância da sua CPU Intel pros pentes de RAM é gigante se comparado a soldar tudo junto no mesmo chip como a Apple faz. É um saco porque não dá pra aumentar RAM depois, mas a razão não é porque eles são uma corporação querendo arrancar mais dinheiro de vocês, mas sim porque tirar esse gargalo faz muita diferença.</p>
<p>A mesma coisa acontece na solução pra data centers da Nvidia, a tal arquitetura Grace-Hopper que eu falei, começa com um superchip Grace-Grace, tanto Intel quanto ARM, que eles mesmos desenvolveram, num único pacote com NVLink que é um barramento de altíssima velocidade entre eles, e a alternativa Grace-Hopper que é outro superchip que junta uma CPU Grace com uma GPU <code>H100</code> Hopper.</p>
<p>São soluções que juntam todos eses chips junto com meio terabyte de memória RAM LPDDR5 de 32 canais. Estamos falando de 96 núcleos de 3 nanômetros. É um monstro. É esse o produto que tem feito as ações da NVIDIA disparar, porque eles encaixam perfeitamente pra acelerar processamento de transformers. Meu PC não é &ldquo;parrudo&rdquo;, esse da NVIDIA sim, é a verdadeira definição de &ldquo;parrudo&rdquo;, o atual estado da arte em 2023.</p>
<p>É com servidores desse tipo, não sei quantos, que se pega meio terabyte de dados, quebramos em 300 bilhões de tokens, e no final a OpenAI consegue gerar um modelo de GPT 4.0 com os tais 170 trilhões de parâmetros. Conseguem entender melhor agora essa frase? Daí a mídia e os jornalistas ficam assustados e noticiam como o GPT 4 se iguala ao cérebro humano, que tem 100 trilhões em sinapses. Lembram? Sinapses mais ou menos são os pesos ou parâmetros entre neurônios. Agora que entendemos mais ou menos o que são esses parâmetros, vamos discutir a premissa errada: parâmetros não são equivalentes a sinapses do cérebro humano.</p>
<p>Quando se joga números arbitrários assim no título de uma matéria, todo mundo fica empolgado. Vamos entender o erro. Pra começar, nosso cérebro, em média, tem uns 100 bilhões de neurônios. De novo, eu não sou um neurologista, então já assuma que minha explicação vai ser simplificada e de alto nível. Em pesquisa de IA, tentamos igualar neurônios biológicos com neurônios digitais numa rede neural, e falamos em bits, como num computador. Mas pra começar, neurônios não são exatamente binários assim. Um único neurônio é capaz de lidar com múltiplos sinais e conexões. Em termos de sinapse pode ter até umas 7 mil.</p>
<p>Isso acho que seria máximo, mas em média o cérebro é capaz de até uns 600 trilhões de sinapses. Não sei porque, dependendo de onde se pesquisa, falam em 100 trilhões, outros falam em 600 trilhões. De novo, precisa pesquisar um pouco mais a literatura de neurologia pra entender o que isso significa. Mas não é um número absoluto. Tem vários fatores. Doenças como Alzeimer, por exemplo, afeta justamente a capacidade de fazer e manter sinapses.</p>
<p>Em crianças, quando o cérebro ainda é muito mais elástico e não foi limitado pelo crescimento, o potencial é de 1 quadrilhão de sinapses. É um número absurdo, mas mais importante, os 170 trilhões de parâmetros do GPT 4 não se equipara à quantidade de sinapses que nosso cérebro é capaz ainda. Portanto é falso que o GPT 4 já se igualou ao cérebro humano em quantidade de sinapses.</p>
<p>A outra premissa que errada: um parâmetro de modelo de GPT não é equivalente a uma sinapse do cérebro humano, nem de longe. Usamos vocabulário neurológico pra simplificar a descrição em termos de inteligência artificial, como redes &ldquo;neurais&rdquo;. É uma metáfora. Em nenhum momento, nenhum cientista da computação vai te dizer que um neurônio de rede neural é idêntico ou sequer próximo de um neurônio biológico. É só uma abstração.</p>
<p>E no caso de IA, parâmetros de modelos de transformers, comparado com neurônios do cérebro. Vamos voltar ao meu Vicuna rodando localmente na minha máquina. Lembram da afirmação do povo que fez testes e otimizou esse modelo? Um modelo Vicuna de 13 bilhões de parâmetros, em muitos casos, chega até 90% da qualidade de respostas de um ChatGPT 4.0 de 170 trilhões de parâmetros. Como isso é possível?</p>
<p>E por isso mencionei não-linearidade e Teoria do Caos. Um único parâmetro isolado, se tentar ler e interpretar, não tem como inferir nada. Só funciona se estiver combinado com vários outros parâmetros, numa rede. O resultado final depende da interação de múltiplos parâmetros, e por isso se gasta processamento da GPU pra gerar uma resposta. Parâmetros são pesos, probabilidades. E tem várias formas de otimizar isso. Por exemplo, probabilidades pra ter o máximo de precisão podem ser valores escalares de tipo float de 32 bits. É isso que se gera num modelo depois do treinamento.</p>
<p>Porém, pesquisas mostram que podemos truncar esses valores pra float-16 e a qualidade das respostas não cai tão drasticamente. É uma forma de otimização. Simplificando, é parecido com o conceito de música em MP3, que dados das frequências que o ouvido humano não é capaz de detectar são cortados fora. Tecnicamente isso tira qualidade do áudio, mas na prática a maioria dos humanos não sente. Em termos de armazenamento, economizamos 10 vezes o espaço fazendo isso.</p>
<p>Expliquei no episódio de 25 tera pra 5 gigas, onde pegamos uma imagem bruta em bitmap e reduzimos pra um JPEG. A qualidade cai, mas o olho humano sem treinamento, não nota diferença tão grande assim. Otimizações e compressão são formas de simplificar os dados, diminuir a qualidade, de forma que nossos sentidos, sem treinamento, não sintam diferença significativa. Fazemos isso com modelos de IA também.</p>
<p>Reduzir os valores de Float 32 pra Float 16 é uma forma de quantização. Existem várias formas de quantização que são otimizações dos modelos, pra exigir menos processamento pra gerar respostas, sem danificar demais a qualidade. Isso ajuda a conseguir fazer um clone de ChatGPT como o Vicuna, rodar numa máquina caseira como a minha. Parece que minha RTX 3090 já é quase topo de linha, mas não. Por isso expliquei sobre a NVIDIA GH100 que é o tipo de hardware necessário pra rodar o ChatGPT de verdade.</p>
<p>Mas respondendo como um Vicuna de 13 bilhões de parâmetros consegue competir com um GPT 4 de 170 trilhões é porque além de quantização, os algoritmos de auto-atenção tem evoluído também. Em auto-atenção, que acontece no processo de treinamento, cada token numa sequência precisa ser considerado com todos os outros tokens pra capturar dependências e relacionamentos. Tipicamente, auto-atenção é computado dentro de uma janela de contexto, onde cada token é considerado com tokens vizinhos.</p>
<p>Agora tem uma variante chamada &ldquo;auto-atenção global&rdquo; onde cada token é considerado independente de posição ou distância, que permite o modelo capturar dependências num contexto mais global. Na prática é assim: custa mais caro pra treinar, mas os parâmetros resultantes no modelo tem mais qualidade. Então com menos parâmetros conseguimos chegar em respostas de qualidade similar. Entenderam? A qualidade de um parâmetro não é universal nem estático, está mudando à medida que aperfeiçoamos os algoritmos de treinamento e estruturas de dados.</p>
<p>Redes neurais, mesmo deep learning com auto-atenção global, ainda são representações rudimentares e grosseiras do nosso cérebro. Pra um GPT ou Vicuna da vida, conseguir escrever um texto com estilo de Shakespeare, precisamos treinar com todos ou quase todos os textos do Shakespeare. Pra conseguir gerar uma música parecida com Mozart, temos que dar o máximo de composições de Mozart quanto possível. O processo de treinamento vai encontrar patterns ou padrões e criar pesos pra eles, registrando no modelo. Mas isso ainda é bem ruim se comparado ao cérebro humano.</p>
<p>Pense você. Se estiver treinando em literatura ou música. Mesmo não lendo nem perto de todas as obras de Shakespeare, mesmo não estudando nem de perto todas as composições do Mozart, rapidamente consegue começar a copiar o estilo deles. Veja você como programador. Não precisou ler todos os códigos já feitos em React pra começar a escrever códigos. Bastou uns 2 tutoriais. Nosso cérebro consegue aprender muito melhor que um transformer, com muito menos dados, e produzir resultados similares ou melhores dentro de um mesmo determinado assunto. IA hoje em dia ainda depende muito de força bruta.</p>
<p>Desde que o LLaMA foi lançado em fevereiro de 2023, estamos só em junho e já temos dezenas de modelos diferentes, com vários níveis de quantidade de parâmetros, caindo desde 65 bilhões até só 7 bilhões, com vários tipos de otimização como GPTQ pra quantização, ou Float 16. E isso permite rodar algo parecido com esse meu Vicuna local, num Android ou até num Raspberry Pi.</p>
<p>É o que eu acho ideal: um transformer rodando localmente, offline, sem compartilhar nenhum dado pessoal meu, nenhuma conversa, com nenhuma corporação por aí. Quanto menos dados meus eu tiver que dar pra alguém, melhor. Especialmente se esse alguém não me paga por isso. Pelo contrário, pra usar OpenAI eu preciso pagar assinatura. E sabe-se lá o que fazem com minhas conversas.</p>
<p>Bacana, significa que se o jornaleiro fala que o GPT 4 é próximo já de um ser humano, e os nerdolas das ciências da computação já compactaram e tornaram eficientes até esse ponto do VIcuna. Então já era. Tá fácil fazer a Skynet né? Afinal, GPT 4 já consegue escrever código de programação. Basta eu carregar o código do GPT 4 e mandar ele melhorar o código e gerar o GPT 5. Daí eu pego o novo GPT 5 e faço ele fazer uma versão melhor, o GPT 6, e assim sucessivamente, até eu ter o Gigachad GPT T-1000 Skynet que vai dominar o mundo, certo?</p>
<p>Errado. No final do dia, um GPT 5, 10, 20, continua não sendo mais que um auto-corretor do seu teclado em versão maior. Tirem da cabeça a noção de que é uma &ldquo;inteligência&rdquo;. Ela não é inteligente. Tentar definir inteligência é um buraco de coelho profundo demais, vai ter gente masturbando filosofia aqui até o fim dos tempos e ninguém vai chegar numa conclusão. Vamos só assumir que até hoje não temos uma definição exata de &ldquo;inteligência humana&rdquo;. Na verdade, quando chamamos de &ldquo;inteligência artificial&rdquo; só quer dizer que os resultados, pra um ser humano, podem &ldquo;parecer&rdquo; com algo inteligente, mas não que de fato &ldquo;é&rdquo; inteligente. Entendem a diferença?</p>
<p>Não ter uma definição exata é ruim, porque não temos um plano exato de pra onde ir. Na neurologia de verdade, apesar dos avanços, não temos uma receita exata de como neurônios e sinapses funcionam 100%. Temos uma boa idéia. Muita coisa ainda é especulativo. Mas podemos dar alguns chutes educados baseado em tudo que falei até agora. E aqui vai ser minha opinião pessoal. Se alguém tiver pesquisas que discordam, sintam-se à vontade pra linkar nos comentários abaixo. Opinião por opinião, cada um pode ter a sua. A maioria parece estar pulando rápido demais pra religião do AGI e eu vou ser advogado do diabo e dizer porque isso não está nem perto de acontecer.</p>
<p>Vocês entenderam até aqui? Quando vamos na interface Web do ChatGPT ou Vicuna ou qualquer outro derivado e escrevemos o tal do prompt, a pergunta, damos uns segundos, e ele nos trás uma resposta. Mas na realidade não é isso. O que vem na verdade é uma continuação do texto do prompt. Tem uma diferença importante aqui: ele não está tentando te responder. Acontece que com os parâmetros treinados do modelo, a probabilidade maior das próximas palavras é se parecer com uma resposta. Essencialmente o que está acontecendo é similar ao auto-corretor do teclado do seu celular: dado o texto que acabou de digitar, quais palavras tem mais probabilidade de serem continuação?</p>
<p>Entendam essa sutileza. Por isso falo que não é uma &ldquo;inteligência&rdquo;. GPT ou Vicuna não são pessoas e nem entidades com cognição tentando se comunicar. É meramente um completador de textos, ou mais tecnicamente, um transformer pré-treinado generativo, um gerador de texto. Uma pergunta, ou prompt, é um texto que se digita na expectativa que o modelo consiga continuar completando. É por isso que o texto da resposta vai aparecendo aos poucos, não é uma animação arbitrária, é igual você ficar clicando na próxima palavra sugerida pelo teclado.</p>
<p>Um dos parâmetros que afeta essa continuação se chama temperatura. Nós não temos controle da temperatura do ChatGPT pela interface Web, mas no Bing da Microsoft tem esses controles de mais balanceado, mais preciso ou mais criativo. E no meu Vicuna tenho esse campo numérico. Novamente, do jeito que todo mundo escreve, parece que estamos configurando uma pessoa pra ser &ldquo;mais criativa&rdquo;. Mas isso é só parte do showzinho. Um transformer não é mais ou menos criativo, ele é mais ou menos &ldquo;aleatório&rdquo;. </p>
<p>Temperatura controla a aleatoriedade do complemento de texto sendo gerado, o que você chama de &ldquo;resposta&rdquo;. O modelo assinala probabilidades pra cada possível token, que são candidatos pra ser a próxima palavra na sequência. Alta temperatura, valores maiores do que &ldquo;1.0&rdquo;, significa que o modelo assinala probabilidades similares a um conjunto maior de tokens. Se vários tokens tem probabilidades parecidas, as respostas podem variar mais quando se repete a mesma pergunta. Quanto maior a temperatura mais você vai &ldquo;achar&rdquo; que ele tá sendo mais criativo, mas também aumenta a probabilidade dele começar a dar respostas sem sentido.</p>
<p>Temperatura média, entre 0.5 a 1.0, é a resposta &ldquo;balanceada&rdquo; do Bing. Mesma coisa que alta temperatura mas ele assinala probabilidades similares pra um conjunto menor de tokens, controlando um pouco mais o que se perceberia como &ldquo;criatividade&rdquo;. Baixa temperatura, que é abaixo de 0.1, faz o modelo assinalar probabilidades similares pra palavras que realmente tinham mais chances de ser o próximo token. Isso faz a resposta parecer mais focada, determinística, previsível.</p>
<p>Respostas sem sentido, muitos chamam de alucinação. Eu não gosto desse termo, porque implica que se alucinação é temporária nas respostas, então no resto do tempo ele está sendo sóbrio ou racional. E não está. Todas as partes das respostas foram geradas mediante probabilidades do modelo. Eu só ajusto a quantidade de de aleatoriedade nos candidatos pra próxima palavra. Nada mais, nada menos. Ele nunca alucina, assim como nunca é sóbrio. Ele não é uma entidade consciente pra ser nenhuma das duas, é só um programa obedecendo probabilidades armazenadas num modelo.</p>
<p>Entenda, no frigir dos ovos, &ldquo;criatividade&rdquo; no mundo de transformers, é só uma métrica de aleatoriedade. Não tem nada a ver com criatividade humana. Assim como inteligência artificial não tem nada a ver com cognição de verdade. Transformers, e todo tipo de machine learning ou deep learning, são meros sacos de probabilidades. Eles não tem de fato cognição pra pensar &ldquo;huumm, os dados dizem que a probabilidade desse evento é X, mas realmente faz sentido? tem alguma coisa que eu não estou percebendo? Deixa eu parar pra pensar&rdquo;, que seria o que um ser humano inteligente consegue fazer. Transformers não refletem, só cospem exatamente o que as probabilidade do treinamento do modelo dizem pra ele cuspir, e nada mais.</p>
<p>Quando um transformer consegue pegar uma equação e achar a resposta, ou pegar até a planilha de balanço de uma empresa e dizer se está indo bem ou mau. Ela não está sendo inteligente. Simplesmente existem probabilidades no modelo que levam à resposta porque materiais que ele usou de treinamento tinham já a resolução de equações parecidas.</p>
<p>Da mesma forma que eu disse que não existe um monte de &ldquo;ifs&rdquo; de regras gramaticais pra conseguir escrever textos, também não tem um monte de &ldquo;ifs&rdquo; de regras matemáticas pra fazer contas. Sempre volte à imagem daquela lista de pares de palavras e probabilidades do exemplo do teclado com auto-corretor. Aquilo é tudo que ele tem. Um conjunto gigante de probabilidades. Toda resposta que te dá, por mais inteligente que pareça, não teve nenhum tipo de raciocínio ou lógica ou inferência, nem nada, só probabilidades.</p>
<p>Um texto gerado por um transformer às vezes pode parecer &ldquo;simpático&rdquo;. Se você for uma pessoa carente, vai parecer que o transformer responde se importando com você. Mas nada disso é intencional, é você projetando nele o que gostaria que ele fosse. Todo mundo faz isso com animais de estimação. Isso se chama &ldquo;antropomorfismo&rdquo;. Você pode atribuir emoções humanas pra animais ou objetos inanimados. Tem gente que jura que uma escultura sorri pra ele quando passa na frente. Obviamente não. </p>
<p>Um exemplo simples disso são nuvens no céu. Vira e mexe você olha pro céu e vê claramente uma escultura nas nuvens. Dependendo da sua inclinação ideológica poderia pensar. &ldquo;Uau, Deus realmente é criativo, olha que obra maravilhosa nos céus que ele está me dando&rdquo;. Ou &ldquo;Uau, a mãe natureza é especial, mãe Gaia continua a demonstrar sua genialidade criativa até nos céus&rdquo;. Ou &ldquo;Uau, a droga que eu acabei de tomar é da hora &hellip;&rdquo; Só esse último pode estar certo.</p>
<p>O fato é que a nuvem em si não está em formato de nada. Se passar um avião por cima da mesma nuvem, não vai ver nada, ou vai ver outra imagem. Da posição que você está, na sua cabeça, suas sinapses pré-treinadas sugerem que você está enxergando  a sillhueta de algum objeto ou animal que já viu antes. É um efeito colateral da nossa cognição. Poderíamos chamar de bug. É seu cérebro primitivo. O cérebro rápido, automático e meio burro.</p>
<p>Ninguém moldou essa nuvem, ela aleatoriamente acabou numa determinada posição, que parece ter um determinado formato. Não houve intenção. Foi aleatório. Eu diria que a IA tá pouco se fodendo pra você, mas isso implicaria que ela tem consciência. Ela nem está se fodendo pra você. Ela não pensou em você. Mesma coisa com um transformer. Ela não tem consciência, ela simples é. Uma mera ferramenta. Uma chave de fenda que sabe cuspir palavras segundo uma matriz de probabilidades. Nada mais, nada menos. Por acaso você está olhando pra chave de fenda e achando que está sorrindo pra você. Não está. Isso diz mais sobre seu estado emocional do que sobre a ferramenta.</p>
<p>Significa que ela não tem insights, não tem momento &ldquo;eureka&rdquo; de descobrimento de coisas novas. Ela só é capaz de sugerir uma palavra, depois das outras palavras que vieram antes, seguindo uma tabela de probabilidades que vieram do treinamento feito com gigabytes de texto. De vez em quando, dá impressão que ela &ldquo;criou&rdquo; algo que não existia, mas não é criação, é aleatoriedade, pura sorte. É a nuvem no céu. Não é um processo repetível e ela não sabe refletir algo como &ldquo;uau, isso que eu inventei é da hora, nunca tinha visto antes&rdquo;. Ela não tem emoções pra conseguir dizer &ldquo;uau&rdquo;.</p>
<p>Se você der o código de uma IA feita com RNNs como falei antes pro GPT 4 analizar, ela não vai magicamente conseguir chegar no paper de transformers. Se esse paper não estava no material de treinamento, ela não vai concluir sozinha que esse era o próximo passo. Portanto ela é incapaz de conseguir chegar num GPT 5. Novas descobertas precisam ser feitas por humanos, documentadas, alimentadas no treinamento da IA e só aí ela vai saber cuspir o texto desse paper. Ela é incapaz de usar o que aprendeu pra gerar descobertas novas de forma intencional. </p>
<p>Existe uma historinha que é chamado do Teorema dos Macacos Infinitos. O teorema sugere que se tivermos um macaco apertando teclas numa máquina de escrever por uma quantidade infinita de tempo, quase com certeza vai conseguir digitar qualquer tipo de texto, incluindo todas as obras do Shakespeare. Na prática, estamos falando de uma quantidade tão absurda de tempo que seria impossível até de vocês terem noção, estamos falando provavelmente de mais tempo que a idade atual do Universo desde o Big Bang. Mas é um conceito que demonstra que sim, sem nenhuma inteligência, só com aleatoridade, dado um tempo absurdo, uma hora tudo que já produzimos vai aparecer. Sem nenhuma intenção, só via aleatoriedade.</p>
<p>É uma história pra dar noção de conceitos como infinito e aleatoriedade. Essa história é creditada ao matemático francês Felix Émile Borel, de 1913. Ou seja, desde o começo do século XX já se tinha essa noção que muitos de vocês, mais de um século depois, ainda estão com dificuldades de entender. GPT e derivados são macacos modernos dessa história. Só que em vez de totalmente aleatório, demos um modelo de probabilidades pra facilitar o trabalho dele, só isso. Mas chamar de macaco é meio ofensivo, pro coitado do macaco, que ainda é mais inteligente que qualquer IA. </p>
<p>Falando em século passado. Deixa eu aproveitar pra voltar lá atrás na história das inteligências artificiais. O estudo de algoritmos e técnicas que contribuem pra esse campo existe desde os anos 50 pelo menos. Mas nos anos 70 surgiu um programa que até hoje ainda deixa os desavisados meio surpresos. O nome do programa é Eliza. Existem várias versões, inclusive rodando online. Dêem uma olhada.</p>
<p>(&hellip; screencast eliza &hellip;)</p>
<p>Como podem ver, é um chatbot também, que nem o ChatGPT. E parece uma pessoa bem desconfiada, fica dando umas respostas meio grossa. Weebs de anime chamariam ela de &ldquo;tsudere&rdquo;. Mas se hoje povo fica empolgado com ChatGPT respondendo, imagina isso nos anos 70. Só pra dar contexto, isso é antes da revolução dos microcomputadores de 8-bits como Commodore ou Apple II. Isso é antes da Microsoft ou Apple existirem. Nem internet existia ainda. Quando eu rodei uma versão de Eliza pra DOS lá no começo dos anos 90, lembro que fiquei fascinado e pensando como eu faria a minha própria versão. E aí vocês podem se perguntar, &ldquo;caraca, mas como era possível ter isso nessa época??&rdquo;</p>
<p>Bom, quem faz ciências da computação ou programa um pouco, já deve ter entendido o funcionamento. O modelo é super simples. É baseado inteiramente em pattern matching. Basicamente encontrar palavras-chave, ver se tem uma resposta pré-programada, e montar essa resposta, na mão. É um sistema de templates. Quando eu digo &ldquo;na mão&rdquo;, aqui sim, ao contrário de modelos de transformers, é realmente um monte de &ldquo;ifs&rdquo;. Quem já montou agentes ou robozinhos, tanto de chat de suporte ou email marketing, já fez coisa similar pra respostas automáticas. É um bom exercício de faculdade pra iniciantes.</p>
<p>No GitHub vai achar várias versões. Veja esta, é uma versão feita em Java. E vamos ver o tal &ldquo;modelo&rdquo; entre aspas. É uma lista de chaves e valores, e os valores, como podem ver, são frases pré-prontas. Por exemplo, se durante o chat você se desculpa e escreve algo como &ldquo;I&rsquo;m sorry&rdquo;, ele quebra essa string, encontra a palavra &ldquo;sorry&rdquo; e nesta lista olha só, tem 3 respostas pré-programadas. Ela pode te responder &ldquo;Por favor, não se desculpe&rdquo; ou &ldquo;Desculpas não são necessárias&rdquo;. E vai escolher aleatoriamente, pra não parecer que tá se repetindo.</p>
<p>Só isso já é suficiente pra passar no Teste de Turing, que é um teste feito pra identificar quando uma inteligência artificial consegue enganar um ser humano numa breve conversa. Eliza é a avó espiritual do ChatGPT. Mas o ponto em apresentar a Eliza é pra vocês verem que pra enganar seres humanos, não precisa  muito. Por alguma razão, nós seres humanos, somos muito fáceis de enganar e somos propensos a acreditar em qualquer coisa. Acho que somos animais com fé excessiva. É um ponto forte, mas é um enorme ponto fraco também. Eu pessoalmente sou cético, mas aí me chamam de &ldquo;do contra&rdquo;, vai entender. Vou continuar não sendo o trouxa, só isso.</p>
<p>Um ChatGPT, se eu quisesse simplificar bastante, não é muito diferente da Eliza, em conceito. É um programa que usa um modelo, um dicionário de probabilidades, que mostra frases de acordo com o que se digitou antes, roboticamente, automaticamente, pegando palavras cujos valores de probabilidade fazem mais sentido dado as palavras anteriores. Não existe nenhuma emoção envolvida. Não existe simpatia, não existe compaixão, não existe amargura. Nada. Não tem uma linha de código, nem de dados no modelo, que representam qualquer emoção. É apenas uma calculadora, que em vez de devolver números, devolve conjuntos de palavras. Só isso.</p>
<p>Mas e as IAs que geram imagens inéditas? Vide o próprio Dall-e 2 da OpenAI, vide o Midjourney, vide o Stable Diffusion, vide os novos plugins de geração e ajustes de imagens proprietário da Adobe, que começaram a ser distribuídos no Creative Cloud. Classificação de imagens e geração de novas imagens são campos diferentes dentro de inteligência artificial. Só pra não perder o gancho, deixa eu jogar na mesa mais alguns conceitos.</p>
<p>Um campo que existe desde pelo menos os anos 80 é o estudo sobre CNNs. (piada) Não, não, não é esse, é um CNN útil. Convolutional Neural Networks, que é um modelo que dizem que é bom pra processar dados que se parecem com grades, como uma grade de pixels, que é como representamos uma imagem. As tais camadas convolucionais, não sei se é assim que fala em português, aplicam vários filtros ou kernels, aos dados de pixels pra criar um mapa de funcionalidades. </p>
<p>Em resumo, esses filtros extraem características da imagem. O equivalente a tokenizar um texto e gerar palavras ou n-grams. Mas CNNs ficaram super famosos só depois de 2012 quando saiu a AlexNet de Alex Krizhevsky, Ilya Sutskever e Geoffrey Hinton. (piada) Arregacei os nomes, mas beleza. Era um concurso que todo ano o vencedor ficava um pouco melhor do que o do ano anterior. Mas em 2012, o salto foi uma ordem de grandeza melhor. Não lembro os números, mas faz de conta que todo ano os melhores algoritmos conseguiam identificar 85% das imagens, no ano seguinte, 86%, aí do nada deu um salto pra 99%. Isso gerou um enorme interesse na comunidade de pesquisa em cima de classificação de imagens.</p>
<p>Em paralelo, em 2014 surgiu o conceito de GANs, Generative Adversarial Networks ou redes generativas adversárias, desenvolvido pelo Ian Goodfellow e seus colegas. Foi um avanço no conceito de geração de imagens. De forma simplificada é como se fossem duas IAs uma competindo com a outra. Um gerador cria imagens e um discriminador avalia. Por exemplo, digamos que quero gerar imagens de gatos, o gerador faz as imagens, e o discriminador tenta identificar se é um gato mesmo. É um sistema que acelera o processo de aprendizado por fornecer feedbacks mais rápidos do que um treinamento supervisionado por humanos.</p>
<p>CNNs e GANs eu mencionei mais pra vocês saberem alguns nomes importantes. Mas o Dall-e da OpenAI não usa GANs, ele usa um derivado do modelo de transformers do GPT só que aplicado a imagens. Assim como geração de respostas de texto, onde ele vai prevendo uma palavras após a outra, levando o contexto anterior em consideração, o Dall-e também usa uma arquitetura de transformers, criando imagens um pedaço atrás do outro. Ou seja, também usa mecanismos de auto-atenção em vez de camadas recorrentes ou convolucionais tradicionais.</p>
<p>Essa não é a única forma de se criar um gerador de imagens. Mais do que o Dall-e, que é fechado, eu gosto mais do Stable Diffusion que, assim como o LLaMA da Meta, também foi liberado publicamente como modelo aberto. Quem me acompanha no Instagram viu quando eu fiquei brincando de usar o Stable Diffusion com outras ferramentas abertas pra fazer remasterização, upscaling de video. Assim eu pegava um video super antigo com qualidade de DVD e conseguia fazer ele redesenhar uma versão 4K. Se não viu isso, veja o destaque &ldquo;Usando IA&rdquo; no meu Insta. Enfim, diferente do Dall-e que usa transformers, o Stable Diffusion, como o próprio nome diz, usa um modelo de difusão.</p>
<p>Esse modelo gera imagens simulando um processo aleatório rodando em reverso, ou seja, o processo de geração começa com uma simples distribuição, como um Gaussian noise, literamente um gerador de barulho aleatório mesmo, e vai gradualmente refinando esse barulho, passo a passo, até chegar numa imagem que parece com os dados que o modelo foi treinado. É meio como partir de um punhado de argila, tudo bagunçado, e passo a passo ir esculpindo e refinando, até chegar numa estátua. Esse processo de refinamento é dirigido por uma rede neural que aprende a predizer o próximo passo da difusão. E por isso se chama Stable Diffusion, ou Difusão Estável.</p>
<p>Um site que parece que tem ganhado relevância na comunidade de pesquisa e desenvolvimento de ferramentas e modelos de IA, é o HuggingFace. Ele serve como um repositório. É de lá que podemos baixar os modelos do LLaMA, Alpaca, Vicuna em todos os diferentes tamanhos e formatos. Existem modelos específicos pra texto como o Vicuna, específico pra imagens como o do Stable Diffusion, conversor de texto pra áudio como o Speech T5 do Facebook e muitos outros. O ponto é que não existe uma única inteligência unificada que faz tudo, existem modelos isolados e independentes feitos pra tarefas específicas. Um GPT não sabe converter texto em áudio, isso é outro modelo.</p>
<p>Portanto, quando ver produtos web que parecem uma única inteligência, não é, é um integrador que divide o que você pediu entre diversas inteligências diferentes. O que me leva a outro tema que queria tocar de leve, mesmo porque só comecei a estudar isso recentemente. Eu expliquei como as coisas funcionam, superficialmente, por baixo dos panos. Mas agora precisamos falar do que fica por cima dos panos, o que usuários normais como eu e você enxergam, a interface.</p>
<p>Não só interface gráfica web de chat, mas interface de APIs e tudo mais. Surgiu um framework que tem ganhado cada vez mais relevância na comunidade que está investindo pra construir ferramentas integradas com os diversos serviços de IA que mencionei, como GPT, Bard, Stable Diffusion e outros. Esse framework se chama LangChain. A grosso modo, pense um framework como Django, ou Laravel, ou Rails, ou Spring, mas feito pra construir aplicativos que usam IA.</p>
<p>Eu gostei particularmente da documentação. Pra um projeto de código aberto novo, tem material suficiente de estudo, incluindo pesquisa de papers acadêmicos sendo publicados agora. Em particular queria tocar no ponto de prompts. A moda dos parasitas sangue-suga agora, é criar cursos online de prompts. Já viram por aí? &ldquo;Vire um Engenheiro de Prompt&rdquo;. É a coisa mais idiota que já vi.</p>
<p>Seria o equivalente a você se chamar de &ldquo;Engenheiro de Pesquisa no Google&rdquo;. Já deixo a dica pra você: não faça nenhum deles. Sem nem olhar, posso garantir que quase todos são pega-trouxa. Lembra aquele ditado? Todo dia um malandro e um otário acordam e vão pra rua, e quando se encontram, rola negócio. Não seja o otário.</p>
<p>Enfim, o LangChain é um framework extenso. Não pense algo simples como um Express de Node.js. Isso tá mais pra um Spring de Java. Tem diversos conceitos, como Models pra interfacear com os diversos serviços de IA, como o ChatGPT. A grosso modo esses Models seriam como um Hibernate, um ORM pra IAs. Mas tem outras abstrações. Tem Agentes. Tem Correntes. Tem Índices, mas uma das partes interessantes é que tem Prompts.</p>
<p>Diferente desses cursinhos idiotas por aí, que ficam mais no esquema &ldquo;olha só, eu testei uns prompts aqui e vou compartilhar com vocês&rdquo; ou então povo que fica copiando teorias da conspiração de prompt que surgem no Reddit. De fato existem pesquisas acadêmicas sendo feitas no estudo das melhores formas de se fazer perguntas pra passar pra um transformador generativo como GPT. Lembram o que eu falei? Os transformers só continuam adicionando palavras na frente do prompt que você escreveu. Quanto melhor escrito for o prompt, maiores as probabilidades de se conseguir as respostas que procura. Ele vai conseguir prever melhor as palavras seguintes.</p>
<p>O melhor prompt não é escrever um textão aleatório, mas sim conciso e estruturado. Na documentação eles fazem links pros diversos papers acadêmicos com o estudo pra cada tópico. Eu não levaria 100% a sério, nem todos os papers são consenso, muita coisa é só teoremas e hipóteses. Mas é melhor do que chutes do YouTuber.</p>
<p>Com um LangChain, eu poderia fazer um Model que se integra com o meu Vicuna rodando localmente, daí poderia não depender de serviços de terceiros e também manter a privacidade dos meus dados, já que não preciso compartilhar nada com ninguém. Se alguém tiver interesse de estudar, pesquisar ou até empreender com essa nova geração de transformadores, essas são ferramentas que podem ser muito úteis. Esquece curso idiota que é &ldquo;duh, olha só como sou inteligente, sei integrar com a API da OpenAI&rdquo;. Sério, isso é o básico do básico do básico, você aprende em 10 minutos num blog post. Não tem absolutamente nada demais. </p>
<p>Aliás, um curso de IA que se preza, tem por obrigação ter tudo que eu falei neste vídeo, só que explicado com 10 vezes mais detalhes. Caso contrário não vale seu dinheiro e muito menos seu tempo. Não perca tempo com esses cursos caça níquel. São todos perda de tempo. Veja os links que deixei na descrição do video e estude um a um que no final você vai aprender muito melhor, e de graça. O hype em torno de IA está fora de proporção. Um prato cheio pros oportunistas de plantão.</p>
<p>Pra finalizar, acho que vale a pena voltar na questão que incomoda todo programador iniciante: &ldquo;mas será que com essa evolução rápida de transformers, não é questão de tempo até ele substituir todos os programadores?&rdquo;. Eu já tinha feito um video só pra responder isso e recomendo que assistam, mas considerando o que expliquei hoje, vou explicar porque, tecnicamente, isso não vai acontecer.</p>
<p>Primeiro. Um transformer é incapaz de gerar textos sobre assuntos que nunca viu na vida. Isso é importante de entender. Vamos recordar, o que é um modelo, é um banco de dados de tokens retirados dos textos de treinamento e mais importante: as probabilidades de um token pra outro token dentro de uma rede. O modelo não contém o texto original, só pesos.</p>
<p>Vou repetir porque isso é importante: nenhum texto original aparece na sua forma original, dentro do modelo. Pra ter na cabeça pense assim. Já viram que vários livros no final tem algumas páginas com um &ldquo;índice remissivo&rdquo;? São todas as palavras importantes que aparecem no texto do livro e as páginas onde aparecem. É que nem um índice rudimentar de banco de dados.</p>
<p>Quando fazemos um prompt e pedimos, sei lá, pra ele citar um trecho da peça King Lear, de Shakespeare. Vai gerar o trecho, se não igual, muito próximo ao trecho do texto original. E isso confunde, pois parece que então, ele tem o texto inteiro guardado em algum lugar. Mas não. Por exemplo, pedi pra ele citar um diálogo entre Albany e Cordelia e corretamente diz que não existe diálogo entre os dois na peça, e sugere outra cena, Ato 4, Cena 2, onde Albany fala com sua esposa Goneril. E segue o trecho exatamente como está na peça. </p>
<p>Novamente, é muito difícil traçar o caminho exato dentro do modelo, indo de probabilidade em probabilidade pra reverter o processo e descobrir como o GPT conseguiu escrever o texto. Mas podemos dar um chute educado: a sequência de palavras do diálogo tem altíssima probabilidade. Primeiro, no meu prompt tem todas as palavras-chave importantes pro contexto como &ldquo;Shakespeare&rdquo;, &ldquo;Albany&rdquo; e tudo mais.</p>
<p>Quando ele começa digitando o trecho &ldquo;You are&rdquo; no contexto to diálogo, a chance maior é da próxima palavra ser &ldquo;not&rdquo;, depois da palavra ser &ldquo;worth&rdquo;, depois ser &ldquo;the&rdquo; e depois &ldquo;dust&rdquo;. São as probabilidades mais altas dessa sequência. Ele não tem o texto original, mas tem as probabilidades, que permitem remontar uma boa parte do texto original.</p>
<p>Até certo ponto, a grosso modo, o modelo acaba servindo como uma versão comprimida dos textos originais, mas com perdas. Quebramos todos os textos em tokens e gravamos os relacionamentos de forma que é possível recuperar alguns desses textos. Mas não podemos garantir que é possível recuperar tudo, porque a mistura de alguns textos similares vai desajustar as probabilidades.</p>
<p>No final teríamos no máximo uma versão mesclada de dois ou mais textos. Quanto mais repetidas vezes um determinado texto aparece em diversas fontes no material de treinamento, maiores as chances de conseguir reconstruir depois. Shakespeare, tendo obras que foram analisadas, discutidas e citadas inúmeras vezes em várias fontes, tem maiores chances de ser reconstruído.  </p>
<p>Sobre mim, Fabio Akita, já vai ser mais difícil, porque no contexto geral da Web, eu sou extremamente pouco citado. Mesmo assim, o GPT 4 até que consegue fazer uma descrição super genérica sobre mim. Fala coisas certas, por exemplo, que sou brasileiro, que fundei a CodeMiner, que palestrei em eventos e ajudei a divulgar Ruby on Rails. Mas aí fala que também ajudei a divulgar metodologias Ágeis, que não é mentira, mas nunca foi um ponto importante.</p>
<p>Ele fala que fui &ldquo;keynote speaker&rdquo;, ou seja, que dei palestra de abertura em muitas conferências, o que não é verdade. Ele já começou a misturar informações de outras pessoas similares. Mas isso não é acidente, é porque comparado a um Shakespeare, não tem quase nada sobre mim na Web, e as probabilidades são muito parecidas com outros palestrantes de tecnologia da mesma área.</p>
<p>Por isso também é difícil pra ele citar fontes exatas, porque cada palavra do texto que gera pode ter vindo de um lugar diferente. Não tem como saber exatamente. Pra ter links pra fontes, como o Bing faz, especulo que precisa de um processo em duas ou mais etapas. Primeiro, gerar o texto da resposta, como um GPT faz. Depois, pegar esse texto e fazer uma pesquisa tradicional no Bing antigo. Daí cruzar as duas informações e ver se os textos minimamente batem, e finalmente apresentar juntas, como se a resposta já tivesse saído pronto com os links pras fontes. </p>
<p>Não é o transformer que dá os links, é um segundo processo separado. Porque o modelo do transformer, em si, não tem como garantir exatamente qual parte veio de onde. Quanto mais um certo assunto tiver textos pra treinar, mais precisas vão ser as respostas. Por isso é mais fácil conseguir informações sobre celebridades, do que sobre alguém como eu ou vocês. No nosso caso, nossas informações vão ser misturadas com de outras pessoas similares, porque as probabilidades não vão ajudar. </p>
<p>Lembra a temperatura de &ldquo;criatividade&rdquo;? Ela garante que, mesmo sem saber a resposta exata, o ChatGPT vai continuar tentando responder baseado nas probabilidades que tem no modelo atual. Mas não tem jeito, toda resposta sobre coisas que não tinha no material original de treinamento, ou tinha muito pouco, vai ser uma alucinação probabilística. Só isso. E vai estar errado. E isso vai acontecer com tudo que for novidade. No dia 1, todo transformer vai ser praticamente inútil.</p>
<p>Outra coisa, mesmo sendo capaz de ler código, explicar código dos outros e gerar códigos, ainda não vai ter a capacidade de gerar um projeto inteiro. Lembra os códigos vazados do Twitch ou o código de ranking do Twitter que eu analisei? Estamos falando de milhares de arquivos, centenas de milhares de linhas de código. Sabe qual é um dos calcanhares de aquiles de toda IA? Backtracking e memória. As respostas costumam ser boa hoje porque ele leva em consideração o texto anterior. Em partes.</p>
<p>Existe um limite de quanto consegue voltar atrás no texto anterior. No caso do GPT 3 e 4 atual, ele se limita a enxergar uma janela de contexto de no máximo 2048 tokens. E as respostas também tem um limite. Quanto mais longo for esse limite, mais caro fica pra processar o resultado seguinte do gerador de palavras, maiores as chances dele começar a &ldquo;esquecer&rdquo; o contexto e muito maiores as chances de rapidamente perder a coerência de respostas longas.</p>
<p>Entendam: o GPT atual não tem capacidade de ler mais que 2048 tokens. Então jogar o código inteiro da kernel do Linux pra ele analisar, por exemplo, é impossível. E pedir pra ele escrever um código desse mesmo tamanho, é mais impossível ainda. Lógico, nada é impossível, mas é altamente improvável. No meu Vicuna rodando local, tem essa tela de configuração pra justamente configurar o tamanho da janela de contexto e de resposta. E não é um número muito grande não,</p>
<p>Num teste não científico, tentei abrir duas janelas do projeto text-generation que é a interface web que estou usando em cima do Vicuna. Não sei se é uma precaução da interface web ou uma limitação do próprio Vicuna por baixo, mas só tem capacidade de responder uma pergunta de cada vez. Se eu abrir outro navegador e tentar fazer outra pergunta, enquanto na outra janela ele ainda não terminou de responder a primeira, vai ignorar e não deixa rodar em paralelo.</p>
<p>E mesmo se conseguisse. Só com um único processo de resposta, está consumindo, no mínimo, 50% da minha GPU, consumindo mais de 18 gigabytes de VRAM, e enquanto responde esse consumo continua aumentando. Ou seja, numa máquina como a minha, ele só consegue responder uma pergunta de cada vez. Você está pensando em fazer um serviço online, SaaS, pra vários usuários acessarem? Vai sair super caro, porque só vai possibilitar uma resposta a cada 2 a 5 segundos. Um servidor web pequeno tem capacidade pra responder, sei lá, 1000 requisições por segundo ou mais. Mas um Vicuna está mais pra 0.2 requisições por segundo. É ridiculamente pesado e não escala.</p>
<p>Não lembro que artigo que a OpenAi falava que cada dia de operação do ChatGPT custa 1 milhão de dólares. Sim, 1 milhão, por dia. Mais ou menos faz sentido, dado o custo de GPU necessário pra responder todo mundo. Mas claro, novas técnicas de otimização tem aparecido, como o exemplo de quantização que expliquei antes, mas estamos falando que precisa melhorar ainda umas 10 mil vezes pra ficar economicamente viável pra casos como escrever um livro inteiro de 500 páginas, sem perder coerência, ou escrever um projeto de verdade com milhares de arquivos de código. No estágio atual da tecnologia, isso é impossível.</p>
<p>Essas são só algumas das razões de porque um ChatGPT não vai substituir programadores e outras profissões. A partir de um certo ponto fica tão caro que não compensa, especialmente porque como já expliquei, essa ¨inteligência&quot; não é inteligente, só cospe probabilidades. Não tem criatividade, não sabe tomar decisões baseadas em coisas como custo-benefício ou bom senso. Não vai ter coerência em respostas longas, não tem noção se o código sendo cuspido tem segurança, ou escalabilidade. Ela tem memória menor que um peixinho dourado. 2048 tokens. Mesmo se aumentar 10x esse limite, ainda é super pouco. O script deste episódio tem 15 mil palavras. O ChatGPT é incapaz de escrever meus scripts.</p>
<p>Mas tudo bem, como uma ferramenta de auxílio, é super boa. Trechos pequenos e repetitivos, ou código trivial que facilmente encontramos num stackoverflow repetidas vezes. O estado atual de transformers é mais que suficiente pra acelerar um bom profissional. Hoje é impossível e vai continuar sendo impossível alguém que não é programador conseguir fazer um projeto complexo inteiro. Qualquer um que diga o contrário não tem a menor noção do que está falando. Considere como os algoritmos funcionam e considere os pontos de máximo e mínimo. Não basta dizer &ldquo;ah, eu tenho esperança, é só ter fé&rdquo;. Precisamos de números e os números dizem que é extremamente improvável.</p>
<p>Eu uso o GitHub Copilot faz meses. Em vários videos, toda vez que aparece trechos de código, usei a ajuda do ChatGPT ou Copilot. Ambos tem plugins pra vários editores como NeoVim ou VS Code e funcionam super bem. Tem capacidade de ler o código do arquivo que se está editando. Não consegue usar o projeto inteiro de contexto pelos limites que acabei de explicar, mas só de usar o trecho próximo do que está digitando, já ajuda muito. Ou seja, parte do prompt é o código do arquivo aberto naquele momento. </p>
<p>Pra mim, vale cada centavo. E todo desenvolvedor que quiser ter vantagens de produtividade, deveria considerar usar esses plugins. De novo, não vai conseguir fazer tudo pra você, e por causa da aleatoriedade e alto risco de alucinações, você nunca deve aceitar o código que ele sugere sem ler com muita calma antes. Mas pra tarefas bem braçais, tediosas, onde faríamos muito copy e paste, como fazer testes unitários simples, ajuda. Testem.</p>
<p>Com tudo isso que expliquei, minha conclusão pessoal é que não estamos nada perto do que o povo chama de AGI, ou Inteligência Artificial Geral. A IA que vai superar todas as IAs. A IA que vai pegar o código inteiro do GPT 4 e sozinho gerar um GPT 5 melhorado. O coitado male male tem capacidade de lembrar de um único arquivo de código, que dirá milhares. Mesmo se conseguisse, ele não estará &ldquo;analisando&rdquo;. Ele não analisa nada. Ele não sabe analisar. Não sabe as regras de cálculo, as regras de programação. Só sabe juntar pedaços que viu repetidas vezes de muitas fontes, 100% via probabilidades. Quando não sabe, vai pegar as probabilidades mais próximas e cuspir o que der, e vai ser um texto sem sentido nenhum.</p>
<p>Vou repetir: transformers não tem inteligência nenhuma de análise e cognição. Ele não sabe porque 2 mais 2 é 4, só leu muitas vezes que é, e as probabilidades fazem ele repetir isso. GPT quer dizer Transformer Generativo Pré-Treinado. Hoje você entendeu o que esses termos significam. E se ficou até aqui, espero que tenha entendido: por mais impressionante que pareçam os textos que gera, é só um gerador de textos, um auto-corretor de teclado de celular glorificado.</p>
<p>Um gerador de textos nunca vai ser inteligente. Só vai &ldquo;parecer&rdquo; inteligente. Assim como o formato de animal que você viu numa nuvem foi totalmente um acidente. Ninguém teve a intenção de desenhar um animal na nuvem, sequer é um animal, é só um formato aleatório. Foi você que escolheu entender assim. </p>
<p>E foi por isso que no outro episódio de ChatGPT eu afirmei e repito: Seu grau de empolgação com IA é inversamente proporcional ao seu entendimento de IA. Quanto menos entender, mais empolgado vai ficar. Vai acreditar que as ações da NVIDIA estarem a mais de 400 dólares é normal, porque acredita que negócios de IA vão aumentar absurdamente e em breve a ação vai atingir 1000 dólares. Vai investir todo seu dinheiro nisso. Já eu, acho que a ação da NVIDIA está hiper valorizado por puro hype. O preço justo da ação deveria ser abaixo de 300 dólares, e vai voltar pra isso uma hora. Não sei quando vai estourar, mas eu estou precavido.</p>
<p>Não tenho dúvidas que transformers são úteis. Várias ferramentas já estão usando. Em particular o Creative Cloud da Adobe tá tirando bom proveito. A Microsoft vai embutir em Office, Windows e tudo mais. Mas no final é isso que vamos ter: um Alexa melhorado. Um template mais inteligente de Excel. Mas não ache que vai substituir seu Diretor Financeiro e que ele vai conseguir tomar decisões inteligentes. Só parece, por sorte e por acidente. Quem apostar em mais do que isso, nas tecnologias atuais, vai perder.</p>
<p>Pessoalmente eu acho que as tecnologias de transformers e outras que nem mencionei hoje que compõe o que é o GPT, assim como tudo, segue uma curva em S. Passamos pela parte do S que é o crescimento que parece exponencial, mas a curva tem um teto em cima. Quando pulamos de bilhões de parâmetros pra trilhões de parâmetros, vamos começar a ter retornos diminuídos, diminishing returns. Se amanhã sair um GPT 5 com o dobro de parâmetros, sei lá 300 trilhões, não vai ficar duas vezes melhor. Talvez melhore sei lá, 20%. Cada melhoria custa mais caro do que o retorno. Isso é diminishing returns. </p>
<p>Não existe crescimento infinito, e quanto mais rápido você força, só chega mais rápido no teto. Precisa acontecer novas descobertas, novas invenções que ainda não conhecemos, obviamente, pra ter uma nova etapa de evolução significativa. Enquanto isso não acontecer e só insistir no que temos até hoje, não vai ser muito melhor que isso não. Já estamos perto do limite. E AGI não está no horizonte ainda. É a mesma coisa ficar especulando se estamos pertos de conseguir construir uma USS Enterprise. É irrelevante, beira ficção científica.</p>
<p>Acho que já me estendi demais. Como falei no começo do video, eu não sou nem de longe um especialista no assunto, sequer tenho experiência prática de trabalhar em projetos relacionados. Meu conhecimento até agora é puramente teóricos. Eu só pesquisei o que existe publicado online. Se eu entendi até aqui em poucos dias, quem dedicar algumas semanas vai acabar sabendo muito mais do que eu, muito rapidamente. Não é tão difícil assim. Espero que tenha dado pra quebrar alguns mitos que tinham na cabeça e finalmente tenham conseguido separar o joio do trigo. Se ficaram com dúvida ou quiserem complementar, fiquem a vontade nos comentários abaixo. Se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>llms</category><category>modelos-locais</category><category>machine-learning</category><category>akitando</category></item></channel></rss>