<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Akitando - Transcrições | AkitaOnRails.com</title><link>https://akitaonrails.github.io/akitando/</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>Fri, 06 Nov 2026 17:43:21 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/akitando/index.xml" rel="self" type="application/rss+xml"/><item><title>Adicionando segurança de verdade nos seus apps GUI de Linux vibecoded</title><link>https://akitaonrails.github.io/2026/11/06/adicionando-seguranca-de-verdade-nos-seus-apps-gui-linux-vibecoded/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/11/06/adicionando-seguranca-de-verdade-nos-seus-apps-gui-linux-vibecoded/</guid><pubDate>Fri, 06 Nov 2026 13:00:00 -0300</pubDate><description>&lt;p&gt;Nas últimas semanas temos visto uma enxurrada de vulnerabilidades sendo expostas a torto e a direito com a ajuda de inteligência artificial. Eu já comentei sobre isso no artigo de &lt;a href="https://akitaonrails.github.io/2026/10/24/por-que-nao-fazemos-provas-formais-em-tudo-seguranca-cibernetica-com-ia/"&gt;provas formais e segurança cibernética com IA&lt;/a&gt;, quando projetos inteiros começaram a cair sob auditorias automatizadas.&lt;/p&gt;
&lt;p&gt;Eu acho excelente que isso esteja acontecendo. A maioria esmagadora dessas falhas vinha de riscos conhecidos há décadas, que continuavam em produção porque os desenvolvedores assumiam que ninguém perderia tempo vasculhando aquele canto do código.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Nas últimas semanas temos visto uma enxurrada de vulnerabilidades sendo expostas a torto e a direito com a ajuda de inteligência artificial. Eu já comentei sobre isso no artigo de <a href="/2026/10/24/por-que-nao-fazemos-provas-formais-em-tudo-seguranca-cibernetica-com-ia/">provas formais e segurança cibernética com IA</a>, quando projetos inteiros começaram a cair sob auditorias automatizadas.</p>
<p>Eu acho excelente que isso esteja acontecendo. A maioria esmagadora dessas falhas vinha de riscos conhecidos há décadas, que continuavam em produção porque os desenvolvedores assumiam que ninguém perderia tempo vasculhando aquele canto do código.</p>
<p>Com agentes de IA, explorar bugs triviais ficou barato demais. Aquela &ldquo;segurança por obscuridade&rdquo; que parecia proteger sistemas menores evaporou da noite pro dia.</p>
<p>Você precisa se preocupar com segurança desde o primeiro commit. O único jeito de desenvolver esse músculo é adotar uma postura de <em>security first</em> o tempo inteiro, colocando segurança até no seu <em>hello world</em>.</p>
<h2>Um caso real: meu consolidador financeiro pessoal<span class="hx:absolute hx:-mt-20" id="um-caso-real-meu-consolidador-financeiro-pessoal"></span>
    <a href="#um-caso-real-meu-consolidador-financeiro-pessoal" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Para mostrar como isso funciona na prática, vou usar como exemplo um aplicativo desktop de investimentos e acompanhamento bancário que construí para uso próprio. Eu mantenho o código no meu servidor Gitea privado aqui em casa e jamais vou abrir o código dele, por motivos óbvios.</p>
<p>Ainda assim, vale compartilhar a arquitetura de segurança que desenhei para ele. Encare este artigo como <strong>uma</strong> maneira de resolver o problema (entre várias possíveis), para servir de inspiração e ponto de partida para você estudar mais a fundo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/06/seguranca-gui-linux/demo-overview.png" alt="Tela inicial do aplicativo rodando em modo de demonstração com dados falsos"  loading="lazy" /></p>
<p>O aplicativo é uma GUI local escrita em Rust com interface em QtQuick/QML (usando Quickshell) e um banco de dados SQLite local. Ele consolida saldos, carteira de ações, vencimento de títulos de renda fixa, cálculo de <em>covered calls</em> e projeções de caixa (<em>runway</em>) em uma única tela. Vale avisar que absolutamente todos os números, saldos, instituições (<em>Institution Alpha</em>, <em>Institution Beta</em>) e tickers que aparecem nas capturas de tela acima e abaixo são <strong>dados 100% falsos</strong>, gerados pelo modo <code>--demo</code> do próprio aplicativo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/06/seguranca-gui-linux/demo-portfolio.png" alt="Aba de portfólio exibindo posições e ativos fictícios do modo de demonstração"  loading="lazy" /></p>
<p>Por que o aplicativo precisa automatizar um navegador web em vez de bater numa API REST bonitinha? Porque a maioria dos portais de <em>web banking</em> e corretoras para pessoa física tem uma usabilidade terrível e zero API para clientes individuais.</p>
<p>Fazer isso na mão exigiria entrar em cada banco, consultar extratos por período, baixar planilhas Excel ou PDFs de notas de corretagem e copiar tudo para outro lugar. No meu fluxo atual, o app abre um navegador controlado, eu faço o login e o 2FA manualmente na página do banco, clico em um botão <em>&ldquo;Take over&rdquo;</em> e a automação assume apenas para ler os extratos, alimentar o banco de dados e encerrar a sessão.</p>
<h2>O pecado original da versão experimental<span class="hx:absolute hx:-mt-20" id="o-pecado-original-da-versão-experimental"></span>
    <a href="#o-pecado-original-da-vers%c3%a3o-experimental" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Na primeira versão experimental (aquela que a gente faz no embalo do <em>vibecoding</em> só para ver os gráficos funcionando na tela), eu cortei todos os caminhos possíveis:</p>
<ul>
<li>O banco SQLite ficava aberto em texto puro no disco, com permissão <code>0644</code>, legível por qualquer processo rodando com o meu usuário.</li>
<li>O navegador instrumentado salvava cookies e sessões em um perfil persistente no disco para acelerar o próximo acesso.</li>
<li>As credenciais dos bancos ficavam guardadas no chaveiro da sessão (<code>secret-tool</code>), permitindo que um timer do <code>systemd</code> rodasse em <em>background</em> sem intervenção humana.</li>
<li>O Chromium subia com <code>--no-sandbox</code> e abria a porta <code>9333</code> do Chrome DevTools Protocol (CDP) em <code>localhost</code> com <code>--remote-allow-origins=*</code>.</li>
<li>Os códigos de verificação (OTP) eram lidos automaticamente da minha caixa de e-mail sincronizada localmente via <code>mbsync</code>.</li>
</ul>
<p>Eu costumo ser bem paranoico com segurança, e justamente por isso comecei a ficar ansioso sabendo que esses dados estavam dando sopa em texto puro no SSD. Mesmo sendo o meu PC pessoal, trancado no meu escritório, onde ninguém mais tem acesso físico, qualquer pacote malicioso de <code>npm</code>, extensão de navegador, script Python ou binário comprometido rodando com o meu UID teria acesso integral à minha vida financeira.</p>
<blockquote>
  <p>Se um único processo comprometido no seu usuário consegue fazer <code>cat</code> no seu banco de dados ou conectar na porta de debug do seu navegador logado no banco, você tem um problema grave de arquitetura.</p>

</blockquote>
<p>Decidi parar tudo e reescrever a fundação de segurança do projeto como se fosse um sistema crítico de produção. Vamos passar por cada camada passo a passo, num nível que qualquer desenvolvedor júnior consiga acompanhar e reproduzir nos próprios projetos.</p>
<h2>Passo 1: criptografando o SQLite com SQLCipher e migração verificada<span class="hx:absolute hx:-mt-20" id="passo-1-criptografando-o-sqlite-com-sqlcipher-e-migração-verificada"></span>
    <a href="#passo-1-criptografando-o-sqlite-com-sqlcipher-e-migra%c3%a7%c3%a3o-verificada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O primeiro passo óbvio era tirar o banco SQLite do texto puro. No ecossistema SQLite, o padrão ouro para criptografia transparente de páginas é o <strong>SQLCipher</strong> (disponível em Rust via feature <code>bundled-sqlcipher-vendored-openssl</code> da crate <code>rusqlite</code>).</p>
<p>Em vez de apenas criptografar colunas soltas, o SQLCipher criptografa o arquivo inteiro página por página (usando AES-256 em modo CBC com autenticação HMAC-SHA512). Nem os nomes das tabelas, nem os índices e nem o cabeçalho padrão do arquivo (<code>SQLite format 3\0</code>) ficam visíveis no disco.</p>
<p>Para garantir que nenhuma conexão abra o banco com parâmetros fracos por engano, toda conexão passa por um inicializador que rejeita cabeçalhos em texto puro e aplica o perfil criptográfico antes de qualquer query:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">RAW_SQLITE_MAGIC</span>: <span class="kp">&amp;</span><span class="p">[</span><span class="kt">u8</span><span class="p">;</span><span class="w"> </span><span class="mi">16</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="sa">b</span><span class="s">&#34;SQLite format 3</span><span class="se">\0</span><span class="s">&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">CIPHER_PAGE_SIZE</span>: <span class="kt">u32</span> <span class="o">=</span><span class="w"> </span><span class="mi">4096</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">CIPHER_KDF_ITER</span>: <span class="kt">u32</span> <span class="o">=</span><span class="w"> </span><span class="mi">256_000</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">apply_cipher_pragmas</span><span class="p">(</span><span class="n">conn</span>: <span class="kp">&amp;</span><span class="nc">Connection</span><span class="p">,</span><span class="w"> </span><span class="n">key</span>: <span class="kp">&amp;</span><span class="nc">DatabaseKey</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">key_pragma</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">format!</span><span class="p">(</span><span class="s">&#34;PRAGMA key = </span><span class="se">\&#34;</span><span class="si">{}</span><span class="se">\&#34;</span><span class="s">;&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">key</span><span class="p">.</span><span class="n">sqlcipher_raw_key_literal</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">conn</span><span class="p">.</span><span class="n">execute_batch</span><span class="p">(</span><span class="o">&amp;</span><span class="n">key_pragma</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="o">|</span><span class="n">_</span><span class="o">|</span><span class="w"> </span><span class="n">VaultError</span>::<span class="n">InvalidKeyOrCorruptDatabase</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">conn</span><span class="p">.</span><span class="n">execute_batch</span><span class="p">(</span><span class="o">&amp;</span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA cipher_compatibility = 4;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA cipher_page_size = </span><span class="si">{CIPHER_PAGE_SIZE}</span><span class="s">;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA kdf_iter = </span><span class="si">{CIPHER_KDF_ITER}</span><span class="s">;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA512;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA cipher_hmac_algorithm = HMAC_SHA512;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA cipher_plaintext_header_size = 0;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA cipher_memory_security = ON;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA foreign_keys = ON;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA secure_delete = ON;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA temp_store = MEMORY;
</span></span></span><span class="line"><span class="cl"><span class="s">        PRAGMA trusted_schema = OFF;
</span></span></span><span class="line"><span class="cl"><span class="s">        &#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">))</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">_</span>: <span class="kt">i64</span> <span class="o">=</span><span class="w"> </span><span class="n">conn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">query_row</span><span class="p">(</span><span class="s">&#34;SELECT count(*) FROM sqlite_schema;&#34;</span><span class="p">,</span><span class="w"> </span><span class="p">[],</span><span class="w"> </span><span class="o">|</span><span class="n">row</span><span class="o">|</span><span class="w"> </span><span class="n">row</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map_err</span><span class="p">(</span><span class="o">|</span><span class="n">_</span><span class="o">|</span><span class="w"> </span><span class="n">VaultError</span>::<span class="n">InvalidKeyOrCorruptDatabase</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</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>Para quem está começando, vale entender o papel de cada <code>PRAGMA</code> ali em cima:</p>
<ul>
<li><code>kdf_iter = 256000</code> e <code>PBKDF2_HMAC_SHA512</code> fazem a derivação da chave passar por mais de 250 mil rodadas de hash SHA-512, tornando tentativas de força bruta muito lentas.</li>
<li><code>cipher_plaintext_header_size = 0</code> criptografa inclusive os primeiros bytes do arquivo (onde fica o <em>salt</em> de 16 bytes), de modo que ferramentas externas como <code>file</code> ou <code>hexdump</code> enxergam apenas ruído aleatório.</li>
<li><code>temp_store = MEMORY</code> e <code>cipher_memory_security = ON</code> impedem que tabelas temporárias de ordenação vazem para <code>/tmp</code> em texto puro e limpam a memória alocada pelo SQLite antes de devolvê-la ao sistema operacional.</li>
<li><code>trusted_schema = OFF</code> evita que funções SQL ou <em>triggers</em> maliciosos embutidos num arquivo de banco de dados executem código inesperado.</li>
</ul>
<h3>Como migrar um banco existente sem perder dados<span class="hx:absolute hx:-mt-20" id="como-migrar-um-banco-existente-sem-perder-dados"></span>
    <a href="#como-migrar-um-banco-existente-sem-perder-dados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando você já tem meses de histórico financeiro num banco SQLite em texto puro, rodar um script de migração no susto é pedir para corromper dados. Eu implementei uma migração transacional com verificação matemática de integridade (<em>logical fingerprint</em>):</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">compute_logical_fingerprint</span><span class="p">(</span><span class="n">conn</span>: <span class="kp">&amp;</span><span class="nc">Connection</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Sha256</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">update_tagged</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;fingerprint-version&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;1&#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">objects</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">load_schema_objects</span><span class="p">(</span><span class="n">conn</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">update_tagged</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;schema-object-count&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">objects</span><span class="p">.</span><span class="n">len</span><span class="p">().</span><span class="n">to_string</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">obj</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&amp;</span><span class="n">objects</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">update_tagged</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;schema-object-kind&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">obj</span><span class="p">.</span><span class="n">kind</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">update_tagged</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;schema-object-name&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">obj</span><span class="p">.</span><span class="n">name</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">update_tagged</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;schema-object-sql&#34;</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">obj</span><span class="p">.</span><span class="n">sql</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">table</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">objects</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="n">o</span><span class="o">|</span><span class="w"> </span><span class="n">o</span><span class="p">.</span><span class="n">kind</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="s">&#34;table&#34;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">hash_table_rows</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="n">table</span><span class="p">.</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">hasher</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(</span><span class="n">hex_encode</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hasher</span><span class="p">.</span><span class="n">finalize</span><span class="p">()))</span><span class="w">
</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 processo completo de migração segue um roteiro estrito:</p>
<ol>
<li>Pausa qualquer timer antigo e executa <code>PRAGMA wal_checkpoint(TRUNCATE)</code> para consolidar o arquivo WAL no banco principal.</li>
<li>Faz uma cópia de segurança com permissão <code>0600</code> e calcula o hash SHA-256 lógico de todo o esquema e de todas as linhas ordenadas de todas as tabelas.</li>
<li>Usa <code>sqlcipher_export</code> para copiar os dados para um novo banco criptografado, confere que o hash lógico permaneceu idêntico e importa também os PDFs de notas de corretagem que antes ficavam soltos em pastas de cache para dentro de uma tabela <code>documents</code> criptografada.</li>
<li>Gera um pacote de backup <code>.mbbackup</code>, restaura esse backup em um diretório temporário e recalcula o hash lógico pós-upgrade.</li>
<li>Só quando todos os hashes batem bit a bit, o sistema arquiva o banco antigo e move o banco criptografado para o caminho oficial usando a syscall <code>renameat2</code> com a flag <code>RENAME_NOREPLACE</code> (que recusa sobrescrever qualquer backup existente por acidente).</li>
</ol>
<h2>Passo 2: onde guardar a chave? TPM 2.0, Polkit e queda de privilégios<span class="hx:absolute hx:-mt-20" id="passo-2-onde-guardar-a-chave-tpm-20-polkit-e-queda-de-privilégios"></span>
    <a href="#passo-2-onde-guardar-a-chave-tpm-20-polkit-e-queda-de-privil%c3%a9gios" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Criptografar o banco de dados cria imediatamente o problema clássico de qualquer sistema de segurança: <strong>onde você guarda a chave que abre o banco?</strong></p>
<p>Se você colocar a chave num arquivo <code>.env</code> ou <code>config.json</code> na sua pasta <code>$HOME</code>, você só trocou um cadeado aberto por um cadeado com a chave pendurada nele. Se você pedir para o usuário digitar uma senha toda vez e derivar a chave diretamente dessa senha, qualquer senha humana curta será o elo mais fraco; além disso, se a interface gráfica em QML ler essa senha, o processo da UI passa a ter acesso ao segredo.</p>
<p>A solução que implementei combina três recursos nativos do Linux moderno: o <strong>TPM 2.0</strong>, o <strong><code>systemd-creds</code></strong> e o <strong>Polkit (<code>pkexec</code>)</strong>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/06/seguranca-gui-linux/diagram-1-tpm-polkit-unlock.jpg" alt="Diagrama do fluxo de desbloqueio com Polkit, TPM 2.0 e queda de privilégios via FD 3"  loading="lazy" /></p>
<h3>O que é um TPM?<span class="hx:absolute hx:-mt-20" id="o-que-é-um-tpm"></span>
    <a href="#o-que-%c3%a9-um-tpm" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Para quem nunca mexeu com isso: <strong>TPM</strong> (<em>Trusted Platform Module</em>) é um pequeno chip criptográfico dedicado que já vem soldado na placa-mãe de praticamente qualquer PC ou notebook moderno (ou integrado no próprio processador, como Intel PTT e AMD fTPM).</p>
<p>Diferente de um arquivo no seu SSD, o TPM possui chaves internas gravadas em hardware que <strong>nunca saem de dentro do chip</strong>. Você pode entregar um bloco de bytes para o TPM (no nosso caso, uma chave aleatória de 256 bits gerada via CSPRNG do kernel) e pedir para ele &ldquo;selar&rdquo; (<em>seal</em>) esse segredo.</p>
<p>O TPM devolve um arquivo criptografado (<code>.cred</code>). Se um atacante roubar o seu SSD ou copiar esse arquivo <code>.cred</code> para outro computador, ele será inútil: apenas o chip TPM físico da sua placa-mãe consegue descriptografar aquele envelope.</p>
<p>No Linux com <code>systemd</code>, nós nem precisamos escrever código C cabeludo falando com <code>/dev/tpmrm0</code> diretamente. O utilitário <code>systemd-creds</code> já faz o <em>seal</em> (<code>encrypt</code> com <code>--with-key=host+tpm2</code>) e o <em>unseal</em> (<code>decrypt</code> com <code>--refuse-null</code>, lendo o tipo de chave direto do cabeçalho do <code>.cred</code>):</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">build_systemd_creds_encrypt_command</span><span class="p">(</span><span class="n">cred_name</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="p">(</span><span class="n">PathBuf</span><span class="p">,</span><span class="w"> </span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">OsString</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">PathBuf</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;/usr/bin/systemd-creds&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">vec!</span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--with-key=host+tpm2&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--name&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="n">cred_name</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--newline=no&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;encrypt&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;-&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;-&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">build_systemd_creds_decrypt_command</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">cred_name</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">credential_path</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="p">(</span><span class="n">PathBuf</span><span class="p">,</span><span class="w"> </span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">OsString</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">PathBuf</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;/usr/bin/systemd-creds&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="fm">vec!</span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--name&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="n">cred_name</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--newline=no&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--refuse-null&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;decrypt&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">credential_path</span><span class="p">.</span><span class="n">as_os_str</span><span class="p">().</span><span class="n">to_os_string</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;-&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">)</span><span class="w">
</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>
<h3>Como a minha senha de login destrava tudo sem tocar na UI<span class="hx:absolute hx:-mt-20" id="como-a-minha-senha-de-login-destrava-tudo-sem-tocar-na-ui"></span>
    <a href="#como-a-minha-senha-de-login-destrava-tudo-sem-tocar-na-ui" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O arquivo <code>.cred</code> selado pelo TPM fica salvo em <code>/var/lib/.../vault.key.cred</code>, pertencente ao usuário <code>root:root</code> com permissão <code>0600</code>. Nenhum processo rodando com o meu usuário comum consegue sequer ler esse arquivo.</p>
<p>Quando eu abro o aplicativo no desktop e clico em <em>&ldquo;Unlock Vault&rdquo;</em>, acontece uma coreografia bem específica:</p>
<ol>
<li>A interface gráfica dispara <code>/usr/bin/pkexec /usr/lib/.../key-launcher</code>.</li>
<li>O <strong>Polkit</strong> intercepta a chamada e abre o prompt nativo do sistema operacional pedindo a minha senha de login do Linux (configurado com <code>&lt;allow_active&gt;auth_self&lt;/allow_active&gt;</code>, sem cache de sessão, exigindo autenticação a cada abertura).</li>
<li>A minha senha de login <strong>nunca passa pelo meu aplicativo</strong>. Quem valida a senha é o agente do Polkit/PAM do próprio sistema operacional.</li>
<li>Assim que o Polkit confirma minha senha, ele inicia o binário <code>key-launcher</code> como <code>root</code>.</li>
<li>O <code>key-launcher</code> confere que foi invocado pelo meu UID esperado (<code>PKEXEC_UID</code>), valida que os diretórios têm as permissões corretas sem <em>symlinks</em> maliciosos (usando <code>O_NOFOLLOW</code>), e chama o <code>systemd-creds</code> para pedir ao TPM que abra o arquivo <code>.cred</code>.</li>
<li>O <code>key-launcher</code> recebe os 32 bytes da chave em memória (protegida com <code>ZeroizeOnDrop</code> para ser zerada assim que sair de escopo), cria um <em>pipe</em> anônimo na memória do kernel, escreve os 32 bytes no <strong>File Descriptor 3 (<code>FD 3</code>)</strong> e fecha a ponta de escrita.</li>
<li>Antes de iniciar o backend do aplicativo, o <code>key-launcher</code> <strong>joga fora todos os privilégios de root de forma irreversível</strong> e substitui o próprio processo via <code>execveat</code>.</li>
</ol>
<p>Olha como fica a sequência de queda de privilégios (<em>privilege drop</em>) e blindagem de memória em Rust dentro do <code>key-launcher</code>:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">KEY_FD</span>: <span class="nc">RawFd</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">drop_privileges_permanently</span><span class="p">(</span><span class="n">target_uid</span>: <span class="kt">u32</span><span class="p">,</span><span class="w"> </span><span class="n">target_gid</span>: <span class="kt">u32</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="p">()</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">libc</span>::<span class="n">umask</span><span class="p">(</span><span class="mo">0o077</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">std</span>::<span class="n">env</span>::<span class="n">set_current_dir</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">)</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">drop_supplementary_groups</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">clear_capability_bounding_set</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">clear_ambient_capabilities</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">lock_no_root_securebits</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Troca GID e UID reais, efetivos e salvos de forma definitiva
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">rc_gid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">libc</span>::<span class="n">setresgid</span><span class="p">(</span><span class="n">target_gid</span><span class="p">,</span><span class="w"> </span><span class="n">target_gid</span><span class="p">,</span><span class="w"> </span><span class="n">target_gid</span><span class="p">)</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="n">rc_gid</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">LauncherError</span>::<span class="n">Syscall</span><span class="p">(</span><span class="s">&#34;setresgid&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">io</span>::<span class="n">Error</span>::<span class="n">last_os_error</span><span class="p">()));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">rc_uid</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">libc</span>::<span class="n">setresuid</span><span class="p">(</span><span class="n">target_uid</span><span class="p">,</span><span class="w"> </span><span class="n">target_uid</span><span class="p">,</span><span class="w"> </span><span class="n">target_uid</span><span class="p">)</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="n">rc_uid</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">LauncherError</span>::<span class="n">Syscall</span><span class="p">(</span><span class="s">&#34;setresuid&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">io</span>::<span class="n">Error</span>::<span class="n">last_os_error</span><span class="p">()));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Verifica que uma tentativa de recuperar root (setuid(0)) falha obrigatoriamente
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">libc</span>::<span class="n">setuid</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">LauncherError</span>::<span class="n">PrivilegeVerification</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;setuid(0) succeeded after privilege drop&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Impede ganho de privilégios em execve futuros e bloqueia ptrace/core dumps
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">libc</span>::<span class="n">prctl</span><span class="p">(</span><span class="n">libc</span>::<span class="no">PR_SET_NO_NEW_PRIVS</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">libc</span>::<span class="n">prctl</span><span class="p">(</span><span class="n">libc</span>::<span class="no">PR_SET_DUMPABLE</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">set_zero_core_limit</span><span class="p">()</span><span class="o">?</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(())</span><span class="w">
</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>Repare nos detalhes no final dessa função:</p>
<ul>
<li><code>PR_SET_DUMPABLE = 0</code> e <code>RLIMIT_CORE = 0</code> proíbem o kernel de gerar arquivos de <em>core dump</em> no disco (que conteriam a chave do banco em memória) e impedem que outros processos do mesmo usuário anexem um <code>gdb</code> ou <code>ptrace</code> para inspecionar a RAM. Como o kernel Linux reseta o atributo <code>dumpable</code> para <code>1</code> após um <code>execve</code> em binários normais, tanto o <code>session-backend</code> quanto os binários auxiliares do navegador e do parser reaplicam <code>PR_SET_DUMPABLE = 0</code> logo na primeira linha do <code>main</code> (enquanto <code>RLIMIT_CORE = 0</code> e <code>PR_SET_NO_NEW_PRIVS</code> já vêm herdados do launcher).</li>
<li>Passar segredos por <code>--key=...</code> ou variáveis de ambiente expõe o valor em <code>/proc/&lt;pid&gt;/cmdline</code> e <code>/proc/&lt;pid&gt;/environ</code>. Passando via um <em>pipe</em> anônimo no <code>FD 3</code> que é lido uma única vez e fechado imediatamente no boot do <code>session-backend</code>, o segredo jamais aparece no <code>/proc</code>.</li>
<li>A interface gráfica em QML conversa com o <code>session-backend</code> apenas por mensagens JSON delimitadas por linha (NDJSON com limite de 64 KiB). A UI nunca recebe a chave criptográfica e nem toca no arquivo SQLite diretamente.</li>
</ul>
<h3>Blindando a chave na memória RAM (<code>mlock</code>, <code>MADV_DONTDUMP</code>) e auto-lock por inatividade<span class="hx:absolute hx:-mt-20" id="blindando-a-chave-na-memória-ram-mlock-madv_dontdump-e-auto-lock-por-inatividade"></span>
    <a href="#blindando-a-chave-na-mem%c3%b3ria-ram-mlock-madv_dontdump-e-auto-lock-por-inatividade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Mesmo depois que o <code>session-backend</code> lê os 32 bytes do <code>FD 3</code> e fecha o descritor, a chave precisa viver na memória RAM enquanto o cofre estiver aberto. Isso levanta dois riscos clássicos que muita gente esquece: e se o kernel do Linux resolver paginar aquela região de memória para uma partição de <em>swap</em> não criptografada no SSD (ou imagem de hibernação)? E se eu destravar o aplicativo de manhã, minimizar a janela e esquecer o cofre aberto pelo resto do dia?</p>
<p>Para resolver o primeiro problema, o tipo <code>DatabaseKey</code> guarda os 32 bytes dentro de um <code>Box&lt;[u8; 32]&gt;</code> na heap e trava a página física na RAM com <code>mlock</code>:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">struct</span> <span class="nc">DatabaseKey</span><span class="p">(</span><span class="nb">Box</span><span class="o">&lt;</span><span class="p">[</span><span class="kt">u8</span><span class="p">;</span><span class="w"> </span><span class="no">KEY_BYTES</span><span class="p">]</span><span class="o">&gt;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="n">DatabaseKey</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">from_bytes</span><span class="p">(</span><span class="k">mut</span><span class="w"> </span><span class="n">bytes</span>: <span class="p">[</span><span class="kt">u8</span><span class="p">;</span><span class="w"> </span><span class="no">KEY_BYTES</span><span class="p">])</span><span class="w"> </span>-&gt; <span class="nc">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">boxed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Box</span>::<span class="n">new</span><span class="p">(</span><span class="n">bytes</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">bytes</span><span class="p">.</span><span class="n">zeroize</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">lock_key_page</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="n">boxed</span><span class="p">[</span><span class="o">..</span><span class="p">]);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">Self</span><span class="p">(</span><span class="n">boxed</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">impl</span><span class="w"> </span><span class="nb">Drop</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">DatabaseKey</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">fn</span> <span class="nf">drop</span><span class="p">(</span><span class="o">&amp;</span><span class="k">mut</span><span class="w"> </span><span class="bp">self</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">ptr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="bp">self</span><span class="p">.</span><span class="mi">0</span><span class="p">[</span><span class="o">..</span><span class="p">].</span><span class="n">as_mut_ptr</span><span class="p">().</span><span class="n">cast</span>::<span class="o">&lt;</span><span class="n">core</span>::<span class="n">ffi</span>::<span class="n">c_void</span><span class="o">&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="bp">self</span><span class="p">.</span><span class="mf">0.</span><span class="n">zeroize</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">rustix</span>::<span class="n">mm</span>::<span class="n">munlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span><span class="w"> </span><span class="no">KEY_BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">lock_key_page</span><span class="p">(</span><span class="n">buffer</span>: <span class="kp">&amp;</span><span class="nc">mut</span><span class="w"> </span><span class="p">[</span><span class="kt">u8</span><span class="p">])</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">ptr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">buffer</span><span class="p">.</span><span class="n">as_mut_ptr</span><span class="p">().</span><span class="n">cast</span>::<span class="o">&lt;</span><span class="n">core</span>::<span class="n">ffi</span>::<span class="n">c_void</span><span class="o">&gt;</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">buffer</span><span class="p">.</span><span class="n">len</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">unsafe</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rustix</span>::<span class="n">mm</span>::<span class="n">mlock</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span><span class="w"> </span><span class="n">len</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="cp">#[cfg(target_os = </span><span class="s">&#34;linux&#34;</span><span class="cp">)]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">let</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">rustix</span>::<span class="n">mm</span>::<span class="n">madvise</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span><span class="w"> </span><span class="n">len</span><span class="p">,</span><span class="w"> </span><span class="n">rustix</span>::<span class="n">mm</span>::<span class="n">Advice</span>::<span class="n">LinuxDontDump</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</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 detalhe sutil de Rust aqui: por que usar <code>Box&lt;[u8; 32]&gt;</code> em vez de um array inline <code>[u8; 32]</code> dentro da struct? Porque a syscall <code>mlock(2)</code> trava um intervalo de endereços virtuais específicos, e qualquer <em>move</em> de um array na stack mudaria o endereço dos bytes, deixando o endereço antigo travado à toa e o novo desprotegido.</p>
<p>Com <code>Box</code>, o endereço na heap permanece estável do nascimento até o <code>Drop</code>, onde limpamos os bytes com <code>zeroize()</code> antes de chamar <code>munlock</code>. De quebra, <code>MADV_DONTDUMP</code> marca aquela página específica para jamais entrar em um dump de memória.</p>
<p>Já para não deixar o cofre aberto indefinidamente quando esqueço a janela aberta em outro workspace, o loop principal do <code>session-backend</code> roda com um timer de inatividade (<code>IDLE_LOCK_TIMEOUT</code> de 15 minutos). Se passarem 15 minutos sem nenhuma requisição da UI e sem nenhuma coleta bancária em andamento, o backend fecha a conexão do SQLCipher, destrói a <code>DatabaseKey</code> (disparando o <code>zeroize</code> + <code>munlock</code>), avisa a UI que a sessão expirou (<code>idle_locked</code>) e encerra o próprio processo. Para voltar a usar o app, sou obrigado a passar novamente pelo prompt do Polkit e pelo TPM.</p>
<h2>Passo 3: colocando o navegador e o parser de PDFs em uma prisão (sandbox)<span class="hx:absolute hx:-mt-20" id="passo-3-colocando-o-navegador-e-o-parser-de-pdfs-em-uma-prisão-sandbox"></span>
    <a href="#passo-3-colocando-o-navegador-e-o-parser-de-pdfs-em-uma-pris%c3%a3o-sandbox" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com o banco de dados trancado a sete chaves, sobrava a parte mais perigosa de qualquer aplicação moderna: <strong>o navegador web</strong>.</p>
<p>Um navegador é praticamente um sistema operacional inteiro executando JavaScript arbitrário vindo da internet. Na minha primeira versão, o navegador rodava solto na minha sessão com acesso à minha pasta <code>$HOME</code>, ao meu servidor gráfico principal e à internet inteira.</p>
<p>Para resolver isso, desenhei um pipeline em <strong>dois estágios isolados</strong>: um sandbox para o navegador durante o login manual, e um segundo sandbox <em>sem rede</em> apenas para fazer o parsing dos arquivos baixados.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/06/seguranca-gui-linux/diagram-2-browser-sandbox-handoff.jpg" alt="Diagrama do pipeline de sandbox em dois estágios para o navegador e parser de arquivos"  loading="lazy" /></p>
<h3>Prós e contras de isolar o navegador em uma sandbox<span class="hx:absolute hx:-mt-20" id="prós-e-contras-de-isolar-o-navegador-em-uma-sandbox"></span>
    <a href="#pr%c3%b3s-e-contras-de-isolar-o-navegador-em-uma-sandbox" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Antes de ver o código, vale colocar na balança o que você ganha e o que você paga ao isolar o navegador dessa forma.</p>
<p>Do lado dos benefícios:</p>
<ul>
<li>O navegador enxerga apenas um <code>/home/banker</code> sintético montado em RAM (<code>tmpfs</code>), sem qualquer acesso aos seus arquivos pessoais, chaves SSH (<code>~/.ssh</code>), GPG, carteira de cripto ou ao cofre SQLCipher.</li>
<li>Assim que a coleta termina e o processo morre, cookies, tokens de sessão, cache e <em>localStorage</em> desaparecem da memória RAM.</li>
<li>Em vez de abrir a janela direto no compositor principal da sessão, rodamos dentro de um compositor Wayland dedicado (<code>cage</code>), evitando que outras janelas façam <em>keylogging</em> ou captura de tela silenciosa.</li>
<li>Mesmo que um script malicioso tente enviar dados para um domínio externo, o firewall interno da sandbox bloqueia qualquer IP fora da lista do banco.</li>
</ul>
<p>Do lado do custo operacional:</p>
<ul>
<li>Como o perfil começa do zero a cada execução e nenhuma credencial fica salva, eu preciso digitar meu login e aprovar o 2FA no celular toda vez que quero sincronizar os dados.</li>
<li>Como trancamos os IPs de saída no início da sessão, se o banco usar um CDN que muda de IP no meio do login, a conexão cai e precisa ser reiniciada.</li>
<li>O empacotamento exige ter <code>bubblewrap</code> (<code>bwrap</code>), <code>slirp4netns</code>, <code>nftables</code> e <code>cage</code> instalados e configurados no host.</li>
</ul>
<h3>Como funciona o bloqueio de rede dentro do Bubblewrap<span class="hx:absolute hx:-mt-20" id="como-funciona-o-bloqueio-de-rede-dentro-do-bubblewrap"></span>
    <a href="#como-funciona-o-bloqueio-de-rede-dentro-do-bubblewrap" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Muita gente usa o <code>bubblewrap</code> (<code>bwrap</code>) com <code>slirp4netns --disable-host-loopback</code> achando que isso basta para isolar a rede. Essa flag realmente impede que o navegador acesse serviços rodando no <code>127.0.0.1</code> da máquina host, mas continua permitindo que o navegador conecte em <strong>qualquer IP público da internet</strong>.</p>
<p>Para impedir que o navegador converse com qualquer servidor fora do banco que estou acessando, o sandbox cria dois usuários diferentes dentro do namespace de rede: o navegador roda como <code>UID 1000</code> (<code>BROWSER_UID</code>) e um pequeno proxy Rust interno (<em>Pinned Egress Broker</em>) roda como <code>UID 1001</code> (<code>BROKER_UID</code>).</p>
<p>Antes do navegador abrir, um conjunto de regras <code>nftables</code> com política padrão <code>drop</code> é aplicado dentro do namespace:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">generate_nftables_ruleset</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pinned</span>: <span class="kp">&amp;</span><span class="nc">PinnedEgress</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">broker_port</span>: <span class="kt">u16</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">browser_uid</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">broker_uid</span>: <span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">allowed_ips</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">Vec</span>::<span class="n">new</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="n">addrs</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">pinned</span><span class="p">.</span><span class="n">host_addrs</span><span class="p">.</span><span class="n">values</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="n">addr</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">addrs</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">is_globally_routable_bank_ip</span><span class="p">(</span><span class="o">*</span><span class="n">addr</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="nb">Err</span><span class="p">(</span><span class="n">SessionError</span>::<span class="n">PinnedIpNotPublic</span><span class="p">(</span><span class="o">*</span><span class="n">addr</span><span class="p">));</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="o">!</span><span class="n">allowed_ips</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">addr</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">allowed_ips</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="o">*</span><span class="n">addr</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">ip_list</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">allowed_ips</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="n">Ipv4Addr</span>::<span class="n">to_string</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">collect</span>::<span class="o">&lt;</span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">_</span><span class="o">&gt;&gt;</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="s">&#34;, &#34;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nb">Ok</span><span class="p">(</span><span class="fm">format!</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;table inet bank_sandbox </span><span class="se">{{\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t</span><span class="s">chain output </span><span class="se">{{\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t\t</span><span class="s">type filter hook output priority 0; policy drop;</span><span class="se">\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t\t</span><span class="s">ct state established,related accept</span><span class="se">\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t\t</span><span class="s">oifname </span><span class="se">\&#34;</span><span class="s">lo</span><span class="se">\&#34;</span><span class="s"> meta skuid </span><span class="si">{browser_uid}</span><span class="s"> ip daddr 127.0.0.1 tcp dport </span><span class="si">{broker_port}</span><span class="s"> accept</span><span class="se">\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t\t</span><span class="s">oifname </span><span class="se">\&#34;</span><span class="s">lo</span><span class="se">\&#34;</span><span class="s"> meta skuid </span><span class="si">{broker_uid}</span><span class="s"> ip daddr 127.0.0.1 accept</span><span class="se">\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t\t</span><span class="s">meta skuid </span><span class="si">{broker_uid}</span><span class="s"> ip daddr </span><span class="se">{{</span><span class="s"> </span><span class="si">{ip_list}</span><span class="s"> </span><span class="se">}}</span><span class="s"> tcp dport 443 accept</span><span class="se">\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t\t</span><span class="s">drop</span><span class="se">\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">\t}}\n\</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">         </span><span class="se">}}\n</span><span class="s">&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">))</span><span class="w">
</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>Olha que elegante o efeito dessas cinco linhas de <code>nftables</code>:</p>
<ol>
<li>O navegador (<code>skuid 1000</code>) é <strong>proibido</strong> de sair para a internet diretamente (<code>policy drop</code>). O único pacote que o kernel deixa o navegador enviar é para <code>127.0.0.1:18080</code>, onde mora o nosso <em>broker</em>.</li>
<li>O <em>broker</em> (<code>skuid 1001</code>) recebe o pedido <code>HTTP CONNECT host:443</code>, confere se o <code>host</code> está na lista estrita daquele banco (ex.: <code>ebanking.bank-alpha.example</code>), e conecta diretamente nos endereços IPv4 públicos que foram pré-resolvidos antes do navegador nascer.</li>
<li>IPs privados (RFC 1918), loopback, link-local e endpoints de metadados (<code>169.254.169.254</code>) são rejeitados na hora, anulando ataques de <em>DNS rebinding</em>.</li>
<li>Como o Chromium roda dentro do <code>bwrap</code> com um namespace de usuário mapeado adequadamente, <strong>nós mantemos o sandbox interno do próprio Chromium ligado</strong> (sem <code>--no-sandbox</code>), e o CDP escuta em uma porta efêmera (<code>--remote-debugging-port=0</code>) acessível apenas dentro do namespace isolado.</li>
</ol>
<h3>Ponte de área de transferência unidirecional e de uso único (<code>--paste-once --sensitive</code>)<span class="hx:absolute hx:-mt-20" id="ponte-de-área-de-transferência-unidirecional-e-de-uso-único---paste-once---sensitive"></span>
    <a href="#ponte-de-%c3%a1rea-de-transfer%c3%aancia-unidirecional-e-de-uso-%c3%banico---paste-once---sensitive" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Isolar o navegador em um compositor Wayland próprio (<code>cage</code>) com um diretório <code>XDG_RUNTIME_DIR</code> separado cria um problema prático imediato: como eu colo uma senha aleatória de 30 caracteres vinda do meu gerenciador de senhas do desktop para dentro do navegador isolado?</p>
<p>Se você compartilhar o socket Wayland principal só para fazer o Ctrl+V funcionar, você joga fora todo o isolamento gráfico e permite que uma página maliciosa leia ou sobrescreva a sua área de transferência do host.</p>
<p>A solução foi construir uma ponte de clipboard <strong>estritamente unidirecional</strong> (do host para o <code>cage</code>) usando <code>wl-paste</code> e <code>wl-copy</code>:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">fn</span> <span class="nf">nested_clipboard_offer_command</span><span class="p">(</span><span class="n">wl_copy</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w"> </span><span class="n">runtime</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w"> </span><span class="n">nested_display</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">)</span><span class="w"> </span>-&gt; <span class="nc">Command</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">command</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Command</span>::<span class="n">new</span><span class="p">(</span><span class="n">wl_copy</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">command</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">args</span><span class="p">([</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;--foreground&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;--paste-once&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;--trim-newline&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;--type&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;text/plain;charset=utf-8&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;--sensitive&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">])</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">env_clear</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">env</span><span class="p">(</span><span class="s">&#34;PATH&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;/usr/bin&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">env</span><span class="p">(</span><span class="s">&#34;XDG_RUNTIME_DIR&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">runtime</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">env</span><span class="p">(</span><span class="s">&#34;WAYLAND_DISPLAY&#34;</span><span class="p">,</span><span class="w"> </span><span class="n">nested_display</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">stdout</span><span class="p">(</span><span class="n">Stdio</span>::<span class="n">null</span><span class="p">())</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="n">stderr</span><span class="p">(</span><span class="n">Stdio</span>::<span class="n">inherit</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">command</span><span class="w">
</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>Funciona assim: o processo supervisor fora do <code>bwrap</code> observa apenas texto puro copiado no host e o injeta no socket Wayland aninhado (<code>wayland-bank</code>) com as flags <code>--paste-once</code> e <code>--sensitive</code>.</p>
<p>O navegador dentro do sandbox consegue receber um único Ctrl+V da senha que acabei de copiar no gerenciador de senhas; imediatamente após essa colagem, o <code>wl-copy</code> destrói a oferta dentro do sandbox e encerra. E como não existe ponte no sentido inverso, o navegador jamais consegue escrever nada de volta na área de transferência do meu desktop.</p>
<h3>O segundo estágio: fazendo parsing de PDFs e Excel sem rede<span class="hx:absolute hx:-mt-20" id="o-segundo-estágio-fazendo-parsing-de-pdfs-e-excel-sem-rede"></span>
    <a href="#o-segundo-est%c3%a1gio-fazendo-parsing-de-pdfs-e-excel-sem-rede" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Depois que eu faço o login no banco, clico em <em>&ldquo;Take over&rdquo;</em> e o worker baixa os extratos ou notas de corretagem para a pasta <code>/handoff</code>, o supervisor mata imediatamente todo o grupo de processos do navegador e da rede.</p>
<p>Só que agora temos outro risco: <strong>fazer parsing de arquivos PDF e planilhas XLSX vindos de fora</strong>. Bibliotecas que leem PDF e formatos legados de Excel são historicamente cheias de vulnerabilidades de memória e <em>denial of service</em> (inclusive atualizamos a crate <code>calamine</code> durante o projeto por conta do advisory <code>RUSTSEC-2026-0436</code>).</p>
<p>Em vez de deixar o processo principal que tem a chave do banco abrir esses PDFs, o sistema congela o diretório de staging com permissão <code>0500</code> (somente leitura) e dispara um <strong>segundo sandbox <code>bwrap</code></strong>, dessa vez com <code>--unshare-net</code> (zero interface de rede) e limites rígidos de recursos via <code>prlimit</code>:</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-rust" data-lang="rust"><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">PARSER_AS_LIMIT_BYTES</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="mi">768</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">1024</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">1024</span><span class="p">;</span><span class="w"> </span><span class="c1">// 768 MiB RAM max
</span></span></span><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">PARSER_CPU_LIMIT_SECONDS</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w">             </span><span class="c1">// 10s CPU max
</span></span></span><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">PARSER_NOFILE_LIMIT</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="mi">64</span><span class="p">;</span><span class="w">                  </span><span class="c1">// max 64 file descriptors
</span></span></span><span class="line"><span class="cl"><span class="k">const</span><span class="w"> </span><span class="no">PARSER_NPROC_LIMIT</span>: <span class="kt">u64</span> <span class="o">=</span><span class="w"> </span><span class="mi">16</span><span class="p">;</span><span class="w">                   </span><span class="c1">// max 16 threads/processes
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">pub</span><span class="w"> </span><span class="k">fn</span> <span class="nf">build_sandboxed_parser_command</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">bwrap_program</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">prlimit_program</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">parser_binary</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">staging_dir</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">output_file</span>: <span class="kp">&amp;</span><span class="nc">Path</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">bank</span>: <span class="kp">&amp;</span><span class="kt">str</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"> </span>-&gt; <span class="p">(</span><span class="n">PathBuf</span><span class="p">,</span><span class="w"> </span><span class="nb">Vec</span><span class="o">&lt;</span><span class="n">OsString</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">let</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--unshare-all&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--unshare-net&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--die-with-parent&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--new-session&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">OsString</span>::<span class="n">from</span><span class="p">(</span><span class="s">&#34;--clearenv&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Monta /usr, /lib e o diretório de staging em modo somente-leitura (--ro-bind)
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Executa sob prlimit --as=768M --cpu=10 --nofile=64 --nproc=16 --core=0
</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>Se um PDF malformado tentar explodir a memória (um <em>zip bomb</em> dentro de um XLSX) ou explorar um bug no parser, o processo bate no teto de cerca de 770 MB de RAM ou 10 segundos de CPU, fica sem rede para exfiltrar nada, sem acesso ao banco de dados e acaba morto pelo kernel. O processo principal só aceita o resultado se o parser devolver um JSON estritamente tipado e validado dentro do limite de tamanho.</p>
<h2>Passo 4: backups autenticados, chave de recuperação e NAS<span class="hx:absolute hx:-mt-20" id="passo-4-backups-autenticados-chave-de-recuperação-e-nas"></span>
    <a href="#passo-4-backups-autenticados-chave-de-recupera%c3%a7%c3%a3o-e-nas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando você trava seu banco de dados no chip TPM da placa-mãe, surge uma pergunta inevitável: <strong>e se a minha placa-mãe queimar amanhã?</strong></p>
<p>Se o único jeito de abrir o banco fosse o chip TPM daquela placa-mãe específica, um defeito de hardware significaria perder todos os seus dados financeiros para sempre. Segurança sem plano de recuperação de desastres é só uma forma sofisticada de perder dados.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/06/seguranca-gui-linux/diagram-3-migration-and-backup.jpg" alt="Diagrama de migração verificada para SQLCipher e estratégia de backups em camadas"  loading="lazy" /></p>
<p>Por isso, a estratégia de backup e recuperação foi dividida em três camadas.</p>
<h3>1. Código de recuperação offline com checksum<span class="hx:absolute hx:-mt-20" id="1-código-de-recuperação-offline-com-checksum"></span>
    <a href="#1-c%c3%b3digo-de-recupera%c3%a7%c3%a3o-offline-com-checksum" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No momento em que o cofre é criado (<em>enrollment</em>), o <code>key-launcher</code> gera a chave de 256 bits e, antes de selá-la no TPM, exibe uma única vez no terminal (<code>/dev/tty</code>) um código de recuperação codificado em Base32 no formato <code>MBRK1-XXXX-XXXX-...</code>, protegido por um checksum SHA-256 de 5 bytes contra erros de digitação.</p>
<p>O programa exige que você redigite esse código às cegas no terminal para provar que você realmente o anotou (eu guardo o meu em uma nota segura no meu gerenciador de senhas, fora da máquina) antes de permitir que o cofre seja ativado. Se meu PC pegar fogo, basta rodar o utilitário de restauração do pacote em uma máquina nova, informar o código <code>MBRK1-...</code> e o sistema restaura o cofre e re-sela a chave no TPM do computador novo.</p>
<h3>2. Pacotes <code>.mbbackup</code> autenticados com teste de restauração imediato<span class="hx:absolute hx:-mt-20" id="2-pacotes-mbbackup-autenticados-com-teste-de-restauração-imediato"></span>
    <a href="#2-pacotes-mbbackup-autenticados-com-teste-de-restaura%c3%a7%c3%a3o-imediato" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Copiar um arquivo SQLite com <code>cp</code> enquanto ele está aberto pode gerar um backup corrompido. Por isso o app possui um formato próprio de backup (<code>.mbbackup</code>) que empacota:</p>
<ul>
<li>Um cabeçalho mágico (<code>MBBACKUP</code>) + versão do formato;</li>
<li>Um manifesto em JSON canônico (seguindo a RFC 8785) contendo a versão do schema, parâmetros do SQLCipher, quantidade de documentos embutidos e o <em>logical fingerprint</em> SHA-256 de todos os dados;</li>
<li>O payload binário do banco já criptografado pelo SQLCipher;</li>
<li>Uma assinatura <code>HMAC-SHA256</code> de 32 bytes calculada sobre o cabeçalho, o manifesto e o payload, usando uma subchave derivada via <code>HKDF-SHA256</code>.</li>
</ul>
<p>O detalhe mais importante: toda vez que um backup é criado, a própria função de backup <strong>abre imediatamente o arquivo <code>.mbbackup</code> recém-gerado, restaura em um diretório temporário, valida o HMAC e compara o fingerprint lógico</strong> antes de retornar sucesso. Backup que nunca foi testado na restauração não existe.</p>
<h3>3. Backup externo diário no NAS com dupla criptografia<span class="hx:absolute hx:-mt-20" id="3-backup-externo-diário-no-nas-com-dupla-criptografia"></span>
    <a href="#3-backup-externo-di%c3%a1rio-no-nas-com-dupla-criptografia" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Por fim, manter o backup apenas no mesmo SSD onde mora o banco original deixa você exposto a falha de disco ou <em>ransomware</em>.</p>
<p>Aqui em casa eu mantenho meus servidores NAS em RAID-6 (incluindo o QNAP com ZFS RAID-Z2 para onde evacuei meus dados recentemente depois que <a href="/2026/10/06/desastre-no-meu-nas-como-quase-perdi-90-tb-de-dados/">quase perdi 90 TB de dados quando dois discos do meu antigo Synology falharam juntos durante um rebuild</a>, e o próprio Synology reconstruído em RAID-6). Toda madrugada, às 3h da manhã, um timer do <code>systemd</code> na minha estação de trabalho roda o <strong>Restic</strong> fazendo snapshot incremental da minha pasta de projetos e dados <a href="/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux/">via NFS para o NAS</a>, mantendo uma rotação enxuta de snapshots diários, semanais e mensais (3 diários, 3 semanais e 2 mensais).</p>
<p>Repare na defesa em profundidade aqui: mesmo que alguém invada a rede local, comprometa o servidor NFS ou roube fisicamente os HDs de dentro do NAS, o atacante encontrará um repositório <strong>Restic criptografado</strong>; e mesmo que por algum milagre ele tivesse a senha do Restic, o arquivo dentro do backup já é um cofre <strong>SQLCipher criptografado</strong> cuja chave mora dentro do TPM da minha estação de trabalho.</p>
<h2>Conclusão: segurança como gestão de risco contínua<span class="hx:absolute hx:-mt-20" id="conclusão-segurança-como-gestão-de-risco-contínua"></span>
    <a href="#conclus%c3%a3o-seguran%c3%a7a-como-gest%c3%a3o-de-risco-cont%c3%adnua" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quão seguro tudo isso deixa o meu aplicativo? Ele é <strong>seguro o suficiente para o meu modelo de ameaça</strong>, sabendo que segurança absoluta simplesmente inexiste no mundo real.</p>
<p>No próprio documento de arquitetura do projeto (ADR 0001), eu deixei registrado explicitamente os riscos residuais que escolhi aceitar:</p>
<ul>
<li>Eu mantenho o <em>Secure Boot</em> desabilitado na minha estação por conta do meu fluxo de boot e drivers no Linux; logo, o selo do TPM protege contra roubo de disco e processos desprivilegiados, enquanto ataques físicos ao bootloader antes do sistema subir ficam fora desse escopo.</li>
<li>O compositor Wayland aninhado (<code>cage</code>) isola o navegador em <code>wayland-bank</code>, mas o próprio <code>cage</code> ainda é um cliente do compositor principal da sessão (Hyprland/wlroots). Um processo malicioso rodando com o meu UID no host ainda poderia usar protocolos de captura de tela do compositor (<code>zwlr_screencopy_v1</code> / <code>ext_image_copy_capture_v1</code>, como o <code>grim</code>) enquanto a janela do banco está visível, ou ler a senha no clipboard do host antes de ela ser limpa após o Ctrl+V.</li>
<li>O <code>key-launcher</code> garante em tempo de execução (via <code>O_NOFOLLOW</code>) que os binários instalados em <code>/usr/lib/...</code> pertencem a <code>root:root</code>, impedindo que um processo do meu usuário troque o executável instalado. Porém, como eu desenvolvo o projeto na mesma máquina com o meu UID normal, um malware no meu usuário poderia tentar alterar o código-fonte ou scripts de build (<code>cargo</code>/<code>makepkg</code>) esperando a próxima vez que eu compilar e instalar um pacote atualizado com <code>pacman -U</code>, o que exige revisar <code>git status</code> e o diff antes de empacotar.</li>
<li>Apagar o banco antigo em texto puro em um SSD moderno com sistema de arquivos <em>Copy-on-Write</em> (como Btrfs) desvincula o arquivo do sistema operacional, mas o <em>wear leveling</em> da controladora flash física pode reter blocos antigos até serem sobrescritos.</li>
<li>Enquanto o cofre está aberto e desbloqueado na minha sessão, um malware que já tenha conseguido execução de código como <code>root</code> no meu kernel ativo pode fazer o que quiser.</li>
</ul>
<p>É aqui que a maioria das pessoas se engana. Muita gente trata segurança como se fosse um interruptor binário: ou o sistema é &ldquo;seguro&rdquo; ou é &ldquo;inseguro&rdquo;.</p>
<p>Na vida real, <strong>segurança é um jogo contínuo de avaliação de risco</strong>: <em>&ldquo;quanto você está disposto a proteger, e a que custo de preço, tempo e usabilidade?&rdquo;</em>.</p>
<blockquote>
  <p>O único computador &ldquo;perfeitamente seguro&rdquo; é aquele desligado da tomada, sem placa de rede, trancado dentro de um bloco de concreto no fundo do oceano (e ele também não serve para nada).</p>

</blockquote>
<p>Se o seu aplicativo precisa se conectar ao site do seu banco pela internet para buscar um extrato, ele já deixou de ser perfeitamente isolado por definição. A partir daí, o nosso trabalho como engenheiros é empilhar <strong>mitigações em camadas</strong> (criptografia em repouso, chave selada em hardware, queda de privilégios, isolamento de namespaces, firewall por UID, parser sem rede, backups verificados) e entender exatamente quanto risco residual ainda sobra em cada camada.</p>
<p>A inteligência artificial é fantástica para escanear vulnerabilidades conhecidas e até para implementar essas blindagens quando você sabe exatamente o que pedir para ela. Mas a IA jamais vai adivinhar o seu modelo de ameaça sozinha, e muito menos vai salvar você da própria ignorância.</p>
<p>Se você apenas pedir para o agente criar um app no <em>vibecoding</em> e aceitar tudo de olhos fechados, confiando 100% na IA sem entender como funcionam permissões do Linux, processos, memória, rede e criptografia, o seu aplicativo nascerá inseguro por padrão. Estude os fundamentos, desconfie do caminho mais fácil e coloque segurança desde o seu primeiro <em>hello world</em>.</p>
]]></content:encoded><category>seguranca</category><category>linux</category><category>rust</category><category>inteligencia-artificial</category></item><item><title>Fingerprinting: você não consegue enganar o 'algoritmo' como pensa</title><link>https://akitaonrails.github.io/2026/11/04/fingerprinting-voce-nao-consegue-enganar-o-algoritmo-como-pensa/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/11/04/fingerprinting-voce-nao-consegue-enganar-o-algoritmo-como-pensa/</guid><pubDate>Wed, 04 Nov 2026 19:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.github.io/2026/11/04/tiktok-tecnicamente-nao-se-enquadra-nas-leis-brasileiras-lgpd-felca/"&gt;artigo anterior sobre a descompilação do TikTok 47.7.1&lt;/a&gt;, mostrei o tamanho da coleta que acontece dentro do aplicativo. Ele reenvia identificadores persistentes, monta um fingerprint do aparelho, mede pressão e trajetória dos toques, lê sensores de movimento e registra mais de 6 mil tipos de evento. Também separa usuários por região e dá ao Brasil menos proteções técnicas que aos Estados Unidos e à Europa.&lt;/p&gt;
&lt;p&gt;Muita gente leu aquilo pensando só em privacidade, como se a preocupação fosse o TikTok encontrar a senha do banco. O problema começa bem antes. Essas empresas precisam reconhecer &lt;strong&gt;quem voltou&lt;/strong&gt;, registrar &lt;strong&gt;o que essa pessoa fez&lt;/strong&gt; e prever &lt;strong&gt;o que vai prender sua atenção em seguida&lt;/strong&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="/2026/11/04/tiktok-tecnicamente-nao-se-enquadra-nas-leis-brasileiras-lgpd-felca/">artigo anterior sobre a descompilação do TikTok 47.7.1</a>, mostrei o tamanho da coleta que acontece dentro do aplicativo. Ele reenvia identificadores persistentes, monta um fingerprint do aparelho, mede pressão e trajetória dos toques, lê sensores de movimento e registra mais de 6 mil tipos de evento. Também separa usuários por região e dá ao Brasil menos proteções técnicas que aos Estados Unidos e à Europa.</p>
<p>Muita gente leu aquilo pensando só em privacidade, como se a preocupação fosse o TikTok encontrar a senha do banco. O problema começa bem antes. Essas empresas precisam reconhecer <strong>quem voltou</strong>, registrar <strong>o que essa pessoa fez</strong> e prever <strong>o que vai prender sua atenção em seguida</strong>.</p>
<p>É aí que aparecem alguns folclores. A pessoa curte e favorita uma pilha de vídeos de cachorro pra &ldquo;confundir o algoritmo&rdquo;. Instala uma VPN e acha que virou anônima. Limpa o cache, reinstala o aplicativo, cria outra conta ou troca de celular e acredita que ganhou uma folha em branco. Também tem quem nunca aperte Like e conclua que não entregou preferência nenhuma.</p>
<p>Tudo isso pode mudar algum sinal. Nenhuma dessas ações apaga o conjunto inteiro.</p>
<blockquote>
  <p>Sua intenção fica na sua cabeça. O sistema recebe o seu comportamento.</p>

</blockquote>
<h2>Não existe um &ldquo;algoritmo&rdquo; só<span class="hx:absolute hx:-mt-20" id="não-existe-um-algoritmo-só"></span>
    <a href="#n%c3%a3o-existe-um-algoritmo-s%c3%b3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando alguém fala &ldquo;o algoritmo do TikTok&rdquo;, parece que existe uma função mágica escolhendo o próximo vídeo. Na prática são várias camadas trabalhando juntas:</p>
<ol>
<li><strong>Identidade:</strong> decide se este aparelho, esta instalação e esta conta já apareceram antes.</li>
<li><strong>Telemetria:</strong> registra o que aconteceu, como tempo assistido, pausa, repetição, busca, comentário, compartilhamento, toque e rolagem.</li>
<li><strong>Recomendação:</strong> estima quais vídeos têm maior chance de produzir a próxima interação.</li>
<li><strong>Experimentação:</strong> coloca conteúdo diferente no feed pra medir reações e atualizar o perfil.</li>
</ol>
<p>O fingerprint fica principalmente na primeira camada. Ele ajuda a manter continuidade quando um cookie, um ID de instalação ou uma conta muda. O histórico de comportamento alimenta as demais.</p>
<p>O próprio TikTok explica que usa vídeos assistidos até o fim ou pulados, likes, compartilhamentos, comentários, contas seguidas, sons, hashtags, localização, idioma e tipo de aparelho. Para a maioria dos usuários, <a href="https://support.tiktok.com/en/using-tiktok/exploring-videos/how-tiktok-recommends-content"target="_blank" rel="noopener">tempo assistido e outras interações recebem mais peso</a> que configurações do aparelho.</p>
<p>A descrição pública é simplificada, claro. A nossa análise estática encontrou o texto de cada busca e eventos de <code>play_time</code>, percentual assistido, pausa, repetição, arrastar a barra de progresso, entrada e saída de tela, troca de conta e milhares de outras ações. O serviço que entrega o feed inevitavelmente sabe o que você pediu e por quanto tempo ficou ali.</p>
<h2>Fingerprinting: reconhecer sem depender de um número só<span class="hx:absolute hx:-mt-20" id="fingerprinting-reconhecer-sem-depender-de-um-número-só"></span>
    <a href="#fingerprinting-reconhecer-sem-depender-de-um-n%c3%bamero-s%c3%b3" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um fingerprint combina sinais pequenos demais pra identificar alguém isoladamente. Modelo do celular, resolução, CPU, GPU, lista de sensores, câmeras, fuso horário, idioma, operadora, versão do sistema, tempo desde o último boot e características da instalação formam um retrato muito mais específico quando aparecem juntos.</p>
<p>Alguns sinais são estáveis. Outros variam. O sistema trabalha com graus de confiança e tolera diferenças entre observações.</p>
<p>No mesmo aparelho e perfil Android, o caso é bem direto. O <code>ANDROID_ID</code> usado pelo TikTok é estável por combinação de aparelho, usuário Android e chave de assinatura do aplicativo. Limpar os dados ou reinstalar troca IDs aleatórios guardados pelo app, mas não troca esse identificador. Modelo, sensores, tela, câmera, rede e estado de hardware também continuam parecidos.</p>
<p>Quando você troca de aparelho, a situação muda. O hardware novo produz outro fingerprint. Entrar na mesma conta resolve a associação de forma determinística. Criar outra conta deixa o cruzamento probabilístico: mesma rede doméstica, região, idioma, horários, relações sociais e um padrão de consumo muito semelhante podem sugerir que as duas identidades pertencem à mesma pessoa, sem provar a relação.</p>
<p>Esse mercado chama o conjunto de <strong>cross-device tracking</strong> ou <strong>device graph</strong>. Há correspondências determinísticas, como o mesmo login ou e-mail, e probabilísticas, baseadas na combinação de sinais. <a href="https://doi.org/10.1515/popets-2017-0020"target="_blank" rel="noopener">Pesquisadores já documentavam essa diferença em 2017</a>. A <a href="https://t.tiktok.com/legal/page/us/privacy-policy/en"target="_blank" rel="noopener">política americana atual do próprio TikTok</a> diz que a empresa atribui IDs de aparelho e usuário e pode usar essas informações pra identificar atividade entre dispositivos.</p>
<p>Portanto, &ldquo;troquei de celular&rdquo; pode significar três coisas completamente diferentes:</p>
<ul>
<li>você entrou na mesma conta e informou a ligação;</li>
<li>criou outra conta, mas deixou várias pontes que permitem uma associação provável;</li>
<li>criou uma identidade realmente isolada, sem conta, contatos, parceiros, rede ou hábitos compartilhados, reduzindo muito a confiança da associação.</li>
</ul>
<h2>Um exemplo simplificado<span class="hx:absolute hx:-mt-20" id="um-exemplo-simplificado"></span>
    <a href="#um-exemplo-simplificado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Os dados abaixo são inventados e servem pra visualizar como um servidor compara observações. O formato interno e qualquer pontuação real do TikTok continuam desconhecidos.</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">conta</span><span class="p">:</span><span class="w"> </span><span class="m">84721</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">id_da_instalacao</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;A9F2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">android_id</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;7c81&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">aparelho</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;Pixel 10&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tela</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;1080x2424@120&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">sensores</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;hash-31b7&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">idioma_fuso</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;pt-BR / America-Sao_Paulo&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">rede_frequente</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;200.10.x.x&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">horario_de_uso</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;22h-01h&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">interesses_observados</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">carros, tecnologia, politica]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">gestos</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;rolagem rapida, pressao media 0.42&#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>O código confirma a coleta de pressão, tamanho e trajetória dos toques como sinais de humanidade e antifraude. A análise estática não demonstra que o TikTok trate isso como uma biometria pessoal capaz de reconhecer sua mão, por isso o campo aparece no exemplo como telemetria e fica fora da comparação de identidade abaixo.</p>
<p>Agora compare o que acontece:</p>
<table>
  <thead>
      <tr>
          <th>Sinal</th>
          <th>Antes</th>
          <th>Reinstalou no mesmo aparelho</th>
          <th>Celular novo e conta nova</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Conta</td>
          <td><code>84721</code></td>
          <td>pode ser a mesma</td>
          <td>nova</td>
      </tr>
      <tr>
          <td>ID da instalação</td>
          <td><code>A9F2</code></td>
          <td>muda</td>
          <td>muda</td>
      </tr>
      <tr>
          <td><code>ANDROID_ID</code></td>
          <td><code>7c81</code></td>
          <td><strong>continua</strong></td>
          <td>muda</td>
      </tr>
      <tr>
          <td>Hardware, tela e sensores</td>
          <td>Pixel 10</td>
          <td><strong>continuam</strong></td>
          <td>mudam</td>
      </tr>
      <tr>
          <td>Idioma, fuso e região</td>
          <td>pt-BR / São Paulo</td>
          <td>provavelmente continuam</td>
          <td>provavelmente continuam</td>
      </tr>
      <tr>
          <td>Rede doméstica</td>
          <td><code>200.10.x.x</code></td>
          <td>pode continuar</td>
          <td>pode continuar</td>
      </tr>
      <tr>
          <td>Horário e padrão de uso</td>
          <td>22h-01h</td>
          <td>tende a reaparecer</td>
          <td>pode reaparecer</td>
      </tr>
      <tr>
          <td>Interesses e sequência de ações</td>
          <td>carros, tecnologia, política</td>
          <td>reaparecem com o uso</td>
          <td>podem reaparecer com o uso</td>
      </tr>
  </tbody>
</table>
<p>Depois da reinstalação, sobram sinais fortes demais pra chamar aquilo de um desconhecido. No celular novo com conta nova, sobra uma hipótese. Quanto mais pontes você mantém, mais a hipótese ganha confiança. Se você entra na conta antiga, acabou a dúvida.</p>
<p>O fingerprint pode começar sem seu nome. Em algum momento você entra numa conta, usa um e-mail, sincroniza contatos, abre um link identificado ou faz qualquer ação que ligue o pseudônimo a você. A partir daí, o histórico anterior pode ser costurado à identidade conhecida. Um <a href="https://www.law.georgetown.edu/tech-institute/wp-content/uploads/sites/42/2026/02/Tech-Brief-Digital-Fingerprinting-February-11-26.pdf"target="_blank" rel="noopener">relatório técnico de 2026 do Georgetown Law Institute for Technology Law &amp; Policy</a> chama isso de <em>identifier bridging</em>: um fingerprint probabilístico ganha uma ponte pra um identificador estável.</p>
<h2>O teatro dos vídeos de cachorro<span class="hx:absolute hx:-mt-20" id="o-teatro-dos-vídeos-de-cachorro"></span>
    <a href="#o-teatro-dos-v%c3%addeos-de-cachorro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vamos usar o exemplo mais comum. Você quer evitar política, fake news e teorias conspiratórias. Então busca vídeos de cachorro, assiste vários até o fim, curte cinquenta e favorita vinte. O feed passa a mostrar mais animais. Funcionou?</p>
<p>Parcialmente. Você ensinou que vídeos de cachorro produzem interação. O TikTok oferece até ferramentas explícitas pra fazer isso: &ldquo;Não tenho interesse&rdquo;, filtros de palavras, gerenciamento de temas e <a href="https://support.tiktok.com/en/getting-started/for-you/test-for-you"target="_blank" rel="noopener">atualização do feed Para Você</a>.</p>
<p>Só que você não apagou política do perfil. Você acrescentou outro interesse. O servidor ainda tem o histórico anterior, as contas seguidas, buscas, vídeos assistidos, pausas, comentários, compartilhamentos e os sinais de pessoas com comportamento semelhante ao seu.</p>
<p>Também existe uma diferença enorme entre o que você acha que comunicou e o que a máquina recebeu:</p>
<ul>
<li>você abriu um vídeo absurdo pra entender o que estavam dizendo;</li>
<li>assistiu até o final porque ficou indignado;</li>
<li>voltou um trecho pra conferir;</li>
<li>abriu os comentários;</li>
<li>respondeu dizendo que era mentira;</li>
<li>mandou o vídeo pra alguém acompanhado de &ldquo;olha que absurdo&rdquo;.</li>
</ul>
<p>Para você, foram seis manifestações de rejeição. Para um modelo olhando eventos, foram vários minutos de atenção e uma sequência rara de interações. O conteúdo funcionou.</p>
<p>O mesmo vale pra sua atuação com animais. Nos eventos enviados ao servidor não existe o campo <code>estou_fingindo_gostar_de_cachorro</code>; chegam buscas, vídeos completos, likes e favoritos. O perfil pode carregar vários interesses ao mesmo tempo. O resultado pode ser alguém que gosta de cachorro <strong>e</strong> consome política quando aparece, especialmente se o segundo assunto gerar reações mais fortes.</p>
<p>Auditorias independentes confirmam que o feed reage a mais de um tipo de sinal. Um <a href="https://arxiv.org/abs/2201.12271"target="_blank" rel="noopener">experimento com contas controladas</a> encontrou efeitos de seguir, curtir e tempo assistido, com seguir como o fator mais forte naquele teste. Outro <a href="https://arxiv.org/abs/2403.12410"target="_blank" rel="noopener">estudo sobre <em>exploration</em> e <em>exploitation</em> de interesses</a> mediu como duração, likes e follows mudam a personalização ao longo da timeline.</p>
<blockquote>
  <p>Você consegue influenciar o feed. Isso é bem diferente de apagar o perfil ou conquistar imunidade contra manipulação.</p>

</blockquote>
<h2>O feed sempre volta a testar você<span class="hx:absolute hx:-mt-20" id="o-feed-sempre-volta-a-testar-você"></span>
    <a href="#o-feed-sempre-volta-a-testar-voc%c3%aa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um recommender não pode servir só o que já sabe. Se fizer isso, fica repetitivo e nunca descobre um interesse novo. Por isso ele alterna <strong>exploitation</strong>, conteúdo com alta chance de funcionar, e <strong>exploration</strong>, apostas usadas pra aprender.</p>
<p>É por isso que o vídeo político, a dieta milagrosa ou a conspiração volta a aparecer no meio dos cachorros. Pode ser popular na sua região, recente, parecido com algo que você viu, consumido por um grupo semelhante ao seu ou simplesmente um teste. O próprio TikTok reconhece que popularidade, diversidade e comportamento de usuários parecidos participam da seleção.</p>
<p>Você pode passar uma semana disciplinado e ignorar tudo. Basta uma noite cansada, um assunto que mexe com você ou um vídeo especialmente eficiente pra produzir os sinais que faltavam. A recomendação se ajusta de novo.</p>
<p>Uma auditoria publicada em 2025 observou <a href="https://arxiv.org/abs/2503.20231"target="_blank" rel="noopener">reforço persistente de conteúdo e redução gradual de diversidade</a> em timelines controladas. O resultado descreve um mecanismo de feedback, sem decretar que todo usuário termina preso numa bolha idêntica: recomendação gera comportamento, comportamento alimenta recomendação, e o próximo lote fica mais parecido com o que funcionou.</p>
<p>O nome &ldquo;echo chamber&rdquo; faz parecer que alguém fechou uma porta por fora. Muitas vezes você participa da construção. Escolhe assistir, comentar, seguir e compartilhar; o sistema reduz a fricção pra repetir aquilo. Em pouco tempo, frequência vira familiaridade e familiaridade começa a parecer consenso.</p>
<h2>A armadilha de achar que você venceu<span class="hx:absolute hx:-mt-20" id="a-armadilha-de-achar-que-você-venceu"></span>
    <a href="#a-armadilha-de-achar-que-voc%c3%aa-venceu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Este é o pedaço mais perigoso. Quem sabe que o feed é manipulado mantém alguma desconfiança. Quem acredita que enganou o sistema baixa a guarda.</p>
<p>&ldquo;Eu treinei meu algoritmo pra não me mostrar fake news&rdquo; vira uma licença pra tratar o que sobrou como confiável. A lógica é circular: se apareceu depois da sua limpeza, deve ter passado pelo seu filtro. Só que o filtro otimiza probabilidade de interação. Ele não funciona como auditor de fatos.</p>
<p>Um estudo publicado no <em>PNAS</em> encontrou um problema parecido fora do TikTok: <a href="https://pubmed.ncbi.nlm.nih.gov/34074757/"target="_blank" rel="noopener">três em cada quatro participantes superestimaram a própria capacidade de distinguir notícias verdadeiras de falsas</a>. Em média, se colocaram 22 percentis acima do desempenho real. Os mais confiantes também visitavam mais sites não confiáveis e demonstravam maior disposição pra curtir ou compartilhar conteúdo falso alinhado à própria política.</p>
<p>Uma auditoria recente do TikTok encontrou outro sinal preocupante: <a href="https://doi.org/10.1016/j.ijhcs.2026.103929"target="_blank" rel="noopener">interagir com desinformação sobre um assunto levou a recomendações de desinformação relacionada e também de outros assuntos</a>. Contas novas sem histórico também receberam mais recomendações desse tipo em alguns experimentos. Um perfil vazio não começa necessariamente num ambiente neutro e limpo.</p>
<p>Sua confiança subjetiva não entra como evidência de que o feed ficou seguro. Ela só reduz a chance de você conferir.</p>
<h2>&ldquo;Não tenho nada importante lá&rdquo;<span class="hx:absolute hx:-mt-20" id="não-tenho-nada-importante-lá"></span>
    <a href="#n%c3%a3o-tenho-nada-importante-l%c3%a1" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Outra defesa comum é dizer que o aplicativo não tem senha de banco, documento secreto nem foto comprometedora. A pessoa avalia cada campo isoladamente e conclui que entregou apenas lixo.</p>
<p>O valor está nas inferências e nas decisões tomadas em cima delas. A <a href="https://t.tiktok.com/legal/page/us/privacy-policy/en"target="_blank" rel="noopener">política do próprio TikTok</a> diz que a empresa infere idade, gênero e interesses. Comportamento repetido pode sugerir rotina, poder de compra, religião, posição política, estado emocional, vulnerabilidades, relacionamentos e acontecimentos importantes da vida. Algumas inferências estarão erradas, o que também causa dano.</p>
<p>Esse poder de inferência já era mensurável muito antes do TikTok. Em 2013, um <a href="https://www.pnas.org/content/pnas/110/15/5802.full.pdf"target="_blank" rel="noopener">estudo com mais de 58 mil voluntários</a> mostrou que Facebook Likes permitiam prever atributos sensíveis como orientação sexual, etnia, religião, posição política, personalidade, idade e gênero. Likes são dados banais até alguém cruzar milhares deles.</p>
<p>Uma plataforma já consegue afetar você escolhendo conteúdo e anúncios. A segmentação também pode ser explorada por campanhas, vendedores e golpistas pra alcançar pessoas no momento em que parecem mais receptivas. Tudo parte de um perfil que você não vê e raramente consegue corrigir.</p>
<p>A <a href="https://www.ftc.gov/system/files/ftc_gov/pdf/Social-Media-6b-Report-9-11-2024.pdf"target="_blank" rel="noopener">FTC analisou as práticas de nove grandes plataformas</a>, incluindo a ByteDance, e descreveu coleta em massa, retenção ampla e monetização por publicidade direcionada. O modelo econômico transforma pequenas observações em segmentação e previsão sem precisar encontrar uma senha valiosa.</p>
<blockquote>
  <p>Privacidade não protege apenas segredos. Ela limita quem pode montar um modelo seu e usar esse modelo pra decidir o que você verá em seguida.</p>

</blockquote>
<h2>Os outros truques que não fazem o que você imagina<span class="hx:absolute hx:-mt-20" id="os-outros-truques-que-não-fazem-o-que-você-imagina"></span>
    <a href="#os-outros-truques-que-n%c3%a3o-fazem-o-que-voc%c3%aa-imagina" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vale limpar mais alguns mitos:</p>
<ul>
<li><strong>VPN:</strong> troca o IP público e ajuda a esconder sua origem da operadora ou da rede local. O app vê que existe uma VPN e continua recebendo conta, fingerprint e comportamento.</li>
<li><strong>Modo anônimo:</strong> serve pro navegador não guardar parte do histórico local. Não muda nada dentro do aplicativo nativo e não impede fingerprinting remoto.</li>
<li><strong>Limpar cache ou reinstalar:</strong> remove estado local descartável. No mesmo aparelho e perfil, identificadores e hardware estáveis continuam.</li>
<li><strong>Criar outra conta:</strong> separa perfis visíveis. No mesmo aparelho, o serviço ainda consegue observar que as duas contas compartilham o dispositivo.</li>
<li><strong>Nunca apertar Like:</strong> deixa de fornecer um sinal explícito. Tempo assistido, conclusão, repetição, busca, pausa, skip e sequência de telas continuam falando.</li>
<li><strong>Seguir fontes variadas:</strong> ajuda e vale a pena, mas até o feed &ldquo;Seguindo&rdquo; é ordenado. O <a href="https://support.tiktok.com/en/using-tiktok/exploring-videos/how-tiktok-recommends-content"target="_blank" rel="noopener">TikTok diz que duas pessoas seguindo os mesmos criadores ainda recebem ordens diferentes</a>.</li>
</ul>
<p>Essas medidas podem reduzir exposição ou mudar recomendações. O erro é transformar mitigação em fantasia de invisibilidade.</p>
<h2>Como usar sem terceirizar seu juízo<span class="hx:absolute hx:-mt-20" id="como-usar-sem-terceirizar-seu-juízo"></span>
    <a href="#como-usar-sem-terceirizar-seu-ju%c3%adzo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você decidiu continuar numa rede social, trate o feed como uma seleção interessada. Cada item foi escolhido porque algum modelo estimou uma reação. Verdade, relevância e equilíbrio são critérios diferentes.</p>
<p>Algumas práticas ajudam:</p>
<ol>
<li>Use os controles reais da plataforma: &ldquo;Não tenho interesse&rdquo;, filtros de palavras, gerenciamento de temas e refresh do feed.</li>
<li>Pare de comentar por raiva. Bloqueie, denuncie quando for o caso e siga em frente. Engajamento indignado continua sendo engajamento.</li>
<li>Para notícias, abra a fonte original fora do aplicativo. Procure confirmação independente e confira data, autoria e contexto.</li>
<li>Misture fontes por decisão própria. Não espere que a exploração automática represente o contraditório de maneira honesta.</li>
<li>Separe diversão de informação. Um feed otimizado pra retenção é uma péssima redação jornalística.</li>
<li>Quando privacidade importar, isole a identidade. Como expliquei no artigo anterior, um perfil Android separado ou aparelho secundário reduz as pontes; spoofing aleatório costuma criar mais anomalias.</li>
</ol>
<p>Também faça pausas. A personalização depende do loop contínuo de recomendação e reação. Sair do fluxo devolve a você a chance de escolher o próximo assunto, em vez de aceitar a próxima aposta do modelo.</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>O mecanismo cabe numa frase: a plataforma tenta reconhecer você, mede suas ações e usa o resultado pra escolher o próximo estímulo. Fingerprinting, telemetria e recomendação fecham esse ciclo.</p>
<p>Favoritar cachorro pode colocar mais cachorro no feed. VPN pode trocar seu IP. Reinstalar pode renovar IDs locais. Cada medida altera uma parte pequena e nenhuma concede imunidade.</p>
<p>O melhor sistema de defesa continua fora do aplicativo: desconfiar do conteúdo que encaixa bem demais nas suas crenças, conferir informação em fontes independentes e lembrar que relevância algorítmica mede a chance de uma reação. Verdade exige outro trabalho.</p>
<p>O algoritmo procura o assunto, o formato e o momento em que você para de questionar. A convicção de que você já venceu facilita exatamente esse trabalho.</p>
]]></content:encoded><category>seguranca</category><category>machine-learning</category></item><item><title>TikTok tecnicamente não se enquadra nas leis Brasileiras (LGPD/Felca). Entenda</title><link>https://akitaonrails.github.io/2026/11/04/tiktok-tecnicamente-nao-se-enquadra-nas-leis-brasileiras-lgpd-felca/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/11/04/tiktok-tecnicamente-nao-se-enquadra-nas-leis-brasileiras-lgpd-felca/</guid><pubDate>Wed, 04 Nov 2026 16:00:00 -0300</pubDate><description>&lt;p&gt;Em julho eu &lt;a href="https://akitaonrails.github.io/2026/07/08/fable-5-analisar-codigo-tiktok-clash-of-kings-govbr-fingerprint/"&gt;pedi pro Fable 5 analisar o código do TikTok&lt;/a&gt;, junto com o Clash of Kings e o app do gov.br. Foi uma descompilação da versão 46 do app Android, feita com jadx, uma varredura de strings nas bibliotecas nativas e bastante leitura de código, bem antes do &lt;a href="https://akitaonrails.github.io/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/"&gt;REA&lt;/a&gt; existir. A conclusão daquela vez: o TikTok não é malware disfarçado, mas faz uma coleta de dados grau industrial, projetada pra ser difícil de auditar.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Em julho eu <a href="/2026/07/08/fable-5-analisar-codigo-tiktok-clash-of-kings-govbr-fingerprint/">pedi pro Fable 5 analisar o código do TikTok</a>, junto com o Clash of Kings e o app do gov.br. Foi uma descompilação da versão 46 do app Android, feita com jadx, uma varredura de strings nas bibliotecas nativas e bastante leitura de código, bem antes do <a href="/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/">REA</a> existir. A conclusão daquela vez: o TikTok não é malware disfarçado, mas faz uma coleta de dados grau industrial, projetada pra ser difícil de auditar.</p>
<p>Agora que eu tenho o REA, resolvi ir mais fundo. Peguei a versão 47.7.1, a mais recente, e dessa vez abri também o código nativo da pilha de rede no Ghidra. Tudo está no repositório <a href="https://github.com/akitaonrails/tiktok_analysis"target="_blank" rel="noopener">tiktok_analysis</a>, com os relatórios novos de 14 a 17 e as evidências linha por linha.</p>
<p>Este artigo é pra duas pessoas ao mesmo tempo: o usuário comum, que quer saber o que o app faz com ele, e o engenheiro de software, que quer saber se dá pra bloquear. E no final tem a parte que interessa a todo brasileiro.</p>
<p>O TikTok não trata todo mundo igual. O app separa os usuários por região, e Estados Unidos, Europa e Coreia do Sul ganham tratamento próprio. O resto do mundo cai num balde só, e o Brasil está nele, <a href="#o-brasileiro-%c3%a9-o-usu%c3%a1rio-menos-protegido">no grupo menos protegido</a>. É por isso que, do jeito que está, o TikTok bate de frente com a LGPD e com a Lei Felca.</p>
<blockquote>
  <p>Tudo aqui vem de <strong>análise estática</strong>: eu li o código do app, não capturei o tráfego de um celular rodando. Isso prova o que o app foi construído pra fazer e pra onde ele foi construído pra mandar. Não prova byte a byte o que sai em cada requisição, porque a parte mais sensível é criptografada em código nativo.</p>

</blockquote>
<h3>Como foi feita, e como reproduzir<span class="hx:absolute hx:-mt-20" id="como-foi-feita-e-como-reproduzir"></span>
    <a href="#como-foi-feita-e-como-reproduzir" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na versão 46, o trabalho foi o jadx transformando o app em Java legível e o LLM lendo o código. O código nativo, as bibliotecas <code>.so</code> em C e C++, ficou só na varredura de strings.</p>
<p>Na 47, um agente rodou tudo sozinho:</p>
<ul>
<li>o <strong>jadx</strong> descompilou as 58 partes de código Java do app, umas 365 mil classes;</li>
<li>o <strong>REA</strong> abriu as bibliotecas nativas no Ghidra e decompilou funções específicas, como o resolvedor de DNS próprio dentro da <code>libsscronet.so</code>;</li>
<li><strong>subagentes em paralelo</strong> varreram a árvore inteira, um por assunto (identificadores, fingerprint, servidores), devolvendo arquivo e linha de cada evidência.</li>
</ul>
<p>Se você quiser refazer, ou aplicar o mesmo método em outro app, o <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/11-diy-reproduce-guide.md#step-8--the-agent--rea-workflow-2026-update-what-reports-1417-used"target="_blank" rel="noopener">relatório 11</a> tem o passo a passo, com os prompts prontos pro agente principal e pros subagentes e a sequência exata de ferramentas do REA.</p>
<h2>O que o TikTok coleta, em português claro<span class="hx:absolute hx:-mt-20" id="o-que-o-tiktok-coleta-em-português-claro"></span>
    <a href="#o-que-o-tiktok-coleta-em-portugu%c3%aas-claro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Primeiro a boa notícia, porque ela é importante e muita gente erra aqui. O TikTok <strong>não</strong> espiona o resto do seu celular:</p>
<ul>
<li>não lê seus SMS, seu histórico de ligações, suas notificações, nem o que você digita em outros apps;</li>
<li>não tem serviço de acessibilidade, que é o jeito que apps maliciosos usam pra ler a tela de outros apps;</li>
<li>não lista todos os apps instalados;</li>
<li>não pega IMEI, número de telefone nem endereço MAC.</li>
</ul>
<p>O sandbox do Android impede, e o app não pede as permissões que contornariam isso. O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/10-os-and-clipboard-access.md"target="_blank" rel="noopener">relatório 10</a> verificou isso na árvore inteira, inclusive a área de transferência, que só é lida quando você cola algo. A única permissão nova do Android na versão 47 serve pra detectar quando alguém está gravando a tela <em>do próprio TikTok</em>.</p>
<p>A má notícia é o que ele coleta <strong>sem pedir nada</strong>, porque o Android não exige permissão pra isso. O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/14-v47-data-inventory-and-dns-blocking.md"target="_blank" rel="noopener">relatório 14</a> tem o inventário completo. Resumindo:</p>
<ul>
<li><strong>quem é o seu aparelho</strong>: vários identificadores persistentes, incluindo o <code>ANDROID_ID</code>, que sobrevive a limpar os dados e a reinstalar o app. E toda vez que você abre ou volta pro app, no máximo uma vez por hora, ele reenvia os principais identificadores, criptografados, pra um endpoint dedicado;</li>
<li><strong>o que o seu aparelho é</strong>: modelo, CPU, GPU, memória, a lista de sensores, a especificação completa de cada câmera (sem pedir permissão de câmera), os teclados instalados e os serviços de acessibilidade ativos;</li>
<li><strong>uma prova assinada pelo Google do seu aparelho</strong>: o app pede ao chip de segurança do celular uma atestação de hardware, que confirma o modelo, o estado do bootloader e o nível de patch. Isso não dá pra falsificar;</li>
<li><strong>como você segura e toca o celular</strong>: pressão e tamanho do toque em cada tela, e amostras de seis sensores de movimento (detalhes logo abaixo);</li>
<li><strong>onde você está, sem pedir localização</strong>: operadora, país do chip, fuso horário, idioma, os servidores de DNS do seu Wi-Fi e se você está usando VPN ou proxy;</li>
<li><strong>tudo o que você faz dentro do app</strong>: são mais de 6 mil <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/_evidence/v47/applog_event_names.txt"target="_blank" rel="noopener">tipos de evento diferentes</a>, incluindo quanto tempo você assiste cada vídeo, o que você pesquisa, o que curte, quem visita.</li>
</ul>
<h3>Coisas que você nem imagina que são medidas<span class="hx:absolute hx:-mt-20" id="coisas-que-você-nem-imagina-que-são-medidas"></span>
    <a href="#coisas-que-voc%c3%aa-nem-imagina-que-s%c3%a3o-medidas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A lista acima já é grande, mas o detalhe impressiona mais. A sonda de risco do app sozinha tem cerca de 90 campos, e o motor de segurança nativo tem outra lista própria. Algumas das variáveis que o código lê, todas sem pedir permissão:</p>
<ul>
<li><strong>cada toque na tela</strong>: a pressão, o tamanho da área de contato, o tipo de &ldquo;ferramenta&rdquo; (dedo, caneta), o dispositivo de entrada e o caminho completo do dedo, ponto a ponto, com o tempo de cada ponto. Isso descreve cada rolagem e cada deslizar, e é registrado em todas as telas do app. As teclas que você digita ficam de fora;</li>
<li><strong>como o celular se move na sua mão</strong>: acelerômetro, giroscópio, magnetômetro, gravidade, aceleração linear e rotação, além da orientação do aparelho calculada a partir deles. O app usa isso pra saber se tem um humano segurando o celular. Mas são exatamente esses os sinais que permitem dizer se você está andando, sentado, deitado ou dentro de um carro;</li>
<li><strong>o &ldquo;tremor&rdquo; do giroscópio e a variação do sensor de luz</strong>, ou seja, o quanto sua mão treme e o quanto a luz em volta muda;</li>
<li><strong>a bateria</strong>: nível, se está carregando, e um histórico das últimas 20 leituras mais ou menos;</li>
<li><strong>o volume da mídia, o modo avião e o estado do chip</strong>;</li>
<li><strong>se o microfone e a câmera têm permissão</strong>, mesmo quando você não está usando;</li>
<li><strong>quando o celular foi ligado pela última vez</strong> e há quanto tempo está ligado, o que permite juntar sessões mesmo depois de reinstalar o app;</li>
<li><strong>quais teclados e quais serviços de acessibilidade estão instalados</strong>;</li>
<li><strong>espaço em disco, memória, taxa de atualização da tela, modelo da GPU</strong>, criando um contexto gráfico só pra perguntar;</li>
<li><strong>se você está numa ligação</strong> ou com o microfone ativo em outro app, se o modo desenvolvedor está ligado, se tem cabo USB conectado.</li>
</ul>
<p>E dentro do app, além do que você assiste, ele registra quanto tempo você fica em cada vídeo, quando você pausa, repete ou arrasta a barra de progresso, e o texto de cada busca.</p>
<blockquote>
  <p>Nenhum desses dados sozinho parece grave. Juntos, eles descrevem o seu aparelho, a sua mão e os seus hábitos com uma precisão que nenhum formulário de cadastro jamais pediria.</p>

</blockquote>
<h2>O que é fingerprinting, e por que isso é perigoso<span class="hx:absolute hx:-mt-20" id="o-que-é-fingerprinting-e-por-que-isso-é-perigoso"></span>
    <a href="#o-que-%c3%a9-fingerprinting-e-por-que-isso-%c3%a9-perigoso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quase tudo da lista acima serve pra montar uma coisa chamada <strong>fingerprint</strong>, a impressão digital do dispositivo.</p>
<p>A ideia é simples. Nenhum dado sozinho identifica você: milhões de pessoas têm o mesmo modelo de celular, o mesmo fuso horário, a mesma operadora. Mas a <strong>combinação</strong> de dezenas desses sinais é tão específica que vira praticamente única. É um número de série que você nunca autorizou e nem sabe que existe.</p>
<p>O que torna isso valioso, e perigoso, é a <strong>persistência</strong>:</p>
<ul>
<li>um cookie você apaga;</li>
<li>o identificador de anúncio você reseta;</li>
<li>o fingerprint sobrevive a limpar os dados e a reinstalar o app. Só um reset de fábrica troca o <code>ANDROID_ID</code>, e mesmo assim o modelo, os sensores e a atestação continuam descrevendo o mesmo aparelho.</li>
</ul>
<p>O servidor ainda pode reemitir o mesmo identificador de dispositivo quando reconhece os outros sinais. No mesmo aparelho e no mesmo perfil, recomeçar do zero é muito difícil.</p>
<h3>Por que isso é nefasto<span class="hx:absolute hx:-mt-20" id="por-que-isso-é-nefasto"></span>
    <a href="#por-que-isso-%c3%a9-nefasto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O problema não é só anúncio. Um identificador que não morre permite:</p>
<ul>
<li><strong>juntar tudo</strong>: o que você assiste, pesquisa e curte vira um perfil ligado ao <em>aparelho</em>, que sobrevive mesmo se você apagar a conta;</li>
<li><strong>cruzar entre apps</strong>: o TikTok tem um endpoint específico pra ligar os identificadores do seu aparelho entre os apps da ByteDance, como CapCut e Lemon8;</li>
<li><strong>reidentificar você</strong>: se você apaga a conta e cria outra no mesmo celular, a plataforma tem como saber que é o mesmo aparelho, porque o <code>ANDROID_ID</code> é o mesmo;</li>
<li><strong>inferir o que você não contou</strong>: o que você assiste e por quanto tempo pode revelar interesses, estado emocional, orientação, religião, política. E você não tem como ver nem corrigir esse perfil.</li>
</ul>
<p>E tem o agravante que já apareceu na análise de julho: o fingerprint é montado e <strong>criptografado em código nativo</strong>, dentro da biblioteca <code>libmetasec_ov.so</code> (o <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/13-native-library-analysis.md"target="_blank" rel="noopener">relatório 13</a> mostra o que dá pra ler dela). Nem descompilando o app inteiro dá pra saber exatamente o que vai nele. Na versão 47, as strings dessa biblioteca parecem ainda mais criptografadas que na 46.</p>
<blockquote>
  <p>Você não consegue auditar, não consegue apagar, e no mesmo aparelho é muito difícil recomeçar. Esse é o ponto central.</p>

</blockquote>
<h2>Dá pra bloquear? A parte pros engenheiros<span class="hx:absolute hx:-mt-20" id="dá-pra-bloquear-a-parte-pros-engenheiros"></span>
    <a href="#d%c3%a1-pra-bloquear-a-parte-pros-engenheiros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A minha primeira ideia foi a óbvia: bloquear os servidores de telemetria no meu DNS, o NextDNS, ou modificar o app pra tirar a coleta. Pedi pro agente estudar as duas opções a fundo. Nenhuma das duas resolve.</p>
<h3>Por que bloquear no DNS não funciona<span class="hx:absolute hx:-mt-20" id="por-que-bloquear-no-dns-não-funciona"></span>
    <a href="#por-que-bloquear-no-dns-n%c3%a3o-funciona" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Bloquear por DNS (NextDNS, Pi-hole, AdGuard) funciona assim: o app pergunta &ldquo;qual o IP de <code>log.tiktokv.com</code>?&rdquo; e o seu DNS responde &ldquo;nenhum&rdquo;. Só que o TikTok foi construído pra não depender do seu DNS:</p>
<ul>
<li><strong>ele tem o próprio resolvedor de DNS</strong>, ligado por padrão, e chega nele por <strong>IP direto</strong> (<code>34.102.215.99</code> fora dos EUA). Quando o app usa esse resolvedor, o seu DNS não vê a consulta, e quem decide quando usar é o servidor;</li>
<li><strong>os servidores principais da API estão fixados em IPs</strong> da própria ByteDance, cravados no código;</li>
<li><strong>o servidor pode reescrever as rotas a qualquer momento</strong>: na abertura, o app baixa uma lista de regras que pode mandar qualquer endereço pra outro, ou fixar qualquer nome num IP;</li>
<li><strong>os IPs de resolvedor de reserva ficam salvos no celular</strong>, então um IP aprendido uma vez sobrevive ao bloqueio que você fizer depois;</li>
<li><strong>as estatísticas de cada vídeo assistido viajam no mesmo servidor do feed</strong>. Uma regra embutida força isso. Se você bloquear, o feed para junto.</li>
</ul>
<p>Dá pra reduzir bastante. O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/14-v47-data-inventory-and-dns-blocking.md#4-where-it-goes-and-the-blocklist"target="_blank" rel="noopener">relatório 14</a> tem uma lista de bloqueio em camadas e as regras de firewall pro roteador que fecham o resolvedor próprio. Isso corta rastreadores de terceiros, uploads de log e crash, o reenvio de identificadores de hora em hora e boa parte do fluxo de eventos. Mas não esconde do TikTok o que você assiste, e o servidor pode mudar as rotas na próxima atualização.</p>
<h3>Por que modificar o app também não funciona<span class="hx:absolute hx:-mt-20" id="por-que-modificar-o-app-também-não-funciona"></span>
    <a href="#por-que-modificar-o-app-tamb%c3%a9m-n%c3%a3o-funciona" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A segunda ideia era reempacotar: descompilar, transformar os coletores em código morto, reassinar e instalar. O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/15-repackaging-feasibility-and-patch-plan.md"target="_blank" rel="noopener">relatório 15</a> estudou isso, e esbarra em três paredes:</p>
<ol>
<li><strong>o fingerprint é o assinador das requisições.</strong> A mesma biblioteca nativa que monta o fingerprint assina todas as chamadas à API. Se você tira um, perde o outro, e o servidor rejeita a requisição sem assinatura. O feed para;</li>
<li><strong>a sua assinatura entra na assinatura do aparelho.</strong> O código nativo lê o certificado com que o app foi assinado. Reassinar com a sua chave muda isso, e o servidor sabe que você roda um cliente modificado. Pode responder com captcha, verificação de telefone ou conta limitada, quando quiser;</li>
<li><strong>a configuração vem do servidor.</strong> Rotas, regras de DNS e listas de eventos são baixadas em tempo de execução, então um patch estático só muda os valores padrão.</li>
</ol>
<p>Dá pra remover a telemetria &ldquo;de luxo&rdquo; na fonte: o reenvio de identificadores, o AppsFlyer, o Firebase Analytics, os uploads de crash. Mas não o fingerprint nem o seu comportamento, e você entra numa esteira de refazer o patch a cada atualização do TikTok.</p>
<h3>Falsificar também não<span class="hx:absolute hx:-mt-20" id="falsificar-também-não"></span>
    <a href="#falsificar-tamb%c3%a9m-n%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>E falsificar os dados piora.</p>
<p>O app verifica root, Magisk, Xposed, Frida, emulador, localização falsa, bootloader destravado e modo desenvolvedor. A atestação de hardware assinada pelo Google denuncia qualquer modelo falsificado. Os sensores de toque e movimento existem justamente pra detectar valores sintéticos. O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/08-mitigation-options.md"target="_blank" rel="noopener">relatório 8</a> explica por que cada tentativa de falsificar sai pela culatra. Falsificar coloca você no balde de &ldquo;aparelho adulterado&rdquo;, e o fingerprint sai do mesmo jeito.</p>
<h2>A única saída hoje: isolar<span class="hx:absolute hx:-mt-20" id="a-única-saída-hoje-isolar"></span>
    <a href="#a-%c3%banica-sa%c3%adda-hoje-isolar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se não dá pra esconder o comportamento de quem te entrega o feed, o objetivo muda: fazer com que esse comportamento fique ligado a uma <strong>identidade descartável</strong>, que não leva até você. Isolar, sem falsificar nada. Tem dois jeitos:</p>
<h3>Um celular secundário<span class="hx:absolute hx:-mt-20" id="um-celular-secundário"></span>
    <a href="#um-celular-secund%c3%a1rio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um aparelho barato, ou um tablet, só pro TikTok:</p>
<ul>
<li><strong>sem chip</strong>, ou só no Wi-Fi: os campos de operadora e país do chip vão vazios, o que é normal em tablet;</li>
<li><strong>sem login</strong>, ou com um e-mail que você não usa em nenhum outro lugar. Nada de entrar com Google, Facebook ou número de telefone;</li>
<li><strong>sem permissões</strong>: negue localização, contatos, fotos, microfone e câmera;</li>
<li><strong>ID de anúncio apagado</strong>, em Configurações → Privacidade → Anúncios;</li>
<li>na rede, a lista de bloqueio e as regras de roteador do <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/14-v47-data-inventory-and-dns-blocking.md#5-the-fake-substitute-plan"target="_blank" rel="noopener">relatório 14</a>.</li>
</ul>
<p><strong>Prós:</strong> é a separação mais completa. Tudo o que o fingerprint descreve é <em>aquele</em> aparelho, e nada é falsificado, então nada contradiz a atestação. O CapCut e os outros apps da ByteDance ficam longe da sua vida real.</p>
<p><strong>Contras:</strong> custa um aparelho, é mais um equipamento pra carregar e atualizar, e é menos prático. E o que você assiste continua sendo registrado, só que preso a uma identidade que não é a sua.</p>
<h3>Um perfil separado no mesmo celular<span class="hx:absolute hx:-mt-20" id="um-perfil-separado-no-mesmo-celular"></span>
    <a href="#um-perfil-separado-no-mesmo-celular" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Android permite um espaço isolado: o <strong>Espaço Privado</strong> no Android 15 ou mais novo, ou um perfil de trabalho com apps como Shelter ou Island. O TikTok vai lá dentro, com as mesmas regras de sem login e sem permissões.</p>
<p><strong>Prós:</strong> custo zero. Cada perfil tem o próprio <code>ANDROID_ID</code>, o app não enxerga seus contatos, fotos e outros apps, e nada é falsificado.</p>
<p><strong>Contras:</strong> o hardware é o mesmo. O fingerprint de hardware, a atestação e o chip do seu celular continuam iguais, então quem quiser cruzar consegue ligar o perfil ao aparelho. Isola bem os identificadores de software, mas o aparelho continua o mesmo.</p>
<blockquote>
  <p>Não existe configuração que faça o TikTok parar de saber o que você assiste. O que existe é garantir que esse &ldquo;você&rdquo; seja uma identidade descartável.</p>

</blockquote>
<h2>O brasileiro é o usuário menos protegido<span class="hx:absolute hx:-mt-20" id="o-brasileiro-é-o-usuário-menos-protegido"></span>
    <a href="#o-brasileiro-%c3%a9-o-usu%c3%a1rio-menos-protegido" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui fica pior pra gente. O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/16-regional-data-treatment.md"target="_blank" rel="noopener">relatório 16</a> mostra que o app separa todo usuário em cinco regiões de compliance:</p>
<ul>
<li><strong>US</strong>: Estados Unidos;</li>
<li><strong>EU</strong>: Europa, no caso o Espaço Econômico Europeu;</li>
<li><strong>KR</strong>: Coreia do Sul, por causa da lei de proteção de dados de lá;</li>
<li><strong>ROW</strong>: &ldquo;rest of world&rdquo;, o resto do mundo. <strong>O Brasil cai aqui</strong>;</li>
<li><strong>UNKNOWN</strong>: ainda não determinado.</li>
</ul>
<p>O que muda entre elas:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>EUA</th>
          <th>Europa (EEE)</th>
          <th>Brasil</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Infraestrutura dedicada pros dados</td>
          <td>Sim (Project Texas, com a Oracle; desde janeiro de 2026, uma joint venture americana)</td>
          <td>Sim (Project Clover, Noruega e Irlanda)</td>
          <td>Não, vai pra infraestrutura global da ByteDance</td>
      </tr>
      <tr>
          <td>Modo do motor de fingerprint</td>
          <td>503, o modo de compliance</td>
          <td>503, o modo de compliance</td>
          <td>603, o modo padrão</td>
      </tr>
      <tr>
          <td>Tratamento regulatório específico</td>
          <td>Sim (exigência do governo americano)</td>
          <td>Sim (GDPR)</td>
          <td>Nenhum. Zero ocorrências de &ldquo;LGPD&rdquo; no código</td>
      </tr>
  </tbody>
</table>
<p>O que cada modo coleta exatamente fica dentro do código nativo criptografado, então não dá pra medir a diferença byte a byte. Mas a estrutura é clara: o caminho com freio regulatório foi escrito pros EUA e pra Europa, e o Brasil cai no padrão. A Coreia pelo menos ganhou o próprio balde no código; o Brasil, com uma lei inspirada na GDPR, não ganhou nem isso.</p>
<p>E não adianta trocar a região da loja do Google pra Europa. O balde vem da região da conta TikTok, que o servidor define, e forçar divergências tende a virar sinal de fraude.</p>
<h2>LGPD e Lei Felca: por que o TikTok precisa se adequar<span class="hx:absolute hx:-mt-20" id="lgpd-e-lei-felca-por-que-o-tiktok-precisa-se-adequar"></span>
    <a href="#lgpd-e-lei-felca-por-que-o-tiktok-precisa-se-adequar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/akitaonrails/tiktok_analysis/blob/main/docs/17-brazilian-law-compliance.md"target="_blank" rel="noopener">relatório 17</a> cruza essas descobertas com a lei. Vale a ressalva: quem decide se houve infração é a ANPD e a Justiça, olhando também a política de privacidade, as telas de consentimento e o que acontece no servidor. A análise do app mostra onde ele <strong>bate de frente</strong> com a lei.</p>
<h3>LGPD<span class="hx:absolute hx:-mt-20" id="lgpd"></span>
    <a href="#lgpd" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <a href="https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm"target="_blank" rel="noopener">Lei Geral de Proteção de Dados</a> tem princípios que o app tem dificuldade de cumprir:</p>
<ul>
<li><strong>necessidade</strong> (art. 6º): coletar só o necessário pra finalidade. Reenviar os principais identificadores a cada hora, uma sonda de cerca de 90 campos sobre root e hardware, toque e sensores de movimento, tudo isso vai muito além do que assistir vídeo curto exige;</li>
<li><strong>transparência</strong> (arts. 6º e 9º): o titular tem direito a saber o que é coletado. Com o fingerprint criptografado em código nativo, o usuário não tem como saber, e nem descompilando o app dá pra ver;</li>
<li><strong>direitos do titular</strong> (art. 18, IV e VI): eliminação de dados desnecessários ou excessivos, e dos dados tratados com consentimento. Um identificador que sobrevive à limpeza e à reinstalação, e que o servidor pode reemitir ao reconhecer o aparelho, esvazia esse direito na prática;</li>
<li><strong>transferência internacional</strong> (art. 33): sem infraestrutura dedicada pro Brasil, os dados vão pra infraestrutura global da ByteDance;</li>
<li><strong>dados de crianças</strong> (art. 14): o app tem um Modo Infantil que apaga identificadores e zera o ID de anúncio, mas ele só vale se o app souber que o usuário é criança. O caminho padrão perfila todo mundo.</li>
</ul>
<p>Esse último ponto não é teoria. A ANPD já <a href="https://teletime.com.br/04/11/2024/anpd-tiktok-falha-no-tratamento-de-dados-de-criancas-e-adolescentes/"target="_blank" rel="noopener">abriu processo contra o TikTok</a> em novembro de 2024 por tratar dados de crianças e adolescentes sem verificação de idade, e <a href="https://canaltech.com.br/redes-sociais/tiktok-tera-que-desativar-acesso-ao-feed-sem-cadastro-no-brasil-determina-anpd/"target="_blank" rel="noopener">mandou desativar o acesso ao feed sem cadastro</a> no Brasil.</p>
<p>Em agosto de 2026 veio a conta: a ANPD <a href="https://www.poder360.com.br/poder-tech/anpd-multa-dona-do-tiktok-em-r-1537-milhoes/"target="_blank" rel="noopener">multou a ByteDance em R$ 153,7 milhões</a> por tratar dados de crianças e adolescentes, dizendo que a empresa &ldquo;aceitou o risco de realizar o tratamento massivo de dados pessoais de crianças e adolescentes&rdquo;. O feed sem cadastro <a href="https://teletime.com.br/25/08/2026/tiktok-muda-feed-multa-anpd/"target="_blank" rel="noopener">continua existindo</a>, mas dentro de um plano de conformidade, sem anúncios e com a coleta limitada.</p>
<h3>Lei Felca (ECA Digital)<span class="hx:absolute hx:-mt-20" id="lei-felca-eca-digital"></span>
    <a href="#lei-felca-eca-digital" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <a href="https://www.planalto.gov.br/ccivil_03/_ato2023-2026/2025/lei/l15211.htm"target="_blank" rel="noopener">Lei 15.211/2025</a>, o ECA Digital, ganhou o apelido de Lei Felca por causa dos vídeos do influenciador Felca sobre adultização de crianças na internet, que empurraram o projeto em 2025. Está em vigor desde março de 2026 e exige das plataformas, entre outras coisas:</p>
<ul>
<li>verificação de idade confiável, que não seja só autodeclaração;</li>
<li>contas de menores de 16 anos vinculadas a um responsável;</li>
<li>proibição de publicidade direcionada com dados de crianças;</li>
<li>proteção desde o design.</li>
</ul>
<p>A maior parte disso acontece no servidor, então a análise do app não prova nem cumprimento nem infração. O que ela mostra: a ferramenta pra proteger dados de crianças existe no código (o Modo Infantil), mas depende inteiramente de o app saber quem é menor. E é exatamente aí que a ANPD já apontou falha.</p>
<h3>Se adequar ou sair<span class="hx:absolute hx:-mt-20" id="se-adequar-ou-sair"></span>
    <a href="#se-adequar-ou-sair" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Juntando tudo:</p>
<ul>
<li>o app tem tratamento regulatório pros EUA e pra Europa, e nada pensado pro Brasil (a única minimização que vale aqui é o Modo Infantil, que é igual em todo lugar);</li>
<li>os dados dos brasileiros não têm infraestrutura dedicada;</li>
<li>o fingerprint não é auditável nem apagável;</li>
<li>a proteção de menores depende de uma verificação de idade que o regulador já considerou fraca.</li>
</ul>
<p>A ByteDance sabe fazer diferente: fez pros americanos e pros europeus, porque lá foi obrigada. Nos EUA, a obrigação foi tão longe que a operação americana virou uma joint venture separada. O Brasil tem uma lei de proteção de dados no mesmo espírito da GDPR e agora uma lei específica pra crianças.</p>
<blockquote>
  <p>O TikTok precisa tratar o usuário brasileiro como trata o europeu: com infraestrutura dedicada, minimização de verdade e verificação de idade que funcione. Se não fizer isso, não deveria poder operar aqui.</p>

</blockquote>
<p>Enquanto isso não acontece, a proteção fica nas suas mãos. Se for usar, use num aparelho ou perfil isolado, sem login e sem permissões. E se tem criança em casa com TikTok no celular, agora você sabe o que está sendo coletado.</p>
]]></content:encoded><category>seguranca</category><category>leis-e-regulacao</category></item><item><title>Criando um framework para IA com IA: REA Launcher SDK</title><link>https://akitaonrails.github.io/2026/11/02/criando-um-framework-para-ia-com-ia-rea-launcher-sdk/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/11/02/criando-um-framework-para-ia-com-ia-rea-launcher-sdk/</guid><pubDate>Mon, 02 Nov 2026 21:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.github.io/2026/10/29/brincando-de-pc-ports-com-colin-mcrae-1/"&gt;primeiro artigo da série&lt;/a&gt; eu mostrei o &lt;a href="https://github.com/akitaonrails/rea-colin-mcrae"target="_blank" rel="noopener"&gt;launcher do Colin McRae Rally&lt;/a&gt; de 1998: uma camada fina em volta do executável original que faz o jogo rodar em 4K, widescreen e 60 fps num PC moderno, com o &lt;a href="https://akitaonrails.github.io/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/"&gt;REA&lt;/a&gt; fazendo a engenharia reversa. No &lt;a href="https://akitaonrails.github.io/2026/11/02/consertei-varios-jogos-de-corrida-antigos-usando-ia/"&gt;segundo&lt;/a&gt;, hoje de manhã, eu repeti a receita em mais meia dúzia de jogos de corrida: &lt;a href="https://github.com/akitaonrails/rea-colin-mcrae-3"target="_blank" rel="noopener"&gt;Colin McRae 3&lt;/a&gt;, &lt;a href="https://github.com/akitaonrails/rea-colin-mcrae-4"target="_blank" rel="noopener"&gt;04&lt;/a&gt; e &lt;a href="https://github.com/akitaonrails/rea-dirt"target="_blank" rel="noopener"&gt;DiRT&lt;/a&gt;, &lt;a href="https://github.com/akitaonrails/rea-outrun-2006"target="_blank" rel="noopener"&gt;OutRun 2006&lt;/a&gt;, &lt;a href="https://github.com/akitaonrails/rea-sega-rally-revo"target="_blank" rel="noopener"&gt;Sega Rally Revo&lt;/a&gt; e a &lt;a href="https://github.com/akitaonrails/rea-screamer-trilogy"target="_blank" rel="noopener"&gt;trilogia Screamer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Depois de fazer esses launchers pra trazer de volta os meus jogos favoritos, eu quis que mais gente pudesse fazer o mesmo com os jogos &lt;em&gt;deles&lt;/em&gt;. Pra isso, juntei tudo num framework: o &lt;a href="https://github.com/akitaonrails/rea-launcher-sdk"target="_blank" rel="noopener"&gt;rea-launcher-sdk&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="/2026/10/29/brincando-de-pc-ports-com-colin-mcrae-1/">primeiro artigo da série</a> eu mostrei o <a href="https://github.com/akitaonrails/rea-colin-mcrae"target="_blank" rel="noopener">launcher do Colin McRae Rally</a> de 1998: uma camada fina em volta do executável original que faz o jogo rodar em 4K, widescreen e 60 fps num PC moderno, com o <a href="/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/">REA</a> fazendo a engenharia reversa. No <a href="/2026/11/02/consertei-varios-jogos-de-corrida-antigos-usando-ia/">segundo</a>, hoje de manhã, eu repeti a receita em mais meia dúzia de jogos de corrida: <a href="https://github.com/akitaonrails/rea-colin-mcrae-3"target="_blank" rel="noopener">Colin McRae 3</a>, <a href="https://github.com/akitaonrails/rea-colin-mcrae-4"target="_blank" rel="noopener">04</a> e <a href="https://github.com/akitaonrails/rea-dirt"target="_blank" rel="noopener">DiRT</a>, <a href="https://github.com/akitaonrails/rea-outrun-2006"target="_blank" rel="noopener">OutRun 2006</a>, <a href="https://github.com/akitaonrails/rea-sega-rally-revo"target="_blank" rel="noopener">Sega Rally Revo</a> e a <a href="https://github.com/akitaonrails/rea-screamer-trilogy"target="_blank" rel="noopener">trilogia Screamer</a>.</p>
<p>Depois de fazer esses launchers pra trazer de volta os meus jogos favoritos, eu quis que mais gente pudesse fazer o mesmo com os jogos <em>deles</em>. Pra isso, juntei tudo num framework: o <a href="https://github.com/akitaonrails/rea-launcher-sdk"target="_blank" rel="noopener">rea-launcher-sdk</a>.</p>
<p>Este artigo conta como ele nasceu, e por que eu fiz na ordem que fiz.</p>
<h2>Primeiro os apps, depois o framework<span class="hx:absolute hx:-mt-20" id="primeiro-os-apps-depois-o-framework"></span>
    <a href="#primeiro-os-apps-depois-o-framework" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Muito amador pensa do jeito errado: primeiro você desenha um framework novo e bonito, e depois constrói aplicações em cima dele. Esse é o caminho completamente errado pra fazer biblioteca ou framework.</p>
<p>O caminho certo é o contrário:</p>
<ol>
<li>implementar <strong>vários projetos do mesmo tipo</strong>, cada um resolvendo o seu problema de verdade;</li>
<li>avaliar todos eles juntos, comparando o que cada um fez;</li>
<li><strong>normalizar</strong> com refatoração, até todos terem a mesma fundação comum;</li>
<li>só então <strong>extrair</strong> as partes comuns pra um framework de verdade.</li>
</ol>
<p>O Rails nasceu assim, extraído do Basecamp. Framework desenhado antes do primeiro app sempre resolve o problema que o autor <em>imaginou</em>, e erra o que aparece na prática.</p>
<blockquote>
  <p>Você só sabe o que é comum depois de ver a mesma coisa feita três, quatro, cinco vezes. Antes disso, é chute.</p>

</blockquote>
<p>Foi o que eu fiz aqui. A diferença é que dessa vez cada passo foi feito com LLMs.</p>
<h2>Os launchers antes do SDK<span class="hx:absolute hx:-mt-20" id="os-launchers-antes-do-sdk"></span>
    <a href="#os-launchers-antes-do-sdk" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todos os launchers foram feitos com agentes, e eu usei vários: Claude, Codex, Antigravity e outros. O que mais tomou meu tempo foi o primeiro, o do Colin McRae 1. Ele começou em 20 de outubro e hoje tem quase 800 commits, porque foi nele que eu descobri o que um launcher desses precisa ter, junto com o recomp e o pacote de texturas HD.</p>
<p>Pros outros, bastava dizer algo como <em>&ldquo;copie tudo do rea-colin-mcrae e adapte pro jogo X&rdquo;</em>. Isso acelerou muito os seguintes. O problema é que cada agente copiou e adaptou do seu jeito, e no fim cada launcher estava pra um lado.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/cmr1-launcher.png" alt="O launcher do Colin McRae Rally, o primeiro da família"  loading="lazy" /></p>
<p>A auditoria que o agente do SDK fez depois mostrou o tamanho da bagunça. Cada bug encontrado existia em uma ou mais cópias, e já estava corrigido em outra:</p>
<ul>
<li>o launcher do Colin McRae 3 tinha uma conversão do executável que <strong>nunca rodava</strong>, porque o caminho do programa era passado duas vezes na linha de comando;</li>
<li>o do Screamer criava a cópia privada com links simbólicos pra pasta original, então o jogo podia escrever na instalação do usuário;</li>
<li>o do Sega Rally escrevia <code>&quot;Disable Hidraw&quot;</code>, com espaço, numa chave que o Wine lê como <code>DisableHidraw</code>;</li>
<li>o do OutRun copiava saves sem checar qual era o mais novo, e o comando de parar matava o shell auxiliar em vez do jogo;</li>
<li>três launchers montavam um script interpolando o caminho do launcher sem escapar as aspas.</li>
</ul>
<p>Umas 2 mil linhas de código se repetiam em cada launcher, cada cópia com os seus próprios bugs.</p>
<h2>Extraindo o SDK<span class="hx:absolute hx:-mt-20" id="extraindo-o-sdk"></span>
    <a href="#extraindo-o-sdk" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando eu achei que tinha material suficiente, criei o rea-launcher-sdk e pedi pro Claude fazer exatamente o que eu prego: estudar cinco dos sete launchers que eu tinha até ali (o Colin McRae 1 e o DiRT entraram depois), normalizar, extrair o que era comum e montar um framework de verdade, com as minhas técnicas de engenharia de software embutidas em forma de skills.</p>
<p>O primeiro commit, às 10h50 de hoje, diz <em>&ldquo;SDK inicial extraído dos cinco launchers rea&rdquo;</em>. Cada peça é a melhor versão entre as cinco cópias, e um documento de auditoria registra de qual launcher veio cada uma. A cópia privada veio de um, a instalação de DLLs de outro, e o hook de fechar janela, que entrou na 0.3.0, veio do Colin McRae 04.</p>
<p>O que ficou no SDK:</p>
<ul>
<li><strong>o núcleo portátil</strong> (<code>rea-launcher</code>): cópia privada do jogo, espelho de saves, identificação do executável por SHA-256, patches binários declarativos, a tabela de opções, edição de INI, downloads com hash fixo;</li>
<li><strong>a camada Windows</strong> (<code>rea-launcher-win32</code>): registro, processos, instância única, mensagens de erro, detecção de GPU e de Wine;</li>
<li><strong>a interface</strong> (<code>rea-launcher-ui</code>): a janela de configurações em egui, com tema, checagem de contraste e testes da interface sem tela;</li>
<li><strong>o hook de fechar</strong> (<code>rea-launcher-close-hook</code>): uma DLL que transforma o Alt+F4 ou o botão de fechar no caminho de saída do jogo, configurável sem recompilar;</li>
<li><strong>o lado Linux</strong>: uma biblioteca de shell pro script de jogar com gamescope e Wine, e o empacotamento em AppImage;</li>
<li><strong>a CI</strong>: workflows reutilizáveis de build, testes, checagem de importações e release, e um workflow semanal que abre um pull request quando sai versão nova do SDK.</li>
</ul>
<p>O que <strong>não</strong> foi extraído é o conhecimento de cada jogo: qual byte patchear, qual chave de registro escrever, qual community fix usar. Isso continua em cada launcher, e o SDK recebe como dado.</p>
<h3>De 0.1 a 0.3 em umas quatro horas<span class="hx:absolute hx:-mt-20" id="de-01-a-03-em-umas-quatro-horas"></span>
    <a href="#de-01-a-03-em-umas-quatro-horas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A primeira versão saiu às 11h07, depois de uma revisão completa do próprio SDK. A 0.3.3 saiu às 15h12. Entre uma e outra, o ciclo foi sempre o mesmo:</p>
<ol>
<li>migrar os launchers pra versão nova do SDK;</li>
<li>a migração expõe um bug ou uma lacuna no SDK;</li>
<li>o SDK corrige e solta outra versão;</li>
<li>os launchers sobem de versão de novo.</li>
</ol>
<p>Alguns exemplos do que esse ciclo pegou:</p>
<ul>
<li><strong>Flags no formato <code>--chave=valor</code>.</strong> A tabela de opções nova parou de aceitar esse formato, que os scripts dos launchers usavam. A 0.3.2 trouxe de volta, com teste.</li>
<li><strong>Erros invisíveis no Wine.</strong> Os scripts de Linux, o AppImage e o Proton mandam a saída pra um log, então o SDK achava que tinha terminal e não mostrava o erro pro jogador. A 0.3.2 criou um relatório que decide quem precisa ver a mensagem.</li>
<li><strong>DLL reescrita a cada partida.</strong> O Sega Rally reescrevia uns 70 MB de DLLs em toda execução. O SDK passou a pular arquivos idênticos.</li>
<li><strong>O Wine ignora <code>WINEDLLOVERRIDES</code>.</strong> O teste no jogo real do Sega Rally mostrou que o Wine carregava a DLL embutida dele no lugar da DLL da Microsoft que estava na cópia privada. A 0.3.3 passou a escrever os overrides no registro do Wine, por programa, e cinco launchers adotaram.</li>
</ul>
<p>E a migração também achou bugs específicos de cada launcher, que só apareceram quando todos foram forçados pro mesmo padrão:</p>
<ul>
<li>o linker do GNU binutils 2.47 deixava importações do kernel32 fora da tabela; no Screamer eram 53, e o launcher crashava antes do <code>main</code>. Virou regra do SDK linkar com o lld e checar as importações na CI;</li>
<li>no Colin McRae 3, desmarcar uma opção de imagem apagava a escolha que o jogador tinha feito no menu do jogo;</li>
<li>no Colin McRae 04, abrir o launcher de novo encontrava a janela do próprio jogo, ou até uma pasta do Explorer com o mesmo nome, e saía em silêncio;</li>
<li>no Sega Rally, o menu em polonês aparecia em inglês, e os saves vazios que o launcher criava faziam o jogo acusar dados corrompidos.</li>
</ul>
<p>Teve até bug gerado pelo próprio processo. O snapshot de teste do <code>--help</code> incluía o número da versão, então o commit de release mudava a versão e quebrava o build da tag. As releases 0.2.0 do Colin McRae 3, 0.3.0 do 04 e 0.2.0 do OutRun foram marcadas e nunca publicadas, e saíram corrigidas minutos depois.</p>
<p>No fim, os sete launchers estavam no SDK, todos verdes, com releases novas. Além de compartilharem a mesma camada, ficaram mais corretos do que eram antes.</p>
<h2>As técnicas embutidas<span class="hx:absolute hx:-mt-20" id="as-técnicas-embutidas"></span>
    <a href="#as-t%c3%a9cnicas-embutidas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O SDK tem um documento de padrões com umas 540 linhas, e a maioria das regras veio de bug real que apareceu nos launchers. As principais:</p>
<h3>Teste antes da correção<span class="hx:absolute hx:-mt-20" id="teste-antes-da-correção"></span>
    <a href="#teste-antes-da-corre%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Toda correção de bug começa com um teste que falha no código antigo. Depois o agente reverte a correção temporariamente pra confirmar que o teste falha mesmo sem ela, porque vários &ldquo;testes de regressão&rdquo; passavam dos dois jeitos.</p>
<p>Outras regras de teste:</p>
<ul>
<li><strong>testar o comportamento</strong>: o bug do xdelta no Colin McRae 3 sobreviveu porque o teste unitário conferia a lista de argumentos errada. Onde existe ferramenta real, testa contra ela;</li>
<li><strong>testar a fiação inteira</strong>: opção → <code>launcher.cfg</code> → arquivo do jogo, registro e ambiente, de ponta a ponta;</li>
<li><strong>testes como história do jogador</strong>: &ldquo;o jogador restaura um backup antigo de save&rdquo;, &ldquo;a sessão é morta antes de copiar os saves de volta&rdquo;, &ldquo;abre o launcher de novo com o jogo rodando&rdquo;;</li>
<li><strong>entradas hostis</strong>: caminhos com espaço, apóstrofo e acento (<code>José/Jogos d'Ação</code>), arquivo em code page do Windows, CRLF, BOM, arquivo truncado;</li>
<li><strong>código de Windows roda com semântica de Windows</strong>: os testes do alvo Windows rodam dentro do Wine, sem tela. Quatro bugs do SDK só apareceram assim.</li>
</ul>
<h3>Snapshots dourados<span class="hx:absolute hx:-mt-20" id="snapshots-dourados"></span>
    <a href="#snapshots-dourados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Cada launcher guarda snapshots de tudo o que produz: o <code>--help</code>, o <code>launcher.cfg</code> gerado, e pra cada perfil visual os arquivos de configuração do jogo, os valores de registro, as variáveis de ambiente e os patches que ele aplicaria. Se uma atualização do SDK muda qualquer byte disso, o teste quebra, e a mudança é revisada como código.</p>
<h3>Sem tela por padrão<span class="hx:absolute hx:-mt-20" id="sem-tela-por-padrão"></span>
    <a href="#sem-tela-por-padr%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Testes, sondas e até o jogo real rodam <strong>headless</strong>: o gamescope sem saída de vídeo, um sink de áudio nulo do PipeWire e um prefixo Wine descartável. A evidência vem de log, código de saída, tabela de processos, arquivos escritos e screenshots capturados sem monitor.</p>
<p>Uma janela só abre quando precisa de um humano, como julgar a aparência da interface ou testar um controle. E o agente avisa antes: o que vai abrir, por quê e o que olhar.</p>
<h3>CI que pega o que o humano esquece<span class="hx:absolute hx:-mt-20" id="ci-que-pega-o-que-o-humano-esquece"></span>
    <a href="#ci-que-pega-o-que-o-humano-esquece" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A CI de cada launcher roda formatação, lint, os testes do alvo Windows dentro do Wine, a checagem de importações, auditoria de dependências e o empacotamento. A release só sai com a CI verde, gera <code>SHA256SUMS</code> e atestado de proveniência, e as notas vêm direto do CHANGELOG.</p>
<h2>Código de IA não precisa ser slop<span class="hx:absolute hx:-mt-20" id="código-de-ia-não-precisa-ser-slop"></span>
    <a href="#c%c3%b3digo-de-ia-n%c3%a3o-precisa-ser-slop" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem muita gente que olha código gerado por IA e assume que é &ldquo;AI slop&rdquo;: funciona no exemplo, quebra no resto, e ninguém sabe por quê. Isso acontece quando a pessoa pede código e aceita o que vier.</p>
<p>Com as técnicas certas, acontece o contrário. Eu diria que o código que eu gero com IA hoje é muito superior ao do desenvolvedor humano médio, que ainda briga contra escrever teste direito por preguiça.</p>
<p>Eu medi. Rodei a suíte de testes e a cobertura de linhas de cada projeto, no Linux:</p>
<table>
  <thead>
      <tr>
          <th>Projeto</th>
          <th style="text-align: right">Testes</th>
          <th style="text-align: right">Cobertura de linhas</th>
          <th style="text-align: right">Snapshots</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>rea-launcher-sdk</td>
          <td style="text-align: right">310</td>
          <td style="text-align: right">95%</td>
          <td style="text-align: right"></td>
      </tr>
      <tr>
          <td>Colin McRae Rally</td>
          <td style="text-align: right">171</td>
          <td style="text-align: right">89%</td>
          <td style="text-align: right">8</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 3</td>
          <td style="text-align: right">97</td>
          <td style="text-align: right">96%</td>
          <td style="text-align: right">7</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 04</td>
          <td style="text-align: right">106</td>
          <td style="text-align: right">98%</td>
          <td style="text-align: right">8</td>
      </tr>
      <tr>
          <td>Colin McRae: DiRT</td>
          <td style="text-align: right">74</td>
          <td style="text-align: right">97%</td>
          <td style="text-align: right">8</td>
      </tr>
      <tr>
          <td>OutRun 2006</td>
          <td style="text-align: right">94</td>
          <td style="text-align: right">92%</td>
          <td style="text-align: right">11</td>
      </tr>
      <tr>
          <td>Sega Rally Revo</td>
          <td style="text-align: right">126</td>
          <td style="text-align: right">96%</td>
          <td style="text-align: right">8</td>
      </tr>
      <tr>
          <td>Trilogia Screamer</td>
          <td style="text-align: right">89</td>
          <td style="text-align: right">71%</td>
          <td style="text-align: right">10</td>
      </tr>
      <tr>
          <td><a href="https://github.com/akitaonrails/rea-colin-mcrae-2005"target="_blank" rel="noopener">Colin McRae Rally 2005</a></td>
          <td style="text-align: right">71</td>
          <td style="text-align: right">96%</td>
          <td style="text-align: right">10</td>
      </tr>
      <tr>
          <td><a href="https://github.com/akitaonrails/rea-dirt-rally"target="_blank" rel="noopener">DiRT Rally</a></td>
          <td style="text-align: right">85</td>
          <td style="text-align: right">85%</td>
          <td style="text-align: right">13</td>
      </tr>
  </tbody>
</table>
<p>São mais de 1.200 testes, todos passando, e nove dos dez projetos com 85% ou mais de cobertura. Pra cada duas linhas de código de produção, tem mais ou menos uma de teste.</p>
<p>A cobertura é só do que compila no Linux. O código exclusivo de Windows, como a DLL de hooks do Colin McRae 1, fica de fora da conta, e é testado na CI rodando os testes do alvo Windows dentro do Wine. O Screamer é o mais baixo: a configuração, a cópia privada e os hooks dentro do emulador ficam abaixo de 75%.</p>
<p>Os números sozinhos não contam a história. O que conta é o tipo de bug que esses testes pegaram.</p>
<h3>O teste que passava errado<span class="hx:absolute hx:-mt-20" id="o-teste-que-passava-errado"></span>
    <a href="#o-teste-que-passava-errado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No Colin McRae 3, a conversão do executável pra versão sem DRM <strong>nunca acontecia</strong>, em silêncio. O launcher montava os argumentos do xdelta repetindo o caminho do programa, e o xdelta recebia <code>xdelta.exe xdelta.exe -f -d -s …</code>.</p>
<p>O teste unitário passava, porque conferia exatamente essa lista errada. A auditoria do SDK rodou o xdelta3 de verdade e tomou &ldquo;too many filenames&rdquo;. A correção veio com um teste novo que gera um patch minúsculo com o xdelta3 real, aplica com os argumentos do launcher e compara os bytes.</p>
<p>Daí saiu uma das regras do SDK: testar o comportamento, com a ferramenta real sempre que ela existir.</p>
<h3>A correção &ldquo;segura&rdquo; que perdia saves<span class="hx:absolute hx:-mt-20" id="a-correção-segura-que-perdia-saves"></span>
    <a href="#a-corre%c3%a7%c3%a3o-segura-que-perdia-saves" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No Colin McRae 3, depois de um crash, o launcher restaurava o save antigo por cima do novo. A correção foi &ldquo;o mais novo ganha&rdquo;, e a auditoria achou tão boa que levou pro OutRun e pro Screamer na mesma manhã.</p>
<p>Só que &ldquo;o mais novo ganha&rdquo; joga fora o backup antigo que o jogador restaurou de propósito, e os saves trazidos de outro PC com data velha. Em 20 minutos, os três launchers trocaram a regra por um registro do estado da última sincronização. Os testes têm nome de história: <code>a_restored_older_backup_wins</code>, <code>a_session_killed_before_saving_is_recovered</code>.</p>
<p>Um revisor independente pegou o problema, e foi por causa dele que o SDK passou a exigir testes escritos como histórias do jogador.</p>
<h3>O checklist que marcou FAILED<span class="hx:absolute hx:-mt-20" id="o-checklist-que-marcou-failed"></span>
    <a href="#o-checklist-que-marcou-failed" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No Sega Rally, a verificação no jogo real tinha um item: &ldquo;o <code>WINEDLLOVERRIDES</code> chega no jogo&rdquo;. Ele ficou marcado como <strong>FAILED</strong>. O launcher registrava os overrides no log, mas com <code>WINEDEBUG=+loaddll</code> dava pra ver o jogo carregando a DLL embutida do Wine.</p>
<p>O <code>WINEDEBUG=+module</code> mostrou duas causas. O Wine troca pela versão dele qualquer DLL que se identifique como da Microsoft no recurso de versão, mesmo na pasta do programa, e ignora o <code>WINEDLLOVERRIDES</code> colocado só no ambiente do processo filho. A correção foi pro SDK, com um teste que prova o bug (<code>env_block_overrides_do_not_reach_wine</code>), e cinco launchers adotaram. Sem a regra de verificar no jogo real, ninguém teria notado: o log dizia que estava tudo certo.</p>
<h3>Duas correções erradas antes da certa<span class="hx:absolute hx:-mt-20" id="duas-correções-erradas-antes-da-certa"></span>
    <a href="#duas-corre%c3%a7%c3%b5es-erradas-antes-da-certa" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No Screamer, chegar na tela de título não provava nada. Uma execução headless trocando a prévia das pistas reproduziu o crash real: <code>mipmap cannot span 2 Mbyte boundary</code>.</p>
<p>A primeira correção tratou um caminho que nunca acontecia. A segunda causou o mesmo erro sozinha e foi revertida. A terceira, depois de ler a desmontagem, achou um <code>jle</code> com sinal que deixava passar uma textura terminando exatamente no limite, mais dois caminhos que pulavam a checagem. A correção final troca <strong>um byte</strong> (<code>jle</code> vira <code>jl</code>) e anula os dois desvios, com um teste que confere os três pontos.</p>
<blockquote>
  <p>Errar de primeira é normal, pra humano e pra IA. O que muda é que o agente só faz commit quando o jogo confirma, e o teste garante que o erro não volta.</p>

</blockquote>
<h2>Agentes que jogam o jogo<span class="hx:absolute hx:-mt-20" id="agentes-que-jogam-o-jogo"></span>
    <a href="#agentes-que-jogam-o-jogo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que mais acelerou tudo isso é que os agentes têm acesso pra <strong>rodar o jogo de verdade</strong>. Eles aplicam o patch, abrem o jogo num gamescope headless, conferem o resultado no log e na captura de tela, e só fazem commit quando tudo está confirmado funcionando.</p>
<p>Isso economiza horas, às vezes dias, de teste manual. Eu não preciso abrir o jogo a cada tentativa de patch, só nos pontos em que o meu olho ou a minha mão no controle fazem diferença.</p>
<p>E quando o jogo crasha, fica um dump. O agente abre no <code>gdb</code> ou no <code>winedbg</code>, acha a instrução que falhou e chega na causa com uma precisão que eu raramente tinha quando fazia isso na mão, anos atrás. Três exemplos:</p>
<ul>
<li><strong>OutRun 2006</strong>: o launcher crashava logo depois de copiar os saves. O <code>winedbg</code> com símbolos colocou a falha numa chamada a <code>GetModuleHandleW</code> cujo slot na tabela de importação não pertencia a nenhum descritor, então o loader nunca preencheu. A causa era o linker do binutils 2.47 do meu toolchain, e a correção foi trocar pro lld.</li>
<li><strong>Colin McRae Rally 2005</strong>: o jogo crashava na abertura no Wine, num endereço dentro da rotina que mede a largura do texto. Ela lia o cabeçalho da fonte antes de checar se a fonte tinha carregado, e a fonte nunca carregava porque o jogo monta os caminhos a partir de chaves de registro que só o instalador escreve. Existia um <a href="https://bugs.winehq.org/show_bug.cgi?id=38198"target="_blank" rel="noopener">bug no Wine</a> com o mesmo crash, que sumiu reinstalando o jogo e acabou fechado sem explicação.</li>
<li><strong>DiRT Rally</strong>: o port de Linux de 2017 crashava logo depois de iniciar o SDL em processadores novos. O jogo mapeia memória só com permissão de execução e depois <em>lê</em> dela. Em CPUs com Memory Protection Keys (Ryzen com Zen 3 ou mais novo, Intel a partir da 11ª geração), o kernel torna essa memória realmente só de execução, e a leitura falha. O problema já era conhecido na comunidade, e a correção do launcher é uma bibliotequinha própria em Rust que intercepta o <code>mmap</code> e o <code>mprotect</code> e adiciona permissão de leitura, sem mexer no jogo.</li>
</ul>
<h2>Testando o SDK com dois jogos novos<span class="hx:absolute hx:-mt-20" id="testando-o-sdk-com-dois-jogos-novos"></span>
    <a href="#testando-o-sdk-com-dois-jogos-novos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com tudo verde, chegou a hora de testar o SDK de verdade: dois launchers novos, do zero, o do <strong>Colin McRae Rally 2005</strong> e o do <strong>DiRT Rally</strong>. Dessa vez eu só precisei dizer <em>&ldquo;use o rea-launcher-sdk e crie um launcher novo pro jogo X&rdquo;</em>.</p>
<p>O agente seguiu o playbook: pesquisa primeiro, depois o scaffold gerado pelo script <code>new-launcher</code> do SDK (66 arquivos e quase 11 mil linhas de partida), depois a implementação em passos, a verificação no jogo real e a release.</p>
<p>Claro que cada jogo teve as suas peculiaridades, e precisou de teste manual, prompt extra e intuição minha.</p>
<h3>Colin McRae Rally 2005<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-2005"></span>
    <a href="#colin-mcrae-rally-2005" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/colin-mcrae-rally-2005/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/colin-mcrae-rally-2005/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li><strong>o crash da abertura</strong>, explicado acima, resolvido escrevendo as chaves de registro a cada início;</li>
<li><strong>o instalador da GOG trava no Wine</strong>, então o launcher desempacota os arquivos do instalador com o innoextract e nunca roda o instalador;</li>
<li><strong>com o renderizador do Wine, uma etapa rodava a 20 fps</strong>; com o DXVK, passou de mil;</li>
<li><strong>um monitor principal em retrato</strong> fazia o jogo parar com &ldquo;Adapter has no modes!&rdquo;, e a chave de desktop virtual por programa do Wine é ignorada no Wine 11. A solução foi abrir o jogo num desktop virtual do tamanho do monitor em paisagem;</li>
<li><strong>num prefixo Wine novo, a janela do launcher não abria</strong>, porque o Wine só lê a configuração de EGL quando o processo começa. O launcher agora troca pra GLX e reinicia a si mesmo.</li>
</ul>
<p>Os dois últimos só apareceram quando eu joguei no meu desktop, com o meu monitor em retrato e o meu Hyprland. Nenhum teste headless pegaria isso.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-launcher-sdk/cmr2005-gameplay.jpg" alt="Colin McRae Rally 2005 numa etapa na Austrália, em 1920x1080 com a visão widescreen"  loading="lazy" /></p>
<h3>DiRT Rally<span class="hx:absolute hx:-mt-20" id="dirt-rally"></span>
    <a href="#dirt-rally" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/dirt-rally-launcher/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/dirt-rally-launcher/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li><strong>o crash de processador novo</strong>, explicado acima;</li>
<li><strong>as bibliotecas antigas</strong> que o Linux de hoje não tem mais (OpenSSL 1.0, libidn11, librtmp, OpenLDAP 2.4, GConf) vêm de pacotes fixos do Steam Runtime, e só o jogo enxerga, porque vazar elas pro gamescope derruba o gamescope;</li>
<li><strong>a janela de pré-jogo da Feral</strong>: as flags óbvias de preferência não funcionaram. O agente decompilou a decisão com o REA e achou as duas chaves certas. Pular a janela sem escrever a resolução derrubava o jogo, então o launcher escreve as duas coisas;</li>
<li><strong>os controles levaram três tentativas.</strong> O meu 8BitDo Ultimate 2 aparecia como joystick genérico. A primeira correção, via variável do SDL, fez o controle <em>sumir</em>. A causa real era uma lista própria de controles da Feral, dentro do jogo, e o launcher agora adiciona os controles de layout padrão nela.</li>
</ul>
<p>O DiRT Rally também expôs a maior premissa escondida do SDK: ele foi feito pra executável de Windows rodando no Wine. O DiRT Rally é um binário nativo de Linux, então faltava janela de erro nativa, trava de instância única no Unix, progresso na cópia de 40 GB e leitura de controle no Linux pra janela do launcher. O agente escreveu tudo isso no próprio launcher primeiro, e depois isso subiu pro SDK.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-launcher-sdk/dirt-rally-launcher-copy.jpg" alt="Primeiro Play do DiRT Rally: a cópia privada sendo feita, 2,3 de 43,1 GB"  loading="lazy" /></p>
<h3>Os números<span class="hx:absolute hx:-mt-20" id="os-números"></span>
    <a href="#os-n%c3%bameros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Comparando os commits e as horas de sessão (somando os intervalos entre commits) até cada launcher chegar no padrão do SDK:</p>
<table>
  <thead>
      <tr>
          <th>Launcher</th>
          <th style="text-align: right">Commits</th>
          <th style="text-align: right">Horas em sessões de commit</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Colin McRae Rally (o primeiro)</td>
          <td style="text-align: right">789</td>
          <td style="text-align: right">~110 h</td>
      </tr>
      <tr>
          <td>Trilogia Screamer</td>
          <td style="text-align: right">87</td>
          <td style="text-align: right">~17,5 h</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 3</td>
          <td style="text-align: right">89</td>
          <td style="text-align: right">~11,5 h</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 04</td>
          <td style="text-align: right">70</td>
          <td style="text-align: right">~10 h</td>
      </tr>
      <tr>
          <td>OutRun 2006</td>
          <td style="text-align: right">88</td>
          <td style="text-align: right">~9 h</td>
      </tr>
      <tr>
          <td>Sega Rally Revo</td>
          <td style="text-align: right">77</td>
          <td style="text-align: right">~8 h</td>
      </tr>
      <tr>
          <td>Colin McRae: DiRT</td>
          <td style="text-align: right">27</td>
          <td style="text-align: right">~3,5 h</td>
      </tr>
      <tr>
          <td><strong>Colin McRae Rally 2005</strong></td>
          <td style="text-align: right"><strong>15</strong></td>
          <td style="text-align: right"><strong>~4 h</strong></td>
      </tr>
      <tr>
          <td><strong>DiRT Rally</strong></td>
          <td style="text-align: right"><strong>18</strong></td>
          <td style="text-align: right"><strong>~4 h</strong></td>
      </tr>
  </tbody>
</table>
<p>Os números dos antigos incluem a primeira versão, as correções e a migração pro SDK, e a migração sozinha foi uns 35 a 50 commits de cada um. O DiRT tem pouco commit porque quase todo o trabalho dele foi feito antes do primeiro commit.</p>
<p>O Colin McRae 2005 já nasceu com tudo: testes, snapshots, CI, README no padrão, formulários de issue, releases com hash. O DiRT Rally saiu sem os snapshots dourados, ganhou logo depois da release, e essa falha virou regra no SDK (mais abaixo). Os números de teste e cobertura dele na tabela anterior são de depois da release.</p>
<blockquote>
  <p>A maioria dos launchers antigos precisou de 70 a 90 commits e de 8 a 18 horas pra chegar no padrão. Os dois novos chegaram lá na primeira release, ou quase, com menos de 20 commits e umas 4 horas cada, feitos em paralelo na mesma tarde e noite.</p>

</blockquote>
<p>Menos commit significa menos ida e volta. O agente acertou mais de primeira porque o SDK já tinha resolvido o que os outros launchers precisaram descobrir apanhando.</p>
<h2>O SDK aprende com os launchers novos<span class="hx:absolute hx:-mt-20" id="o-sdk-aprende-com-os-launchers-novos"></span>
    <a href="#o-sdk-aprende-com-os-launchers-novos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois que os dois saíram, eu voltei pro agente do SDK e pedi pra ele avaliar os dois: ajustar o que precisasse neles, e aprender o que desse pra trazer pro framework. É a mesma regra de antes: só vira framework o que já apareceu em projeto real.</p>
<p>O resultado foi a <a href="https://github.com/akitaonrails/rea-launcher-sdk/releases/tag/v0.4.0"target="_blank" rel="noopener">versão 0.4.0</a>. O que ela trouxe:</p>
<ul>
<li>do DiRT Rally: trava de instância única pra launcher nativo de Linux, janela de erro nativa, cópia com progresso, leitura de controle no Linux;</li>
<li>do Colin McRae 2005: a troca pra GLX com reinício num prefixo novo, paletas claras com contraste certo, e um modo novo no hook de fechar pra jogos que tomam o teclado em modo exclusivo do DirectInput, onde o Alt+F4 nunca chega;</li>
<li>de vários: o tamanho do gamescope por perfil visual, que o Colin McRae 1, o 2005, o 04 e o OutRun tinham reimplementado cada um no seu hook;</li>
<li>do bug das releases: um helper de snapshot que nunca grava o número da versão.</li>
</ul>
<p>E o que veio dos dois launchers novos já vai beneficiar os antigos também. O OutRun, o Colin McRae 1 e o 04, por exemplo, vão poder apagar o código próprio que dimensionava o gamescope e usar o do SDK, e vários ganham a janela de erro certa, a segunda execução tratada do mesmo jeito e a remoção segura dos overrides de DLL. É o ciclo fechando: o projeto novo ensina o framework, e o framework melhora os projetos velhos.</p>
<p>E veio uma ideia nova: um <strong>verificador de conformidade</strong>, o <code>launcher-check</code>, que roda na CI de cada launcher e falha o build quando um launcher repete um erro conhecido. São 33 regras, e cada uma explica por que existe e como corrigir. Um erro que apareceu em um launcher e que dá pra detectar olhando os arquivos vira regra. Por exemplo, &ldquo;o DiRT Rally saiu sem snapshots&rdquo; virou uma.</p>
<p>O playbook de launcher novo também ganhou seções de padrões: port nativo de Linux, os ports antigos da Feral, instalador da GOG com innoextract, monitor em retrato com desktop virtual.</p>
<p>Nenhum software está &ldquo;pronto&rdquo;. Mas eu acredito que o SDK já é bom o suficiente: foi testado em batalha com nove launchers e onze jogos de verdade, e qualquer pessoa pode começar a usar.</p>
<h2>Feito pra ser usado por IA<span class="hx:absolute hx:-mt-20" id="feito-pra-ser-usado-por-ia"></span>
    <a href="#feito-pra-ser-usado-por-ia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O SDK foi desenhado <strong>AI-first</strong>. O README diz isso logo no começo: ele foi escrito pra ser dirigido por um agente de código. O agente faz a pesquisa, a engenharia reversa e o código; você entra com o jogo e com as decisões.</p>
<p>E não precisa ser o Claude. A estrutura funciona com o agente que você preferir, seja Claude Code, Codex, opencode ou Cursor:</p>
<ul>
<li><strong><code>AGENTS.md</code></strong> é a porta de entrada pra qualquer agente: uma tabela de &ldquo;tarefa → playbook&rdquo; e as regras fáceis de esquecer. O <code>CLAUDE.md</code> só aponta pra ele;</li>
<li><strong>os playbooks</strong> são Markdown puro: launcher novo, patch binário, componentes da comunidade, CI e releases, mudanças no SDK, contribuir de volta, e o modelo de README;</li>
<li><strong><code>docs/STANDARDS.md</code></strong> são as regras obrigatórias, e cada launcher recebe uma cópia delas;</li>
<li><strong>as skills do Claude</strong> (<code>rea-new-launcher</code>, <code>rea-sdk-change</code>, <code>rea-contribute-back</code>) são só ponteiros finos pros playbooks, pra quem usa Claude Code.</li>
</ul>
<p>Algumas regras que o agente segue sem você precisar lembrar:</p>
<ul>
<li>nunca distribuir arquivos do jogo, e nunca modificar a pasta do jogo;</li>
<li>identificar cada executável pelo SHA-256 e <strong>nunca</strong> patchear uma versão desconhecida no chute;</li>
<li>pesquisar os fixes da comunidade antes de escrever código;</li>
<li>registrar as decisões do dono do projeto, com data;</li>
<li>não publicar nada, nem push, nem release, nem post em fórum, sem aprovação.</li>
</ul>
<h2>Como fazer o launcher do seu jogo favorito<span class="hx:absolute hx:-mt-20" id="como-fazer-o-launcher-do-seu-jogo-favorito"></span>
    <a href="#como-fazer-o-launcher-do-seu-jogo-favorito" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você tem um jogo antigo que não roda mais direito, o caminho é este.</p>
<p><strong>1. Prepare as ferramentas.</strong> Clone o SDK e instale o REA no seu agente:</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/akitaonrails/rea-launcher-sdk ~/Projects/rea-launcher-sdk
</span></span><span class="line"><span class="cl">npx rea-agents setup                                  <span class="c1"># REA, o MCP de engenharia reversa</span>
</span></span><span class="line"><span class="cl">~/Projects/rea-launcher-sdk/scripts/install-skills    <span class="c1"># só pra Claude Code</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>Você também precisa do toolchain: Rust com os alvos de Windows, MinGW, lld, Wine, gamescope e mais algumas ferramentas que o README lista. O jeito mais fácil é usar um container que já tenha tudo, como o meu <a href="https://github.com/akitaonrails/distrobox-gaming"target="_blank" rel="noopener">distrobox-gaming</a>.</p>
<p><strong>2. Tenha o jogo instalado.</strong> A sua própria cópia, numa pasta que o agente possa ler. Ela nunca é modificada, nem copiada pro repositório.</p>
<p><strong>3. Peça pro agente.</strong> Abra o agente numa pasta vazia e cole um prompt como este, adaptado do README:</p>
<blockquote>
  <p>Crie um novo launcher rea para <em>Need for Speed: Porsche Unleashed</em> (PC, 2000) usando o SDK em <code>~/Projects/rea-launcher-sdk</code>. Leia o <code>AGENTS.md</code>, o <code>docs/STANDARDS.md</code> e o <code>docs/playbooks/new-launcher.md</code> e siga o playbook fase por fase. O jogo está instalado em <code>/mnt/games/NFSPU</code> (só leitura, nunca copie pro repo). Use as ferramentas MCP do REA pra analisar. Pesquise os fixes da comunidade primeiro e me mostre o que encontrou antes de escrever código. Mantenha os testes headless. Não faça push nem publique nada sem me perguntar.</p>

</blockquote>
<p>Com o Claude Code e as skills instaladas, <em>&ldquo;faça um launcher rea pro jogo X em /caminho&rdquo;</em> já basta.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-launcher-sdk/cmr2005-launcher-picture.jpg" alt="Aba de imagem do launcher do Colin McRae Rally 2005, gerada a partir da tabela de opções"  loading="lazy" /></p>
<p><strong>4. Revise nos pontos de checagem.</strong> O playbook para em quatro momentos pra você decidir:</p>
<ol>
<li>o que a pesquisa encontrou;</li>
<li>a lista de problemas que o launcher vai resolver;</li>
<li>as opções que a janela vai oferecer;</li>
<li>o que foi verificado no jogo real, registrado no <code>docs/RUNTIME_NOTES.md</code>.</li>
</ol>
<p>Entre eles, o agente segue as fases: conhecer o jogo com o REA, gerar o scaffold, construir em ordem (edição do jogo, cópia privada, componentes, configuração, patches, perfis visuais, janela, linha de comando, script de Linux, empacotamento), e verificar com os testes e com o jogo real rodando headless.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-launcher-sdk/dirt-rally-launcher-setup.jpg" alt="Launcher do DiRT Rally ainda sem a pasta do jogo: o Play espera"  loading="lazy" /></p>
<p><strong>5. Jogue.</strong> Essa parte é sua. É jogando no seu PC, com o seu monitor e o seu controle, que aparecem os bugs que nenhum teste pega.</p>
<p><strong>6. Publique.</strong> Quando você aprovar, o agente faz o push, e a release sai pela CI com o AppImage pra Linux, o zip pra Windows e os hashes. O playbook ainda tem uma fase de contribuir de volta: rascunhos pro PCGamingWiki e pros projetos da comunidade, que só são postados com a sua aprovação.</p>
<h2>Ferramenta boa nasce de uso real<span class="hx:absolute hx:-mt-20" id="ferramenta-boa-nasce-de-uso-real"></span>
    <a href="#ferramenta-boa-nasce-de-uso-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O rea-launcher-sdk saiu do zero pra versão 0.3.3 em umas quatro horas e meia, mas isso só foi possível porque antes existiam sete launchers funcionando, com quase mil commits somados. O framework é a parte fácil quando você já tem os projetos.</p>
<blockquote>
  <p>Não comece pelo framework. Faça o mesmo tipo de projeto várias vezes, deixe as cópias divergirem, e só depois extraia. Com agentes, esse ciclo que levaria meses cabe em poucos dias.</p>

</blockquote>
<p>Se o seu jogo favorito está preso no passado, o SDK está aberto. Aponte o seu agente pra ele, aponte pra pasta do jogo, e solte o seu próprio launcher.</p>
]]></content:encoded><category>agentes-de-codigo</category><category>engenharia-de-software</category><category>arquitetura-de-software</category><category>games</category></item><item><title>Consertei vários jogos de corrida antigos usando IA: Colin McRae 1, 3, 4, Sega Rally Revo e Outrun 2006, até a trilogia Screamer</title><link>https://akitaonrails.github.io/2026/11/02/consertei-varios-jogos-de-corrida-antigos-usando-ia/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/11/02/consertei-varios-jogos-de-corrida-antigos-usando-ia/</guid><pubDate>Mon, 02 Nov 2026 09:00:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; fiz launchers que instalam e rodam Colin McRae Rally 1, 3, 04, 2005 e DiRT, DiRT Rally, OutRun 2006, Sega Rally Revo e a trilogia Screamer em PC moderno, com 4K, widescreen e controle de Xbox, no Linux e no Windows. Se você só quer baixar e jogar, pule direto pra &lt;a href="#os-jogos"&gt;lista dos jogos&lt;/a&gt;, com os links das releases, vídeos e instruções.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No &lt;a href="https://akitaonrails.github.io/2026/10/29/brincando-de-pc-ports-com-colin-mcrae-1/"&gt;artigo anterior&lt;/a&gt; eu mostrei os meus dois projetos em cima do Colin McRae Rally de 1998: o &lt;strong&gt;recomp&lt;/strong&gt; em Rust, que tenta reconstruir o jogo do zero, e o &lt;strong&gt;launcher&lt;/strong&gt;, uma camada fina em volta do executável original que faz ele rodar em 4K, widescreen e 60 fps num PC moderno.&lt;/p&gt;</description><content:encoded><![CDATA[<blockquote>
  <p><strong>TL;DR:</strong> fiz launchers que instalam e rodam Colin McRae Rally 1, 3, 04, 2005 e DiRT, DiRT Rally, OutRun 2006, Sega Rally Revo e a trilogia Screamer em PC moderno, com 4K, widescreen e controle de Xbox, no Linux e no Windows. Se você só quer baixar e jogar, pule direto pra <a href="#os-jogos">lista dos jogos</a>, com os links das releases, vídeos e instruções.</p>

</blockquote>
<p>No <a href="/2026/10/29/brincando-de-pc-ports-com-colin-mcrae-1/">artigo anterior</a> eu mostrei os meus dois projetos em cima do Colin McRae Rally de 1998: o <strong>recomp</strong> em Rust, que tenta reconstruir o jogo do zero, e o <strong>launcher</strong>, uma camada fina em volta do executável original que faz ele rodar em 4K, widescreen e 60 fps num PC moderno.</p>
<p>O resultado do launcher me agradou muito, e o mais interessante foi perceber como a receita era fácil de repetir. Então resolvi trazer mais abandonware de volta pra era moderna. Entre a noite de 31 de outubro e a noite de 2 de novembro saíram mais oito launchers, cobrindo dez jogos de corrida:</p>
<ul>
<li><strong>Colin McRae Rally 3</strong> (2003)</li>
<li><strong>Colin McRae Rally 04</strong> (2004 no PC)</li>
<li><strong>Colin McRae Rally 2005</strong> (2004)</li>
<li><strong>Colin McRae: DiRT</strong> (2007)</li>
<li><strong>DiRT Rally</strong> (2015), no port nativo de Linux de 2017</li>
<li><strong>OutRun 2006: Coast 2 Coast</strong> (2006)</li>
<li><strong>Sega Rally Revo</strong> (2007)</li>
<li>a trilogia <strong>Screamer</strong> (1995 a 1997), da era do MS-DOS</li>
</ul>
<p>Os seis primeiros substituíram as instalações manuais que eu mantinha no meu projeto <a href="/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/">distrobox-gaming</a>. Por enquanto o objetivo foi só um: instalação fácil e reproduzível, com o jogo rodando bem.</p>
<p>Os dois últimos, o Colin McRae Rally 2005 e o DiRT Rally, saíram na noite de 2 de novembro, depois da primeira versão deste artigo, já criados direto do SDK que eu explico no final.</p>
<h2>O problema do abandonware<span class="hx:absolute hx:-mt-20" id="o-problema-do-abandonware"></span>
    <a href="#o-problema-do-abandonware" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Existem centenas de jogos em estado de abandonware: não tem nenhuma forma oficial e suportada de jogar hoje. Eles não estão na Steam, não estão nem na GOG. E mesmo alguns que estão na GOG só rodam direito depois de muito ajuste manual. A trilogia Screamer é um exemplo: a GOG vende o Screamer e o Screamer 2 (o Screamer Rally não está lá), e a versão dela vem com um stub de Glide pra Windows que simplesmente não funciona no DOS.</p>
<p>O motivo é que a compatibilidade quebrou em camadas.</p>
<p>Jogos de MS-DOS precisam de emulador. Jogos de Windows 95/98 falam com DirectDraw e Direct3D 5, APIs que o Windows moderno mantém só por compatibilidade, com bugs e limitações que a Microsoft não prioriza mais. Jogos de Windows XP de 32 bits dependem de proteções de disco como o SafeDisc e versões antigas do SecuROM, que a Microsoft <a href="https://www.pcworld.com/article/2985806/windows-vista-7-8-update-prevents-some-old-cd-rom-games-from-running.html"target="_blank" rel="noopener">bloqueou em 2015</a> por questão de segurança. No Windows 10 e 11, a maioria desses jogos não sobe sem configuração manual, e no Linux a lista de ajustes é ainda maior.</p>
<h3>Dois exemplos do que era preciso fazer<span class="hx:absolute hx:-mt-20" id="dois-exemplos-do-que-era-preciso-fazer"></span>
    <a href="#dois-exemplos-do-que-era-preciso-fazer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O <strong>Colin McRae: DiRT</strong> de 2007 era assim no meu setup antigo:</p>
<ol>
<li>Rodar o instalador InstallShield de 2007 de forma interativa, porque ele não tem modo silencioso confiável.</li>
<li>Aplicar a cadeia de patches oficiais na ordem: 1.1, 1.2, 1.21 e 1.22, cada um com seu assistente.</li>
<li>Descobrir que o jogo abre os menus, mas <strong>trava ao carregar qualquer corrida</strong> num processador com 32 threads. O motor EGO monta mapas de distribuição de trabalho por número de núcleos, e os mapas originais não sabem lidar com tanta thread.</li>
<li>Aplicar um fix da comunidade, o MTFix, que corrige isso mas apaga o cache de configuração e reseta todas as opções gráficas.</li>
<li>Lembrar que, depois de mudar a resolução, o botão &ldquo;reiniciar agora&rdquo; do jogo só fecha o jogo.</li>
</ol>
<p>O <strong>Sega Rally Revo</strong> de 2007 tinha outra lista:</p>
<ul>
<li>um microstutter permanente em toda corrida, causado por dois bugs de tempo empilhados que o patch 4.060 introduziu;</li>
<li>uma chave de registro obrigatória, sem a qual o jogo não abre;</li>
<li>um configurador em .NET que não funciona direito no Wine;</li>
<li>vídeos de logo que faziam a inicialização parecer travada;</li>
<li>tela cheia exclusiva falhando em monitor de 240 Hz, e tela preta em PC com duas GPUs, a integrada e a dedicada.</li>
</ul>
<blockquote>
  <p>A maioria das pessoas simplesmente não vai fazer nada disso. Elas desistem do jogo, e o jogo morre um pouco mais.</p>

</blockquote>
<h2>Decomp, recomp e por que eu fiz só launcher<span class="hx:absolute hx:-mt-20" id="decomp-recomp-e-por-que-eu-fiz-só-launcher"></span>
    <a href="#decomp-recomp-e-por-que-eu-fiz-s%c3%b3-launcher" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O hype do retrogaming hoje está nos <strong>decomps</strong> e <strong>recomps</strong>. Um decomp reconstrói, a partir do binário, um código-fonte equivalente ao original, que compila pro mesmo binário, como foi feito com o <a href="https://github.com/n64decomp/sm64"target="_blank" rel="noopener">Super Mario 64</a> e com o Ocarina of Time, que virou o <a href="https://github.com/HarbourMasters/Shipwright"target="_blank" rel="noopener">Ship of Harkinian</a>. Um recomp traduz o binário automaticamente pra C e compila isso como código nativo, como o <a href="https://github.com/N64Recomp/N64Recomp"target="_blank" rel="noopener">N64Recomp</a> faz com jogos de Nintendo 64, que <a href="https://tomshardware.com/video-games/nearly-all-nintendo-64-games-can-now-be-recompiled-into-native-pc-ports-to-add-proper-ray-tracing-ultrawide-high-fps-and-more"target="_blank" rel="noopener">gerou ports de PC com ultrawide e fps alto</a>.</p>
<p>A vantagem dos dois é enorme: você deixa de depender do binário original e consegue compilar o jogo pra qualquer plataforma, de ARM e Android a Mac. É o que eu tentei com o recomp do Colin McRae 1, e é trabalho de meses.</p>
<p>Pra mim, a coceira era outra. Eu queria que os jogos que eu gosto fossem fáceis de instalar e de manter no meu distrobox. Pra isso, um launcher basta.</p>
<h2>O conceito: religar o jogo nas bordas<span class="hx:absolute hx:-mt-20" id="o-conceito-religar-o-jogo-nas-bordas"></span>
    <a href="#o-conceito-religar-o-jogo-nas-bordas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todos os launchers seguem a mesma ideia. O jogo original continua igual, e o que muda são as bordas dele com o mundo, as partes que envelheceram:</p>
<ul>
<li><strong>gráfico</strong>: a API de 1998 ou de 2003 passa a conversar com uma API moderna;</li>
<li><strong>entrada</strong>: controle de Xbox, gatilho analógico e rumble no lugar de teclado e joystick de porta de jogos;</li>
<li><strong>áudio e arquivos</strong>: registro do Windows, arquivos de configuração, saves e caches escritos certo antes de o jogo abrir;</li>
<li><strong>tela</strong>: escolher o monitor certo, a resolução certa e o modo de janela que não quebra.</li>
</ul>
<p>Cada launcher identifica o executável, por hash ou por assinatura de bytes, recusa versões desconhecidas, e trabalha numa <strong>cópia privada</strong> do jogo. A pasta original nunca é modificada; qualquer patch vai só na cópia.</p>
<h3>As peças de compatibilidade<span class="hx:absolute hx:-mt-20" id="as-peças-de-compatibilidade"></span>
    <a href="#as-pe%c3%a7as-de-compatibilidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quase nada disso eu escrevi do zero. Os launchers orquestram ferramentas que a comunidade levou décadas construindo:</p>
<ul>
<li><strong>Wine</strong>: a camada que implementa a API do Windows no Linux. Os launchers dos jogos de Windows levam o próprio Wine 11.20 dentro de um AppImage.</li>
<li><strong>DXVK</strong>: traduz Direct3D 8, 9, 10 e 11 pra Vulkan. É o que deixa jogo de DirectX 9 rodar rápido no Linux.</li>
<li><strong>dgVoodoo 2</strong>: um wrapper do desenvolvedor Dege que pega APIs gráficas antigas (Glide da 3dfx, DirectDraw e Direct3D das primeiras versões até a 9) e traduz pra Direct3D 11 ou 12. É como fazer um jogo de 2003 achar que está numa placa da época enquanto roda em placa moderna.</li>
<li><strong>dosbox-staging</strong>: um emulador de DOS moderno, com emulação da placa 3dfx Voodoo.</li>
<li><strong>gamescope</strong>: o compositor da Valve que roda o jogo numa tela própria e faz o escalonamento pro monitor.</li>
<li><strong>patches e ferramentas da comunidade</strong>: SilentPatch pro Colin McRae 3, OutRun2006Tweaks pro OutRun, o fix de stutter do Sega Rally, e o Xidi, que apresenta controles XInput pros jogos que só leem DirectInput.</li>
</ul>
<p>E o <a href="/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/">REA</a>, a minha ferramenta de engenharia reversa, entra onde não tem ferramenta pronta: achar a função do jogo que calcula a projeção, o formato do arquivo de perfil do controle, o lugar certo pra um hook.</p>
<h2>Os que exigiram mais engenharia reversa<span class="hx:absolute hx:-mt-20" id="os-que-exigiram-mais-engenharia-reversa"></span>
    <a href="#os-que-exigiram-mais-engenharia-reversa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Colin McRae Rally 04: três camadas de tradução<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-04-três-camadas-de-tradução"></span>
    <a href="#colin-mcrae-rally-04-tr%c3%aas-camadas-de-tradu%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O CMR04 usa Direct3D 9, mas é hostil a widescreen: sem patch, o 16:9 não mostra mais cenário dos lados. E o menu gráfico do jogo só lista modos até 2048x1536, reescreve o registro e trava.</p>
<p>O pipeline final ficou com três camadas no Linux: o Direct3D 9 do jogo passa pelo <strong>dgVoodoo 2.80</strong>, que converte pra Direct3D 11, o <strong>DXVK</strong> converte o Direct3D 11 pra Vulkan, e o Wine roda tudo por baixo. No Windows, fica só o dgVoodoo. A versão mais nova do dgVoodoo crashava, e a 2.80 nem está mais disponível completa no site oficial, então o repositório guarda só a DLL testada.</p>
<p>Pro widescreen, a engenharia reversa achou a função de projeção e a matemática dela: o patch de 16:9 da comunidade só funciona se o jogo renderizar em 4:3 e a imagem for esticada depois. Renderizar direto em 16:9 com o patch deixava os carros 25% mais estreitos. Então o launcher renderiza em 4:3 na altura da tela e o dgVoodoo estica pra 16:9 com a geometria certa.</p>
<p>E teve detalhe de todo tipo:</p>
<ul>
<li>o jogo ignora o Alt+F4: a função da janela devolve zero pra mensagem de fechar e o loop principal ignora a de sair, então o launcher injeta um hook que fecha o processo;</li>
<li>o perfil do controle fica num arquivo binário de quase 1 MB, e o launcher escreve os bytes certos pra o controle já vir configurado, conferido byte a byte contra o arquivo que o próprio jogo salvou;</li>
<li>num monitor principal em retrato, a imagem saía numa faixa de 3829x682, então o launcher escolhe o maior monitor em paisagem.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/cmr04-game.jpg" alt="Colin McRae Rally 04 rodando em 16:9 com controle"  loading="lazy" /></p>
<h3>Sega Rally Revo: corrigindo o stutter em Rust<span class="hx:absolute hx:-mt-20" id="sega-rally-revo-corrigindo-o-stutter-em-rust"></span>
    <a href="#sega-rally-revo-corrigindo-o-stutter-em-rust" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O stutter do Sega Rally tem uma correção da comunidade, de um desenvolvedor chamado stuken, mas o script dele não tem licença. Por isso a correção do launcher é uma implementação <strong>clean room</strong>: foi escrita a partir de uma especificação do que ela precisa fazer e do código do próprio jogo, lido pelo REA, por um agente que nunca viu o patch original. O stuken fica com o crédito por achar os bugs e mostrar que dava pra corrigir.</p>
<p>O resultado são &ldquo;code caves&rdquo; escritas no executável da cópia privada:</p>
<ul>
<li>uma trava que impede o jogo de criar a thread de ritmo duplicada a partir da segunda etapa;</li>
<li>uma rotina única que todas as escritas no relógio de corrida passam a chamar, que lê o relógio mestre e nunca deixa o tempo andar pra trás;</li>
<li>uma atualização desse relógio a cada quadro, na thread principal.</li>
</ul>
<p>Medido no jogo, o relógio de corrida fica a menos de 1 ms do tempo real em 99,9% dos quadros, a 60 fps, a 120 fps e sem limite. Cada ponto de hook é conferido byte a byte antes de escrever, e o executável resultante é conferido pelo hash. O mesmo esquema aplica o patch de aspecto pra 21:9 e 32:9, achando as tabelas por padrão de bytes.</p>
<p>O resto foi lidar com o Wine: o jogo roda sempre num desktop virtual isolado, o launcher fixa a GPU NVIDIA em máquina com duas placas, e configura o driver de controle do Wine pra o XInput nativo do jogo funcionar.</p>
<h3>A trilogia Screamer: hackeando o próprio emulador<span class="hx:absolute hx:-mt-20" id="a-trilogia-screamer-hackeando-o-próprio-emulador"></span>
    <a href="#a-trilogia-screamer-hackeando-o-pr%c3%b3prio-emulador" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A trilogia Screamer é de MS-DOS, então o caminho foi outro: um <strong>dosbox-staging modificado</strong>, com uma biblioteca em Rust ligada estaticamente dentro do emulador.</p>
<ul>
<li><strong>3dfx Glide de verdade.</strong> O Screamer 2 e o Screamer Rally carregam o <code>GLIDE2X.OVL</code> do DOS, que conversa com a Voodoo 1 emulada pelo dosbox. O Screamer 2 da GOG traz um stub de Windows que não funciona; com o Screamer Rally na mesma pasta, o launcher usa o OVL genuíno de DOS dele.</li>
<li><strong>Um bug do próprio jogo.</strong> O código de upload de texturas tinha um erro na fronteira de 2 MiB, corrigido com um patch de três instruções.</li>
<li><strong>Controle analógico.</strong> Com o emulador rodando na velocidade máxima, a leitura de joystick da porta de jogos termina antes de o capacitor emulado descarregar, e o controle não funciona. A solução foi achar na memória do jogo os registros de controle e escrever direção, acelerador e freio direto neles, em 10 bits.</li>
<li><strong>Widescreen e distância de desenho</strong> vieram de patches no executável Watcom: a matriz de câmera ganha um campo de visão maior antes da projeção, e os limites de distância dos objetos aumentam.</li>
</ul>
<p>Uma primeira versão usava <code>LD_PRELOAD</code> pra interceptar o relógio do sistema e travava o SDL, porque rodava em várias threads do emulador ao mesmo tempo. Por isso o hook acabou dentro do loop principal do dosbox.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/screamer-launcher-play.png" alt="Launcher da trilogia Screamer, com a seleção do Screamer de 1995"  loading="lazy" /></p>
<h2>Depois de rodar, melhorar<span class="hx:absolute hx:-mt-20" id="depois-de-rodar-melhorar"></span>
    <a href="#depois-de-rodar-melhorar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com o jogo rodando de novo no Linux e no Windows, dá pra ir além do original. É aqui que o launcher vira mais que um instalador.</p>
<h3>Resolução e aspecto<span class="hx:absolute hx:-mt-20" id="resolução-e-aspecto"></span>
    <a href="#resolu%c3%a7%c3%a3o-e-aspecto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Jogo de 1998 foi feito pra 640x480 ou 800x600 em monitor 4:3. Renderizar em 4K é a parte fácil, quando a API permite.</p>
<p>O difícil é o aspecto: esticar uma imagem 4:3 pra 16:9 deixa tudo largo. O certo é o <strong>Hor+</strong>, que mantém a altura e mostra mais cenário nas laterais, aumentando o campo de visão horizontal. Isso exige mexer na projeção da câmera, e foi o que eu fiz no Colin McRae 1 e no Screamer. No Colin McRae 3, o SilentPatch já entrega Hor+ em qualquer proporção.</p>
<h3>Taxa de quadros<span class="hx:absolute hx:-mt-20" id="taxa-de-quadros"></span>
    <a href="#taxa-de-quadros" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Muitos jogos antigos amarram física e renderização no mesmo relógio, e destravar o fps acelera o jogo inteiro. O Colin McRae 1 tem física num tick fixo de 25 Hz e interpola os quadros, então chega a 60 fps sem mexer na física. O Screamer não: a física lê uma tabela de 50 posições indexada pelo tempo do quadro, e passar de 50 fps faz os carros voarem na largada ou derruba o jogo.</p>
<p>Pro Screamer, o launcher tem um modo experimental de <strong>geração de quadros</strong> a 100 fps: entre dois quadros reais ele desenha um quadro intermediário, com uma busca de movimento simples feita num shader. Funciona, mas custa uns 10 ms de atraso, e por isso fica desligado por padrão.</p>
<h3>Filtragem anisotrópica<span class="hx:absolute hx:-mt-20" id="filtragem-anisotrópica"></span>
    <a href="#filtragem-anisotr%c3%b3pica" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando uma textura é vista quase de lado, como o asfalto lá na frente, a filtragem comum borra tudo, porque amostra a textura como se ela estivesse de frente. A <strong>filtragem anisotrópica</strong> amostra mais pontos na direção em que a textura está inclinada, e o asfalto e as placas ao longe ficam nítidos. Nos jogos de Direct3D 9, como o Colin McRae 3 e o OutRun, dá pra forçar até 16x.</p>
<h3>Escaladores<span class="hx:absolute hx:-mt-20" id="escaladores"></span>
    <a href="#escaladores" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando o jogo renderiza numa resolução e a tela tem outra, alguém precisa escalonar a imagem. No Linux, quase todos os launchers usam o gamescope, que oferece:</p>
<ul>
<li><strong>nearest</strong>: repete pixels inteiros, ideal pra quem quer a cara original;</li>
<li><strong>pixel</strong>: mantém os pixels nítidos mas suaviza a borda entre eles, bom pra arte em pixel numa escala que não é inteira;</li>
<li><strong>linear</strong>: interpola entre os pixels, suave, mas borra;</li>
<li><strong>FSR</strong>: o FidelityFX Super Resolution 1.0 da AMD, que reconstrói bordas e aplica nitidez, bom pra subir de 1080p pra 4K;</li>
<li><strong>NIS</strong>: o equivalente da NVIDIA, usado no CMR04.</li>
</ul>
<p>O Screamer tem um FSR próprio, escrito como shader dentro do dosbox, e um &ldquo;superscale&rdquo; 2x da Voodoo emulada: o emulador renderiza a placa 3dfx no dobro da resolução, multiplicando as coordenadas dos triângulos.</p>
<h3>Cor e gama no Colin McRae 3<span class="hx:absolute hx:-mt-20" id="cor-e-gama-no-colin-mcrae-3"></span>
    <a href="#cor-e-gama-no-colin-mcrae-3" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Colin McRae 3 sempre teve uma imagem lavada. Medindo um quadro em 4K, a imagem inteira ficava entre uns 25 e 160 numa escala de 0 a 255: preto que não é preto, branco que não é branco.</p>
<p>O launcher gera uma <strong>LUT</strong>, uma tabela de conversão de cores em 3D, que reajusta ponto preto, ponto branco, gama e saturação, e aplica ela com o vkBasalt por cima do DXVK, junto com um filtro de nitidez. Tem dois perfis, &ldquo;Colour&rdquo; e &ldquo;Cinematic&rdquo;, ambos opcionais e por enquanto só no Linux.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-colin-mcrae-3-cinematic-before-after.jpg" alt="Colin McRae Rally 3 antes e depois da correção de cor e gama"  loading="lazy" /></p>
<h3>Texturas HD geradas por IA no Colin McRae 1<span class="hx:absolute hx:-mt-20" id="texturas-hd-geradas-por-ia-no-colin-mcrae-1"></span>
    <a href="#texturas-hd-geradas-por-ia-no-colin-mcrae-1" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O projeto que mais me divertiu foi o pacote de arte HD do Colin McRae 1. O jogo guarda as imagens num formato próprio, e uma ferramenta em Rust extrai todas, inclusive as que ficam dentro dos modelos 3D: céus, pinturas dos carros, placas na beira da pista.</p>
<p>A primeira tentativa foi o Real-ESRGAN, um upscaler clássico. Ficou ruim: inventou textura de couro rachado na fibra de carbono e contornou as letras. Daí em diante, cada tipo de imagem ganhou um método:</p>
<ul>
<li><strong>arte chapada</strong> (HUD, logos, bandeiras, ícones) passa por um algoritmo determinístico de despixelização, sem IA, e depois é travada na paleta original;</li>
<li><strong>o resto</strong> é redesenhado em 4K pelo modelo de imagem do Google, o Gemini 3.1 Flash Image, com um modelo de visão conferindo se cada palavra e número do redesenho é real. Se nenhuma tentativa passa, fica a imagem original.</li>
</ul>
<p>Os erros da IA foram didáticos: o &ldquo;50&rdquo; do mostrador virou outro &ldquo;60&rdquo;, &ldquo;SNR&rdquo; virou &ldquo;SMX&rdquo;, apareceu &ldquo;New ZeZealand&rdquo;, carros ganharam estepe e entrada de ar que não existiam. A regra que ficou: nunca confiar nas cores do modelo, pegar a melhor de três tentativas, e comparar cada resultado com o original.</p>
<p>O pacote 4 cobre o HUD, os menus, os céus de todas as etapas, 124 das 164 placas e 247 das 370 texturas de carro. A arte bruta tem uns 800 MB; comprimida, fica em uns 47 MB. No jogo, um hook troca cada textura original pela versão HD na hora de desenhar, sem mudar o layout.</p>
<h2>Os jogos<span class="hx:absolute hx:-mt-20" id="os-jogos"></span>
    <a href="#os-jogos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todos seguem o mesmo esquema de instalação, então explico uma vez. <strong>Você precisa ter a sua própria cópia do jogo</strong>, na versão que cada launcher pede, e nenhum deles distribui arquivos do jogo.</p>
<ul>
<li><strong>Linux</strong>: baixe o AppImage da release, dê permissão de execução e rode. Ele já traz o Wine, e o DXVK nos jogos de Direct3D 9; o do Screamer traz o dosbox modificado. Se o gamescope estiver instalado, quase todos usam.</li>
<li><strong>Steam / Steam Deck</strong>: adicione o AppImage como jogo não-Steam, ou o executável de Windows do launcher com um Proton forçado. O Screamer roda nativo, sem Proton.</li>
<li><strong>Windows</strong>: baixe o zip, descompacte e rode o executável do launcher.</li>
</ul>
<p>Na primeira execução, aponte a pasta onde o jogo está instalado, escolha um perfil visual e aperte Play.</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">chmod +x CMR4Launcher-*-x86_64.AppImage
</span></span><span class="line"><span class="cl">./CMR4Launcher-*-x86_64.AppImage</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>Colin McRae Rally (1998)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-1998"></span>
    <a href="#colin-mcrae-rally-1998" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-colin-mcrae/releases/tag/v0.8.0"target="_blank" rel="noopener">Release v0.8.0</a> · precisa da versão em CD, atualizada pra EU v1.5.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/01/colin-mcrae-rally/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/01/colin-mcrae-rally/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>4K, widescreen Hor+ e 60 fps, com a física original a 25 Hz;</li>
<li>pacote de arte HD gerado por IA no HUD, menus, céus, placas e carros;</li>
<li>filtragem trilinear com mipmaps, distância de desenho estendida, buffer de profundidade de 24 bits;</li>
<li>pedais nos gatilhos, rumble e layouts de botão modernos.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/cmr1-launcher.png" alt="Launcher do Colin McRae Rally"  loading="lazy" /></p>
<h3>Colin McRae Rally 3 (2003)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-3-2003"></span>
    <a href="#colin-mcrae-rally-3-2003" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-colin-mcrae-3/releases/tag/v0.2.1"target="_blank" rel="noopener">Release v0.2.1</a> · precisa da versão oficial 1.1.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-colin-mcrae-3-demo-v012-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-colin-mcrae-3-demo-v012.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>SilentPatch com widescreen em qualquer proporção e interface HD em 4K;</li>
<li>conversão do executável oficial 1.1 pro executável sem DRM das reedições;</li>
<li>filtragem anisotrópica até 16x, campo de visão ajustável, sombras nítidas ou suaves;</li>
<li>correção de cor e gama opcional no Linux;</li>
<li>gatilhos como pedais e o controle já configurado no perfil novo.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/cmr3-launcher-picture.png" alt="Aba de imagem do launcher do Colin McRae Rally 3"  loading="lazy" /></p>
<h3>Colin McRae Rally 04 (2004)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-04-2004"></span>
    <a href="#colin-mcrae-rally-04-2004" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-colin-mcrae-4/releases/tag/v0.3.1"target="_blank" rel="noopener">Release v0.3.1</a> · precisa da sua instalação do jogo, com o <code>cmr4.exe</code> original.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/colin-mcrae-rally-04/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/colin-mcrae-rally-04/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>16:9 com a geometria correta, via dgVoodoo, ligado sozinho quando a resolução escolhida é 16:9;</li>
<li>escolha automática do monitor em paisagem e fechar a janela funcionando;</li>
<li>gatilhos analógicos como pedal combinado e perfil do controle pré-configurado;</li>
<li>limite de fps e vsync opcionais.</li>
</ul>
<h3>Colin McRae Rally 2005 (2004)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-rally-2005-2004"></span>
    <a href="#colin-mcrae-rally-2005-2004" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-colin-mcrae-2005/releases/tag/v0.1.0"target="_blank" rel="noopener">Release v0.1.0</a> · precisa da versão 1.1 da GOG, instalada ou só os três arquivos do instalador; as versões em DVD usam a proteção StarForce e não são suportadas.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/colin-mcrae-rally-2005/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/colin-mcrae-rally-2005/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>corrige o crash logo na abertura no Wine: o jogo procura os arquivos por chaves de registro que o instalador grava, e o launcher escreve essas chaves a cada início;</li>
<li>instala direto dos arquivos do instalador da GOG, que trava no Wine, desempacotando tudo com o innoextract;</li>
<li>widescreen 16:9 com a visão widescreen do próprio jogo, no maior tamanho que cabe na tela;</li>
<li>DXVK no Linux: uma etapa que rodava a umas 20 fps passou de 1000 fps, com limite de fps e vsync configuráveis;</li>
<li>saves numa pasta própria e fechar a janela funcionando.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/cmr2005-launcher-play.jpg" alt="Launcher do Colin McRae Rally 2005"  loading="lazy" /></p>
<h3>Colin McRae: DiRT (2007)<span class="hx:absolute hx:-mt-20" id="colin-mcrae-dirt-2007"></span>
    <a href="#colin-mcrae-dirt-2007" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-dirt/releases/tag/v0.2.0"target="_blank" rel="noopener">Release v0.2.0</a> · precisa da versão oficial 1.22.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/dirt-launcher/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/dirt-launcher/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>correção própria do crash em processador com muitas threads, aplicada sozinha (até 12 threads) e sem resetar as configurações;</li>
<li>flag Large Address Aware no executável da cópia, pra ele usar mais de 2 GB de memória, conferida byte a byte;</li>
<li>cópia privada com hard links pros cerca de 9 GB de dados estáticos, quando está no mesmo disco do jogo, sem duplicar espaço;</li>
<li>preset Ultra em 4K, 16:9 nativo do jogo e XInput nativo.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/dirt-launcher.png" alt="Launcher do Colin McRae DiRT detectando a versão 1.22"  loading="lazy" /></p>
<h3>DiRT Rally (2015)<span class="hx:absolute hx:-mt-20" id="dirt-rally-2015"></span>
    <a href="#dirt-rally-2015" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-dirt-rally/releases/tag/v0.1.0"target="_blank" rel="noopener">Release v0.1.0</a> · só pra Linux. Precisa do port de Linux da Feral Interactive, versão 1.0 (o build final de 2017), numa cópia que já rode sem o cliente da Steam. O jogo foi retirado de venda em 2022, e a versão de Windows ainda não é suportada.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/dirt-rally-launcher/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/dirt-rally-launcher/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>corrige o crash logo na abertura em AMD Ryzen 5000 e mais novos (e Intel recentes), com uma pequena biblioteca, sem mexer no jogo;</li>
<li>traz as bibliotecas antigas que o Linux moderno não tem mais (OpenSSL 1.0, libidn11, librtmp, OpenLDAP 2.4, GConf), de pacotes fixos do Steam Runtime, e entrega só pro jogo;</li>
<li>pula a janela da Feral e o aviso de &ldquo;distribuição Linux não suportada&rdquo;, e abre em tela cheia na resolução do monitor, 4K inclusive;</li>
<li>controles novos, como os da 8BitDo e os estilo Xbox recentes, entram na lista de controles do próprio jogo, com o preset pronto;</li>
<li>cópia privada de uns 40 GB, ou hard links quando está no mesmo disco do jogo.</li>
</ul>
<p>O RaceNet foi desligado, então eventos online e ligas não funcionam mais. Carreira offline e etapas avulsas funcionam.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/dirt-rally-launcher-play.jpg" alt="Launcher do DiRT Rally"  loading="lazy" /></p>
<h3>OutRun 2006: Coast 2 Coast (2006)<span class="hx:absolute hx:-mt-20" id="outrun-2006-coast-2-coast-2006"></span>
    <a href="#outrun-2006-coast-2-coast-2006" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-outrun-2006/releases/tag/v0.2.1"target="_blank" rel="noopener">Release v0.2.1</a> · precisa da sua cópia de PC do jogo.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/outrun-2006/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/outrun-2006/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>o OutRun2006Tweaks, que conserta o port de PC e remove o DRM de disco, instalado automaticamente numa versão fixa e testada;</li>
<li>4K, filtragem anisotrópica 16x, reflexos em 2048 e distância de desenho maior;</li>
<li>a música &ldquo;Rush a Difficulty&rdquo; com os dois segundos iniciais que faltavam na versão de PC;</li>
<li>pacote de texturas HD opcional, baixado sob demanda;</li>
<li>rumble e gatilhos completos pelo SDL.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/outrun-launcher-picture.png" alt="Aba de imagem do launcher do OutRun 2006"  loading="lazy" /></p>
<h3>Sega Rally Revo (2007)<span class="hx:absolute hx:-mt-20" id="sega-rally-revo-2007"></span>
    <a href="#sega-rally-revo-2007" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-sega-rally-revo/releases/tag/v0.2.0"target="_blank" rel="noopener">Release v0.2.0</a> · precisa da versão 4.0.6.0 de PC.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/sega-rally-revo-launcher-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/sega-rally-revo-launcher.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>correção do stutter própria, feita em clean room;</li>
<li>4K, qualidade Ultra e ultrawide 21:9 e 32:9;</li>
<li>limite de 60 ou 120 fps, controle XInput nativo e teste de rumble.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/jogos-corrida-ia/srr-launcher-play.png" alt="Launcher do Sega Rally Revo"  loading="lazy" /></p>
<h3>Trilogia Screamer (1995 a 1997)<span class="hx:absolute hx:-mt-20" id="trilogia-screamer-1995-a-1997"></span>
    <a href="#trilogia-screamer-1995-a-1997" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><a href="https://github.com/akitaonrails/rea-screamer-trilogy/releases/tag/v0.7.0"target="_blank" rel="noopener">Release v0.7.0</a> · precisa do Screamer e do Screamer 2 da GOG ou dos CDs originais; o Screamer Rally só existe em CD.</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-screamer-trilogy/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/11/02/rea-screamer-trilogy/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<ul>
<li>3dfx Glide com o OVL genuíno de DOS e superscale 2x da Voodoo;</li>
<li>FSR em shader, widescreen Hor+ e distância de desenho estendida;</li>
<li>controle analógico de verdade, inclusive no 8BitDo Ultimate 2;</li>
<li>trilha de CD funcionando e geração de quadros experimental a 100 fps.</li>
</ul>
<h2>Windows e Linux ao mesmo tempo<span class="hx:absolute hx:-mt-20" id="windows-e-linux-ao-mesmo-tempo"></span>
    <a href="#windows-e-linux-ao-mesmo-tempo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Fazer o mesmo launcher funcionar nos dois sistemas exige algumas escolhas:</p>
<ul>
<li><strong>um executável só.</strong> Os launchers dos jogos de Windows são escritos em Rust, com interface em egui, e compilados como executável de Windows. No Windows ele roda direto; no Linux, o mesmo executável roda dentro do Wine;</li>
<li><strong>o AppImage leva tudo.</strong> No Linux, cada launcher de jogo de Windows empacota um Wine 11.20, e o DXVK quando o jogo precisa, com o próprio prefixo, sem depender de nada instalado na máquina;</li>
<li><strong>o gráfico muda por sistema.</strong> No Windows o jogo usa o Direct3D nativo; no Linux, o DXVK traduz pra Vulkan, e o Colin McRae 1 usa o wined3d do próprio Wine;</li>
<li><strong>o Screamer é nativo nos dois</strong>, porque o dosbox-staging compila pra Linux e Windows;</li>
<li><strong>o DiRT Rally é a exceção</strong>: ele roda o port nativo de Linux da Feral, sem Wine, então por enquanto o launcher só existe pra Linux.</li>
</ul>
<p>Teve pegadinha nisso também. O linker do GNU binutils 2.47 descartava parte da tabela de importação do executável de Windows, e o launcher crashava logo ao abrir. A correção foi trocar pro linker do LLVM, o lld. Hoje todos os launchers do SDK (abaixo) linkam com ele, e a CI confere as importações de cada executável.</p>
<blockquote>
  <p>Todos os launchers foram testados de ponta a ponta no Linux (com Wine 11, com o dosbox no Screamer, ou nativo no DiRT Rally), numa RTX 5090 e com controle estilo Xbox. <strong>No Windows nativo eles ainda não foram testados.</strong> Se você tem algum desses jogos e um PC com Windows, teste e abra uma issue no repositório, com o SHA-256 do executável e o log. Contribuições são muito bem-vindas.</p>

</blockquote>
<h2>Quanto tempo levou<span class="hx:absolute hx:-mt-20" id="quanto-tempo-levou"></span>
    <a href="#quanto-tempo-levou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pelo histórico do git, somando as sessões de trabalho, e contando o trabalho antes do primeiro commit pelas datas dos arquivos do projeto:</p>
<table>
  <thead>
      <tr>
          <th>Launcher</th>
          <th style="text-align: right">Commits</th>
          <th style="text-align: right">Trabalho estimado</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Colin McRae Rally (desde o artigo anterior)</td>
          <td style="text-align: right">39</td>
          <td style="text-align: right">~17 h</td>
      </tr>
      <tr>
          <td>Trilogia Screamer</td>
          <td style="text-align: right">29</td>
          <td style="text-align: right">~12 h</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 3</td>
          <td style="text-align: right">27</td>
          <td style="text-align: right">~7 h</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 04</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">~5,5 h</td>
      </tr>
      <tr>
          <td>Colin McRae: DiRT</td>
          <td style="text-align: right">3</td>
          <td style="text-align: right">~4,5 h</td>
      </tr>
      <tr>
          <td>OutRun 2006</td>
          <td style="text-align: right">17</td>
          <td style="text-align: right">~4,5 h</td>
      </tr>
      <tr>
          <td>Sega Rally Revo</td>
          <td style="text-align: right">6</td>
          <td style="text-align: right">~4,5 h</td>
      </tr>
      <tr>
          <td>Colin McRae Rally 2005 (depois, já no SDK)</td>
          <td style="text-align: right">15</td>
          <td style="text-align: right">~4,5 h</td>
      </tr>
      <tr>
          <td>DiRT Rally (depois, já no SDK)</td>
          <td style="text-align: right">22</td>
          <td style="text-align: right">~5 h</td>
      </tr>
  </tbody>
</table>
<p>Vários foram feitos em paralelo: o CMR04 e o DiRT na mesma tarde, o OutRun e o Sega Rally na mesma noite. Somando, são umas 38 horas de sessões de trabalho nos seis launchers novos, mas como eles andaram em paralelo, tudo coube em pouco mais de um dia de calendário. O Colin McRae Rally 2005 e o DiRT Rally vieram depois, em paralelo, numa tarde e noite só.</p>
<h3>O que deu errado no caminho<span class="hx:absolute hx:-mt-20" id="o-que-deu-errado-no-caminho"></span>
    <a href="#o-que-deu-errado-no-caminho" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>A versão nova nem sempre é a melhor.</strong> O dgVoodoo 2.87 crashava no CMR04; a 2.80, que funciona, nem está mais disponível completa.</li>
<li><strong>O jogo também tem bugs.</strong> O Screamer tinha um bug de upload de textura, e o DiRT tem mapas de threads incompletos pra 6 núcleos.</li>
<li><strong>Código que parece morto pode estar vivo.</strong> No Screamer, uma área de &ldquo;preenchimento com zeros&rdquo; usada pra um patch era código alcançável, e o jogo fechava no meio da corrida.</li>
<li><strong>Diagnóstico errado custa caro.</strong> No CMR3, um controle virtual criado por outro script na minha máquina me fez caçar um bug de mapeamento que não existia.</li>
<li><strong>O Wine tem as suas manias.</strong> O EGL do Wine 11 caía pra renderização por software na NVIDIA, a tela cheia escolhia o monitor em retrato, e um crash do player de vídeo travava o jogo atrás de um depurador invisível.</li>
</ul>
<h2>A receita virou SDK<span class="hx:absolute hx:-mt-20" id="a-receita-virou-sdk"></span>
    <a href="#a-receita-virou-sdk" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois da primeira leva, o código que se repetia nos sete launchers saiu de cada repositório e foi pra uma base comum, o <a href="https://github.com/akitaonrails/rea-launcher-sdk"target="_blank" rel="noopener">rea-launcher-sdk</a>. Ali ficam a janela de configurações, a cópia privada do jogo, a identificação do executável por SHA-256, os patches binários, o hook de fechar a janela, o empacotamento em AppImage com Wine e a CI.</p>
<p>Com isso, cada launcher ganhou de uma vez:</p>
<ul>
<li><strong>uma instância por vez</strong>: abrir o launcher de novo traz a janela aberta pra frente, e com o jogo rodando ele avisa em vez de abrir uma segunda cópia;</li>
<li><strong>uma tabela única de opções</strong>, de onde saem a janela, o <code>--help</code> e o <code>launcher.cfg</code>, com um teste que falha se a janela e a linha de comando divergirem;</li>
<li><strong>lld e checagem de importações</strong> na CI de todos;</li>
<li><strong>formulários de issue padronizados</strong>, pra bug e pra versão desconhecida do jogo, pedindo o SHA-256 do executável e o log.</li>
</ul>
<p>O SDK foi escrito pra ser usado por um agente de código. O README tem o passo a passo: instalar o REA, apontar o agente pro SDK e pra pasta do seu jogo, e pedir um launcher novo. O agente pesquisa os fixes da comunidade, faz a engenharia reversa e escreve o código, e você revisa nos pontos de checagem que o playbook define.</p>
<p>O Colin McRae Rally 2005 e o DiRT Rally foram os primeiros a nascer assim, a partir do script <code>new-launcher</code> do SDK: cada um levou umas 4 a 5 horas da primeira pesquisa até a release.</p>
<h2>Ferramentas velhas, velocidade nova<span class="hx:absolute hx:-mt-20" id="ferramentas-velhas-velocidade-nova"></span>
    <a href="#ferramentas-velhas-velocidade-nova" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Nada aqui é técnica nova. Wine existe desde 1993, o DOSBox desde 2002, o dgVoodoo e o DXVK há anos, e os patches da comunidade foram feitos por gente que passou noites com depurador e editor hexadecimal. Ghidra, disassembler, <code>winedbg</code>, xdelta: tudo ferramenta de décadas.</p>
<p>A diferença é que agora um LLM opera essas ferramentas comigo. Ele lê a documentação, escreve o hook, roda o jogo, compara o resultado e tenta de novo, enquanto eu decido o que vale a pena consertar e reconheço quando a estratégia está errada. O REA dá a ele acesso estruturado à engenharia reversa, e o resto é a caixa de ferramentas que a comunidade de preservação já tinha construído.</p>
<blockquote>
  <p>Portar abandonware de volta pra era moderna costumava levar semanas por jogo, e por isso quase ninguém fazia. Com LLMs operando ferramentas que existem há décadas, cada jogo virou trabalho de algumas horas, e eu fiz oito em uns dois dias.</p>

</blockquote>
<p>Os launchers são experimentais e vão continuar mudando. Se o seu jogo favorito está preso no passado, a receita está aberta nos repositórios.</p>
]]></content:encoded><category>games</category><category>retrocomputacao</category><category>emulacao</category><category>linux</category></item><item><title>Brincando de PC Ports com Colin McRae 1</title><link>https://akitaonrails.github.io/2026/10/29/brincando-de-pc-ports-com-colin-mcrae-1/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/29/brincando-de-pc-ports-com-colin-mcrae-1/</guid><pubDate>Thu, 29 Oct 2026 22:00:00 -0300</pubDate><description>&lt;p&gt;Tenho brincado bastante com engenharia reversa usando o &lt;a href="https://akitaonrails.github.io/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/"&gt;REA&lt;/a&gt;. Expliquei naquele artigo que ele junta Ghidra, Hopper, IDA, Frida e outras ferramentas numa interface que um agente consegue operar, mantendo evidência e procedência do que encontrou.&lt;/p&gt;
&lt;p&gt;Isso ajuda muito. Só não confunda ajuda com botão mágico. Você não escreve &lt;em&gt;&amp;ldquo;REA, decompila este jogo&amp;rdquo;&lt;/em&gt; ou &lt;em&gt;&amp;ldquo;porta isso pra Windows&amp;rdquo;&lt;/em&gt; e vai tomar café enquanto ele termina. Precisa entender a arquitetura, o formato do binário, as APIs antigas, o que observar em runtime, qual ferramenta usar e como provar que a reconstrução bate com o programa original.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Tenho brincado bastante com engenharia reversa usando o <a href="/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/">REA</a>. Expliquei naquele artigo que ele junta Ghidra, Hopper, IDA, Frida e outras ferramentas numa interface que um agente consegue operar, mantendo evidência e procedência do que encontrou.</p>
<p>Isso ajuda muito. Só não confunda ajuda com botão mágico. Você não escreve <em>&ldquo;REA, decompila este jogo&rdquo;</em> ou <em>&ldquo;porta isso pra Windows&rdquo;</em> e vai tomar café enquanto ele termina. Precisa entender a arquitetura, o formato do binário, as APIs antigas, o que observar em runtime, qual ferramenta usar e como provar que a reconstrução bate com o programa original.</p>
<p>O trabalho de pensar continua com você. O REA acelera a coleta de evidências e dá braços extras pra quem sabe conduzir a investigação. Sem conhecimento técnico, o resultado continua sendo um amontoado de pseudocódigo plausível.</p>
<p>O <a href="https://github.com/akitaonrails/rea-colin-mcrae"target="_blank" rel="noopener">projeto está no GitHub</a> e ainda é <strong>experimental e work in progress</strong>. A versão que mostro aqui já permite jogar uma etapa inteira, mas estou construindo a próxima rodada: controle de frame pacing e vsync, filtros bilinear, trilinear e anisotrópico, além de estudar mipmaps sem alterar os dados originais. Também faltam testes em mais máquinas e no Windows nativo. Espere mudanças e releases novas.</p>
<p>Agora assista a isto:</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/29/colin-mcrae-rally/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/29/colin-mcrae-rally/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>Esse é o <strong>Colin McRae Rally original de 1998</strong> rodando uma etapa inteira da Nova Zelândia. A captura está em 1080p, mas o jogo chega a 4K, em 16:9 real, mostrando mais cenário nas laterais. O HUD foi reposicionado nos cantos, a distância de desenho aumentou e aquele pop-in constante quase desapareceu.</p>
<p>A imagem é apresentada a 60 fps nessa configuração, com a física original no tick fixo de 25 Hz. O jogo já separava atualização e renderização e interpolava os frames. A velha cadeia de DirectDraw/Direct3D 5 só precisava chegar corretamente ao display moderno.</p>
<p>E abaixo está o launcher que fiz pra isso:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/29/colin-mcrae-rally/launcher.png" alt="Launcher do Colin McRae Rally, com configuração de resolução, widescreen, draw distance, controle e diretórios"  loading="lazy" /></p>
<p>Sim, eu fiz um launcher moderno pro primeiro Colin McRae Rally de PC.</p>
<h2>O jogo que ficou preso em 1998<span class="hx:absolute hx:-mt-20" id="o-jogo-que-ficou-preso-em-1998"></span>
    <a href="#o-jogo-que-ficou-preso-em-1998" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Muita gente lembra do Colin McRae Rally pelo PlayStation. Era um jogo 4:3 de baixa resolução, feito pro ritmo dos televisores PAL e NTSC da época. Em 2026, ainda dá pra jogar num emulador e subir a resolução interna, porém o resultado continua carregando as limitações visuais e de apresentação do console.</p>
<p>A Codemasters também lançou uma <a href="https://www.pcgamingwiki.com/wiki/Colin_McRae_Rally"target="_blank" rel="noopener">versão nativa pra Windows em 1998</a>. Ela foi feita pra Windows 95 e 98, num executável x86 de 32 bits com Direct3D 5 e DirectX 6.</p>
<p>Hoje o jogo está fora de catálogo e é tratado como abandonware pela comunidade. Esse rótulo descreve a realidade de preservação, não concede licença pra redistribuir nada. Meu launcher não leva executável, pista, carro, música ou qualquer asset da Codemasters. Você precisa da sua própria cópia instalada, do CD ou de um backup antigo, atualizada pro patch 1.5.</p>
<p>Encontrar os arquivos virou arqueologia. Eu não vou hospedar nem apontar download de jogo comercial. O projeto aceita hoje duas variantes europeias conhecidas do <code>Rally.exe</code> 1.5 e mostra o SHA-256 quando encontra outra versão, justamente pra podermos adicionar suporte sem adivinhar offsets.</p>
<h2>A novela anterior ao launcher<span class="hx:absolute hx:-mt-20" id="a-novela-anterior-ao-launcher"></span>
    <a href="#a-novela-anterior-ao-launcher" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu já tinha automatizado o Colin McRae no meu projeto de <a href="/2026/04/19/retrogames-de-corrida-favoritos-no-distrobox/">retrogames de corrida no Distrobox</a>. Funcionava, mas leia a lista do que foi necessário:</p>
<ul>
<li>copiar uma instalação portátil e reconstruir no Wine as chaves <code>CDPath</code>, <code>HDPath</code>, <code>AVIPath</code> e <code>WAVPath</code> do Registry;</li>
<li>forçar o perfil de compatibilidade do Windows XP SP3;</li>
<li>prender um Xwayland rootful numa tela de 800x600, porque o menu abre em 640x480 e a corrida troca de modo;</li>
<li>forçar o backend GLX e o offload pra NVIDIA, evitando que o Wine escolhesse a iGPU AMD ou <code>llvmpipe</code> na minha máquina;</li>
<li>emular teclado a partir do gamepad, porque o caminho antigo de DirectInput detectava o controle, mas o jogo não aceitava os eixos;</li>
<li>conviver com a imagem 4:3 esticada e com os becos sem saída de gamescope, dgVoodoo2 e outros wrappers.</li>
</ul>
<p>Depois de tudo isso, o jogo chegava a <strong>800x600</strong>, 4:3, com a distância de desenho original. O controle precisava estar ligado antes de abrir. Clicar no velho painel de joystick podia deixar a tela preta com a música tocando. Era o tipo de configuração que funciona no meu PC porque eu gastei horas documentando cada gambiarra.</p>
<p>No Windows 11 talvez você consiga montar outra combinação de compatibility mode e wrappers. Eu não testei. A própria <a href="https://www.pcgamingwiki.com/wiki/Colin_McRae_Rally"target="_blank" rel="noopener">PCGamingWiki</a> recomenda compatibilidade com XP SP3 e documenta os problemas da versão de 1998 em sistemas atuais.</p>
<blockquote>
  <p>Depender de uma sequência secreta de Registry, Xwayland, Wine e wrapper gráfico reduz a &ldquo;preservação&rdquo; a algo que funciona temporariamente no computador de alguém.</p>

</blockquote>
<h2>Eu tentei fazer o decomp completo<span class="hx:absolute hx:-mt-20" id="eu-tentei-fazer-o-decomp-completo"></span>
    <a href="#eu-tentei-fazer-o-decomp-completo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/pablocpas/OpenCMR2"target="_blank" rel="noopener">OpenCMR2</a> me animou a tentar algo mais ambicioso. Ele é um port nativo em andamento do <strong>Colin McRae Rally 2.0</strong>, construído sobre o CMR2Decomp, usando SDL3 e os dados da cópia original. É exatamente o tipo de projeto que permite abandonar DirectDraw, DirectInput e outras APIs antigas.</p>
<p>Tentei fazer o equivalente pro primeiro jogo. Comecei a reconstrução no dia 20 de outubro. Quando parei pra revisar a direção no dia 28, depois de oito dias de commits quase contínuos, o experimento já tinha cerca de 250 mil linhas de Rust, 2.922 testes, 2.168 procedures mapeadas no executável, 32.530 símbolos e 2.310 strings catalogadas. O roadmap marcava 117 itens completos de 119.</p>
<p>No oitavo dia, o padrão ficou claro. O REA continuava encontrando funções, relações e evidências locais, e os agentes continuavam transformando cada descoberta em mais código e testes. Só que o processo estava derivando. O volume do repositório crescia, enquanto a distância até um port fiel e utilizável deixava de diminuir.</p>
<p>A parte parcial concentrava justamente a integração mais difícil: colisão e suspensão em runtime, animação de carro e rodas, produtores de marcha e giro, fluxo completo de chegada e pós-corrida, fidelidade de pintura, reflexos, danos e execução equivalente no Windows. Existia um harness dirigível, útil pra provar subsistemas, com partes recuperadas e outras aproximadas. Cada nova camada revelava outros contratos escondidos no executável original.</p>
<blockquote>
  <p>Quando o repositório cresce mais rápido que o resultado, está na hora de revisar a estratégia.</p>

</blockquote>
<p>Eu tratei aquilo como qualquer spike de engenharia: aprendi o que precisava, reconheci que o caminho estava se afastando do objetivo e parei. Continuar só porque já havia investido oito dias seria custo afundado. O experimento cumpriu sua função ao mostrar onde a reconstrução completa começava a perder convergência.</p>
<p>No dia 28, mudei o plano. O executável original já resolvia física, renderização, áudio, menus e toda a lógica do jogo. A opção mais curta era preservar esse núcleo e corrigir apenas as fronteiras quebradas pelo tempo. Foi daí que nasceu o launcher.</p>
<p>Essa estratégia serve pra muitos jogos antigos de Windows. Se o executável x86 ainda roda e Wine ou Proton conseguem fornecer as APIs básicas, a tarefa muitas vezes se resume a refazer a ligação com o sistema atual, mantendo dentro do binário as decisões originais. Isso já basta pro objetivo de voltar a jogar num computador moderno.</p>
<p>O custo esperado em tokens de LLM cai muito. Um decomp completo obriga o agente a ler, explicar, reconstruir e testar física, áudio, renderização, menus, formatos de arquivos e cada estado intermediário. O launcher reduziu o problema a uma lista fechada: Registry, checagem de CD e Windows, modo de vídeo, controles, resolução, projeção, HUD e distância de desenho. Quando a meta é jogar, esse tipo de <em>rewiring</em> entrega muito mais por token.</p>
<h2>Um launcher fino em volta do original<span class="hx:absolute hx:-mt-20" id="um-launcher-fino-em-volta-do-original"></span>
    <a href="#um-launcher-fino-em-volta-do-original" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/akitaonrails/rea-colin-mcrae"target="_blank" rel="noopener">projeto está no GitHub</a>. A arquitetura ficou bem menor: um launcher em Rust prepara um ambiente privado, valida a versão do jogo, configura o runtime e aplica correções em memória no <code>Rally.exe</code>. Uma DLL pequena intercepta os pontos em que resolução e HUD precisam de contexto extra.</p>
<p>Nada é alterado no diretório original. Na primeira execução, o launcher cria uma cópia privada em <code>%LOCALAPPDATA%\CMRLauncher\Rally</code>, sincroniza os arquivos e guarda os saves fora da instalação. Isso evita depender de escrita em <code>Program Files</code>, VirtualStore e restos de estado de uma tentativa anterior.</p>
<p>O launcher também recria as chaves de Registry que o jogo espera. Ele aponta <code>CDPath</code>, <code>HDPath</code>, vídeos e áudio pra cópia privada, escolhe o idioma e prepara o hardware. O velho CD deixa de ser uma dependência de execução, desde que a instalação tenha sido feita no modo completo.</p>
<p>As correções ficam associadas ao hash exato do executável. Hoje o projeto conhece a versão europeia 1.5 oficial e uma variante de fan pack. Se o binário não bater, ele recusa aplicar endereços de outra versão. Patch binário por aproximação é uma ótima maneira de transformar um jogo antigo em crash aleatório.</p>
<h3>Compatibilidade básica<span class="hx:absolute hx:-mt-20" id="compatibilidade-básica"></span>
    <a href="#compatibilidade-b%c3%a1sica" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O executável começa testando uma coleção de premissas dos anos 90. Eu removi ou substituí as que não fazem sentido hoje:</p>
<ul>
<li>o teste do CD e o bloqueio por versão do Windows;</li>
<li>leituras privilegiadas de portas VGA;</li>
<li>o caminho de vídeo 8-bit incompatível, trocado pelo frontend de 16 bits que já existia no jogo;</li>
<li>o comportamento em que fechar a janela ou apertar <code>Alt+F4</code> era ignorado;</li>
<li>um busy-wait baseado num relógio de CPU que se comporta mal em processadores modernos.</li>
</ul>
<p>Wine e Proton continuam fornecendo DirectDraw, Direct3D 5, WinMM e as outras APIs antigas. Meu trabalho ficou concentrado no contrato entre o jogo e a camada de compatibilidade, sem reescrever um renderer inteiro.</p>
<h3>Controles modernos<span class="hx:absolute hx:-mt-20" id="controles-modernos"></span>
    <a href="#controles-modernos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O jogo reconhecia controles antigos de DirectInput e esperava eixos de uma época anterior aos triggers analógicos atuais. Agora aceleração e freio podem usar os gatilhos ou o analógico direito, e <code>Start</code> pausa.</p>
<p>Também corrigi um bug em que o jogo salvava um controle inexistente, voltava sozinho pro teclado ou carregava uma configuração quebrada e deixava o carro girando. O launcher detecta esse estado e repara o save. Isso elimina a ponte de teclado que eu mantinha no Distrobox.</p>
<h3>Resolução e widescreen<span class="hx:absolute hx:-mt-20" id="resolução-e-widescreen"></span>
    <a href="#resolu%c3%a7%c3%a3o-e-widescreen" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As quatro resoluções originais eram 320x240, 512x384, 640x480 e 800x600. O frontend foi desenhado em 640x480, enquanto a corrida recria o dispositivo gráfico com a resolução escolhida.</p>
<p>O launcher permite 1920x1080, 2560x1440 e 3840x2160. Durante a corrida, toda a interface 2D passa a usar uma tela virtual de 640x480. Sprites, fontes, mensagens, pausa, resultados e loading screen são escalados dessa área virtual; os elementos laterais do HUD ficam ancorados nos cantos da tela real.</p>
<p>O widescreen usa projeção <strong>Hor+</strong>. O centro mantém o mesmo enquadramento do 4:3 e a tela ganha informação nas laterais, em vez de esticar carros e árvores. Eu validei a geometria comparando 1440x1080 com 1920x1080: a faixa central permanece igual.</p>
<p>Os menus continuam sendo desenhados internamente em 640x480 e são ampliados com pixels nítidos. Em 4K essa cópia pode ficar abaixo de 30 fps em algumas máquinas. A corrida não sofre do mesmo problema; 1440p ou 1080p também deixa a navegação mais leve.</p>
<h3>Distância de desenho<span class="hx:absolute hx:-mt-20" id="distância-de-desenho"></span>
    <a href="#dist%c3%a2ncia-de-desenho" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As pistas já são carregadas inteiras. O sumiço do cenário vinha do far plane e dos limites de culling gravados nos dados de cada etapa, normalmente entre 100 e 200 metros.</p>
<p>O launcher pode multiplicar essas distâncias em até três vezes. Ele ajusta o far plane, o wedge de objetos, o limite de terreno e o cap especial de algumas etapas. A geometria estava lá o tempo todo; o PC de 1998 só não tinha orçamento pra desenhá-la.</p>
<p>Aumentar demais custa desempenho e pode expor flicker distante por causa da precisão do Z-buffer de 16 bits. Por isso o controle é configurável entre <code>1.0</code> e <code>3.0</code>, e o padrão não força a melhoria.</p>
<h3>60 fps sem mexer na física<span class="hx:absolute hx:-mt-20" id="60-fps-sem-mexer-na-física"></span>
    <a href="#60-fps-sem-mexer-na-f%c3%adsica" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Esta parte me surpreendeu. A simulação roda em 25 Hz e existe um modo escondido de 50 Hz que muda o resultado da física. Alterar isso seria mudar o jogo.</p>
<p>No modo normal, uma thread de timer atualiza a simulação e o loop principal renderiza tão rápido quanto o <code>Flip</code> permite. O código calcula um alpha pelo tempo desde o último tick e interpola carro, câmera e objetos. Em gamescope sem vsync, medi cerca de 190 frames apresentados por segundo em 1080p. Em 4K, o renderer por software ficou perto de 60 fps.</p>
<p>Os 60 fps vêm desse pipeline original de renderização interpolada. Num display real, o jogo apresenta no refresh do monitor, limitado pelo vsync da camada que o hospeda. A física continua a mesma de 1998.</p>
<h2>Atualização: voltei pro recomp<span class="hx:absolute hx:-mt-20" id="atualização-voltei-pro-recomp"></span>
    <a href="#atualiza%c3%a7%c3%a3o-voltei-pro-recomp" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com a primeira versão jogável do launcher publicada, voltei ao <a href="https://github.com/akitaonrails/rea-colin-mcrae-recomp"target="_blank" rel="noopener">repositório do recomp</a>, enquanto os dois continuaram evoluindo em paralelo. O primeiro novo commit entrou na madrugada do dia 30, poucas horas depois do vídeo do launcher. Eu já tinha aprendido quais partes do executável realmente importavam em runtime, e agora também tinha uma forma muito mais simples de abrir o original e comparar o comportamento.</p>
<p>Na primeira madrugada de volta, fechamos os dois itens que restavam e o mapa de reconstrução chegou a <strong>119 de 119 itens completos</strong>, com mais de 2.900 testes automatizados. Esse número significa que cobrimos o escopo que estava mapeado. Fidelidade perfeita exige outra etapa: jogar, observar, comparar com o original e ajustar as diferenças que só aparecem quando todos os subsistemas rodam juntos.</p>
<p>O estado atual já merece o rótulo de <strong>mostly playable</strong>. O front-end funciona com teclado e gamepad, dá pra escolher Championship, Rally, Time Trial e Rally School, passar pela tela de boas-vindas e Service Area, escolher carro e correr as 52 etapas dos oito países. O jogo carrega a pista e o carro escolhidos, toca as pace notes do Nicky Grist, anima rodas e suspensão, reproduz som de motor e superfícies, desenha HUD, processa checkpoints, splits e ranking, detecta a chegada e volta pros menus.</p>
<p>Também reconstruímos a órbita de câmera antes da largada, a contagem 5-4-3-2-1-GO! com áudio, objetos móveis, luzes noturnas, faróis, colisão com cenário e dano visível. Os commits mais recentes corrigiram o eixo longitudinal usado pela física, o término prematuro no Split 7 e os parâmetros de setup usados pra derivar as tabelas de câmbio e as constantes de cada carro. Já parece e soa como o Colin McRae original.</p>
<p>Ainda falta trabalho na sensação de dirigir. Nos meus testes desta build, o carro escorrega muito mais do que deveria e demora demais pra recuperar aderência. Esse tipo de diferença não aparece numa checklist de funções recuperadas. Ela aparece quando você entra numa curva, corrige o volante e percebe que o carro continua atravessado. Agora a prioridade é calibrar essas interações contra o original até o handling ficar convincente.</p>
<p>Também não coloquei nenhuma melhoria gráfica do launcher no recomp. O renderer moderno usa <code>wgpu</code>, mas continua apresentando a estética original em <strong>640x480</strong>, com os assets, HUD e enquadramento antigos. Resolução alta, widescreen, draw distance maior, filtros modernos e outros confortos ficam pra depois. Primeiro quero acertar o jogo; embelezar uma física errada só produz um erro mais bonito.</p>
<p>Este é o estado do recomp na noite de 31 de outubro:</p>
<video
  controls
  preload="metadata"
  playsinline
  class="html5-video"
  style="width:100%; max-width:960px; display:block; margin:1.25em auto; border-radius:8px; background:#000;"
  poster="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/31/colin-mcrae-rally-recomp/showcase-poster.jpg">
  <source src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/31/colin-mcrae-rally-recomp/showcase-h264.mp4" type="video/mp4">
  Your browser does not support HTML5 video.
</video>

<p>O vídeo mostra a tela de boas-vindas, largada, câmera, HUD, checkpoints e uma etapa dirigível, com áudio; a facilidade com que o carro entra e permanece numa derrapagem ainda denuncia a diferença de física. É um resultado muito além daquele harness aproximado de três dias atrás, porém continua sendo uma reconstrução em desenvolvimento.</p>
<p>Agora já existe base suficiente pra convidar mais gente. O repositório não inclui executáveis nem assets da Codemasters; cada pessoa importa os dados da própria cópia europeia do jogo. Quem quiser contribuir pode testar outras etapas, carros, controles, distribuições e Windows, abrir <a href="https://github.com/akitaonrails/rea-colin-mcrae-recomp/issues"target="_blank" rel="noopener">issues detalhadas</a>, comparar o handling com o original ou trabalhar diretamente nos subsistemas. O README tem as instruções de importação, compilação e execução.</p>
<p>O launcher continua sendo a opção prática pra jogar hoje com melhorias modernas. O recomp voltou a ser interessante porque cruzou outra linha: já dá pra jogar, sentir onde ele diverge e substituir aproximações por evidência. Agora cada contribuição consegue atacar uma diferença visível em vez de trabalhar meses até aparecer o primeiro frame.</p>
<h2>Como instalar<span class="hx:absolute hx:-mt-20" id="como-instalar"></span>
    <a href="#como-instalar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Você precisa do <strong>Colin McRae Rally de PC de 1998</strong>, com instalação completa e patch europeu 1.5. Dentro da pasta devem existir <code>Rally.exe</code>, <code>Game</code>, <code>New_Fe</code> e <code>ini</code>.</p>
<p>Baixe o ZIP na <a href="https://github.com/akitaonrails/rea-colin-mcrae/releases/latest"target="_blank" rel="noopener">última release do launcher</a>. Ele vem com <code>CMRLauncher.exe</code>, <code>cmrhooks.dll</code> e os auxiliares de Linux. Não vem com o jogo.</p>
<h3>Linux com Steam e Proton<span class="hx:absolute hx:-mt-20" id="linux-com-steam-e-proton"></span>
    <a href="#linux-com-steam-e-proton" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ol>
<li>Extraia o ZIP, por exemplo em <code>~/Games/cmr-launcher</code>.</li>
<li>No Steam, escolha <strong>Games → Add a Non-Steam Game to My Library</strong> e selecione <code>CMRLauncher.exe</code>.</li>
<li>Em <strong>Properties → Compatibility</strong>, force GE-Proton. Testei com GE-Proton 11.</li>
<li>Em <strong>Launch options</strong>, passe a pasta da sua instalação:</li>
</ol>
<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">%command% --game &#34;Z:/home/voce/Games/Colin McRae Rally&#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>O caminho fica salvo depois da primeira execução. Também existe um script pra Wine com gamescope e uma interface em Quickshell, a que aparece no screenshot.</p>
<h3>Windows<span class="hx:absolute hx:-mt-20" id="windows"></span>
    <a href="#windows" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Extraia o ZIP ao lado de <code>Rally.exe</code> e abra <code>CMRLauncher.exe</code>. Se preferir deixar o launcher em outro diretório:</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">CMRLauncher.exe --game &#34;C:\Games\Colin McRae Rally&#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>O código foi preparado pra Windows 10 e 11, mas <strong>eu ainda não testei em Windows nativo</strong>. Tudo que validei até agora foi em Arch Linux, usando Wine e Proton. Quem testar em Windows, outra distribuição, AMD, Intel, outros monitores e outros controles já ajuda bastante.</p>
<p>Ao encontrar um problema, abra uma <a href="https://github.com/akitaonrails/rea-colin-mcrae/issues"target="_blank" rel="noopener">issue no GitHub</a> com o <code>launcher.log</code>, hash do <code>Rally.exe</code>, sistema operacional, GPU, versão do Wine ou Proton e passos pra reproduzir. <em>&ldquo;Não funcionou&rdquo;</em> não dá pista suficiente pra corrigir compatibilidade.</p>
<h2>Menos de dois dias<span class="hx:absolute hx:-mt-20" id="menos-de-dois-dias"></span>
    <a href="#menos-de-dois-dias" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A parte específica deste launcher saiu do primeiro commit de compatibilidade jogável ao vídeo acima em menos de 30 horas. Nesse intervalo eu troquei a estratégia de um port nativo gigantesco por uma camada pequena em volta do executável original, fiz engenharia reversa dos pontos necessários, implementei as correções, testei uma etapa inteira e empacotei a release.</p>
<p>Recomps completos continuam difíceis. Exigem meses ou anos, conhecimento da plataforma, comparação contra o original e uma quantidade enorme de validação. REA ajuda a chegar na evidência; Claude e Codex ajudam a escrever e testar; alguém ainda precisa decidir qual problema vale resolver e reconhecer quando a estratégia está errada.</p>
<blockquote>
  <p>Código barato também barateia a decisão de jogar código fora.</p>

</blockquote>
<p>Eu descartei uma reconstrução com cerca de 250 mil linhas porque ela não entregava o resultado que eu queria. A solução útil ficou muito menor: preservei o jogo original, substituí as bordas que envelheceram e acrescentei as melhorias que o hardware moderno permite.</p>
<p>Antigamente, fazer isso à mão seria tão demorado que eu provavelmente aceitaria os 800x600, o Xwayland preso e a ponte de teclado. Agora consegui devolver uma experiência moderna a um jogo fora de catálogo em menos de dois dias.</p>
<p>O ganho prático da engenharia reversa com IA está em tornar viável a correção daquelas incompatibilidades pequenas e cumulativas que deixavam software antigo preso ao computador de uma única pessoa. Pedir um PC port completo continua longe de ser suficiente.</p>
<p>O trabalho continua. Frame pacing e escolha de vsync, filtragem anisotrópica, mipmapping quando os dados permitirem, borderless com Alt-Tab seguro, rumble, remapeamento de botões e mais detalhes à distância já estão no plano da próxima rodada. O launcher de hoje é experimental; volte nas releases porque ainda quero tirar bastante coisa desse executável de 1998.</p>
]]></content:encoded><category>games</category><category>retrocomputacao</category><category>linux</category><category>engenharia-de-software</category></item><item><title>LLM Benchmark v4: Gemini 4 Argon, GPT Sol 6.1 Terra, Kimi K3.1 Pro, Claude Sonnet 5.5</title><link>https://akitaonrails.github.io/2026/10/28/llm-benchmark-v4-gemini-4-argon-gpt-sol-6-1-terra-kimi-k3-1-pro-claude-sonnet-5-5/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/28/llm-benchmark-v4-gemini-4-argon-gpt-sol-6-1-terra-kimi-k3-1-pro-claude-sonnet-5-5/</guid><pubDate>Wed, 28 Oct 2026 10:00:00 -0300</pubDate><description>&lt;p&gt;Passei as últimas semanas falando bastante de LLM open source, hardware local, B300, RTX 5090, Qwen, Basalt. Enquanto isso, os laboratórios comerciais continuaram soltando modelo novo como se ninguém precisasse dormir.&lt;/p&gt;
&lt;p&gt;A &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;última atualização do ranking comercial&lt;/a&gt; tinha adicionado Opus 5.5, GPT 6 Sol e Luna, MiMo 2.6 Pro e Grok 4.7. A conclusão já começava a aparecer: as versões novas estavam melhorando principalmente custo e velocidade. Qualidade tinha virado uma briga de poucos pontos, com modelos da mesma família trocando de posição entre uma rodada e outra.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Passei as últimas semanas falando bastante de LLM open source, hardware local, B300, RTX 5090, Qwen, Basalt. Enquanto isso, os laboratórios comerciais continuaram soltando modelo novo como se ninguém precisasse dormir.</p>
<p>A <a href="/2026/09/23/llm-benchmark-v4-opus-5-5-gpt-6-sol-luna-mimo-2-6-grok-4-7/">última atualização do ranking comercial</a> tinha adicionado Opus 5.5, GPT 6 Sol e Luna, MiMo 2.6 Pro e Grok 4.7. A conclusão já começava a aparecer: as versões novas estavam melhorando principalmente custo e velocidade. Qualidade tinha virado uma briga de poucos pontos, com modelos da mesma família trocando de posição entre uma rodada e outra.</p>
<p>Agora entraram <strong>Gemini 4 Argon, GPT-6.1 Sol, GPT-6.1 Terra, Kimi K3.1 Pro, Claude Sonnet 5.5 e Muse Wonder</strong>. Os seis caíram no Tier A.</p>
<p>E acho que esta será a última atualização que faço do ranking comercial, ou uma das últimas. Os modelos ficaram próximos demais.</p>
<h2>Lembrando o que este benchmark mede<span class="hx:absolute hx:-mt-20" id="lembrando-o-que-este-benchmark-mede"></span>
    <a href="#lembrando-o-que-este-benchmark-mede" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">llm-coding-benchmark</a> não tenta medir &ldquo;inteligência geral&rdquo;. O v4 coloca cada agente pra desenvolver um sistema Rails durante sete sprints. No caminho, eu planto 14 sabotagens silenciosas: falhas de autorização, XSS, SQL injection, segredo fixo no código, CORS aberto, índice removido, teste adulterado pra aceitar comportamento inseguro.</p>
<p>Se o modelo encontra e corrige sozinho, recebe o peso inteiro. Se só conserta depois que a sabotagem é revelada, recebe 40%. Se nem depois disso resolve, recebe zero. A pontuação final é normalizada pra 100.</p>
<p>Portanto, esta é uma régua estreita de <strong>vigilância durante programação agentic de longa duração</strong>. Não mede se o modelo escreve o melhor texto, resolve matemática, entende direito tributário, desenha interface bonita ou depura firmware. Nem sequer mede tudo que importa em programação.</p>
<p>Nenhum benchmark consegue fazer isso, inclusive os mais populares. Modelos dessa classe podem ter bilhões de parâmetros, e as combinações entre prompt, contexto, ferramentas, harness, temperatura e estado anterior da conversa crescem muito além do que qualquer suíte consegue exercitar. Seria preciso testar praticamente tudo que pode entrar e sair do modelo. É impossível.</p>
<p>Por isso, discutir se o benchmark A é &ldquo;melhor&rdquo; que o benchmark B costuma começar pela pergunta errada. Cada um ilumina uma superfície minúscula. Testes estilo LeetCode e o <a href="https://arxiv.org/abs/2107.03374"target="_blank" rel="noopener">HumanEval</a> medem resolução de problemas curtos e bem definidos. SWE-bench mede correção de issues em repositórios existentes.</p>
<p>O meu acompanha um projeto crescendo por várias horas, com contexto acumulado, decisões anteriores e defeitos plantados que simulam o tipo de problema deixado por outro programador, outra equipe, uma dependência ou até pelo próprio agente.</p>
<p>Essa é uma situação mais próxima da programação real que eu faço, mas continua sendo apenas <strong>o meu recorte</strong>. Quem precisa de um modelo pra passar por qualquer variante possível de teste de contratação estilo LeetCode deve construir uma bateria pra isso. Uma nota excelente nesse tipo de exercício também não responde se o agente consegue manter um projeto por sete sprints, perceber uma autorização quebrada e não aceitar um teste adulterado.</p>
<p>Você precisa testar o seu caso de uso, com o seu código, suas restrições e seus critérios de falha. Foi por isso que deixei o <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">framework inteiro open source</a>: qualquer pessoa pode trocar a aplicação, os prompts, as sabotagens, os pesos e o harness. O ranking publicado aqui responde às minhas perguntas. Use a estrutura pra responder às suas.</p>
<p>Tem mais: já medi o mesmo GPT 5.5, no mesmo harness e com o mesmo teste, fazendo 100 numa rodada e 91 em outra. Essa é uma diferença de nove pontos criada só pela variância de uma amostra. LLMs geram respostas probabilísticas. Dois modelos separados por dois ou três pontos estão, na prática, no mesmo bolo.</p>
<blockquote>
  <p><strong>Leia Tier A como uma faixa de modelos competentes, não como pódio olímpico.</strong> O primeiro colocado desta tabela não virou automaticamente o melhor modelo do mundo.</p>

</blockquote>
<h2>Ranking comercial atualizado<span class="hx:absolute hx:-mt-20" id="ranking-comercial-atualizado"></span>
    <a href="#ranking-comercial-atualizado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra não misturar placa local, pod alugado e API, a tabela abaixo mostra apenas modelos que testei por uma rota gerenciada: assinatura, API, OAuth ou plano comercial. Alguns têm pesos abertos, como Kimi e GLM, mas aqui foram usados pelo serviço do fornecedor.</p>
<p>Os modelos desta rodada estão em <strong>negrito</strong>. Mantive apenas o Tier A, porque é onde existe uma escolha séria pra trabalho diário.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">Custo da rodada</th>
          <th style="text-align: right">Tempo</th>
          <th>Harness</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Gemini 4 Argon (high)</strong></td>
          <td style="text-align: right"><strong>100,0</strong></td>
          <td style="text-align: right"><strong>$0 marginal (OAuth)</strong></td>
          <td style="text-align: right"><strong>114 min</strong></td>
          <td>Antigravity</td>
      </tr>
      <tr>
          <td>GPT-6 Astra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">$30,55</td>
          <td style="text-align: right">100 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>Claude Opus 5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">~$71</td>
          <td style="text-align: right">145 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Claude Fable 5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">~$50</td>
          <td style="text-align: right">~85 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Claude Opus 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">$16,63</td>
          <td style="text-align: right">64 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>GPT 5.6 Sol</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">$20,37</td>
          <td style="text-align: right">317 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>GPT 5.6 Terra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">$9,52</td>
          <td style="text-align: right">80 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>GPT 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">$34,69</td>
          <td style="text-align: right">117 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>Grok 4.6</td>
          <td style="text-align: right">98,5</td>
          <td style="text-align: right">$13,00</td>
          <td style="text-align: right">67 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td><strong>GPT-6.1 Sol</strong></td>
          <td style="text-align: right"><strong>97,0</strong></td>
          <td style="text-align: right"><strong>~$8,12</strong></td>
          <td style="text-align: right"><strong>65 min</strong></td>
          <td>Codex</td>
      </tr>
      <tr>
          <td><strong>Muse Wonder 1.0</strong></td>
          <td style="text-align: right"><strong>97,0</strong></td>
          <td style="text-align: right"><strong>plano Power (flat-rate)</strong></td>
          <td style="text-align: right"><strong>126 min úteis / quase 15 dias</strong></td>
          <td>Muse Code</td>
      </tr>
      <tr>
          <td><strong>GPT-6.1 Terra</strong></td>
          <td style="text-align: right"><strong>96,8</strong></td>
          <td style="text-align: right"><strong>~$1,69</strong></td>
          <td style="text-align: right"><strong>21 min</strong></td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>Claude Fable 5.1</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">~$51</td>
          <td style="text-align: right">133 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Sakana Fugu Ultra v2</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">$122,01</td>
          <td style="text-align: right">294 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>GPT 6 Luna</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">~$0,84</td>
          <td style="text-align: right">122 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>GPT 5.6 Luna</td>
          <td style="text-align: right">95,0</td>
          <td style="text-align: right">$10,04</td>
          <td style="text-align: right">123 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>Nex N2.5 Pro</td>
          <td style="text-align: right">94,0*</td>
          <td style="text-align: right">grátis</td>
          <td style="text-align: right">480 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>GLM 5.3</td>
          <td style="text-align: right">94,0</td>
          <td style="text-align: right">plano flat-rate</td>
          <td style="text-align: right">215 min</td>
          <td>zcode</td>
      </tr>
      <tr>
          <td>DeepSeek V4.1 Flash</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">$1,21</td>
          <td style="text-align: right">172 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td><strong>Kimi K3.1 Pro</strong></td>
          <td style="text-align: right"><strong>92,11</strong></td>
          <td style="text-align: right"><strong>~$12,67</strong></td>
          <td style="text-align: right"><strong>110 min</strong></td>
          <td>Kimi CLI</td>
      </tr>
      <tr>
          <td>MiMo V2.6 Pro</td>
          <td style="text-align: right">92,0</td>
          <td style="text-align: right">$1,22</td>
          <td style="text-align: right">308 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Claude Sonnet 5</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: right">~$27</td>
          <td style="text-align: right">112 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>GPT 6 Sol</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: right">~$8,35</td>
          <td style="text-align: right">73 min</td>
          <td>Codex</td>
      </tr>
      <tr>
          <td>Gemini 3.8 Flash (OpenRouter)</td>
          <td style="text-align: right">90,5</td>
          <td style="text-align: right">$15,98</td>
          <td style="text-align: right">97 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Gemini 3.8 Flash (Antigravity)</td>
          <td style="text-align: right">89,5</td>
          <td style="text-align: right">$0 marginal (OAuth)</td>
          <td style="text-align: right">120 min</td>
          <td>Antigravity</td>
      </tr>
      <tr>
          <td>Muse Spark 1.3</td>
          <td style="text-align: right">88,75</td>
          <td style="text-align: right">$13,31</td>
          <td style="text-align: right">150 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td><strong>Claude Sonnet 5.5</strong></td>
          <td style="text-align: right"><strong>88,0</strong></td>
          <td style="text-align: right"><strong>~$3,90</strong></td>
          <td style="text-align: right"><strong>27 min</strong></td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Grok 4.5</td>
          <td style="text-align: right">88,0</td>
          <td style="text-align: right">$6,19</td>
          <td style="text-align: right">48 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Claude Opus 4.6</td>
          <td style="text-align: right">87,5</td>
          <td style="text-align: right">$25,64</td>
          <td style="text-align: right">89 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Kimi K2.7</td>
          <td style="text-align: right">87,25</td>
          <td style="text-align: right">$7,75</td>
          <td style="text-align: right">175 min</td>
          <td>Kimi CLI</td>
      </tr>
      <tr>
          <td>MiMo V2.5 Pro</td>
          <td style="text-align: right">86,5</td>
          <td style="text-align: right">$1,03</td>
          <td style="text-align: right">158 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash</td>
          <td style="text-align: right">86,5</td>
          <td style="text-align: right">$1,17</td>
          <td style="text-align: right">149 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>DeepSeek V4 Flash</td>
          <td style="text-align: right">86,0</td>
          <td style="text-align: right">$0,97</td>
          <td style="text-align: right">111 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Claude Opus 4.8</td>
          <td style="text-align: right">86,0</td>
          <td style="text-align: right">~$37</td>
          <td style="text-align: right">87 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Claude Sonnet 4.6</td>
          <td style="text-align: right">85,75</td>
          <td style="text-align: right">$18,64</td>
          <td style="text-align: right">91 min</td>
          <td>Claude Code</td>
      </tr>
      <tr>
          <td>Kimi K3</td>
          <td style="text-align: right">85,0</td>
          <td style="text-align: right">$13,79</td>
          <td style="text-align: right">148 min</td>
          <td>Kimi CLI</td>
      </tr>
      <tr>
          <td>DeepSeek V4 Flash 0731</td>
          <td style="text-align: right">85,0</td>
          <td style="text-align: right">$1,94</td>
          <td style="text-align: right">194 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash</td>
          <td style="text-align: right">84,25</td>
          <td style="text-align: right">plano flat-rate</td>
          <td style="text-align: right">296 min</td>
          <td>zcode</td>
      </tr>
      <tr>
          <td>DeepSeek V4 Pro 0813</td>
          <td style="text-align: right">84,0</td>
          <td style="text-align: right">$4,49</td>
          <td style="text-align: right">152 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Step 3.7 Flash</td>
          <td style="text-align: right">83,75</td>
          <td style="text-align: right">$4,15</td>
          <td style="text-align: right">118 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Grok 4.7</td>
          <td style="text-align: right">83,5</td>
          <td style="text-align: right">$27,94</td>
          <td style="text-align: right">120 min</td>
          <td>OpenCode</td>
      </tr>
      <tr>
          <td>Genesys PI House</td>
          <td style="text-align: right">83,5</td>
          <td style="text-align: right">~$460</td>
          <td style="text-align: right">68 min</td>
          <td>OpenCode</td>
      </tr>
  </tbody>
</table>
<p>Os custos com <code>~</code> são equivalentes calculados pela contagem de tokens e pela tabela pública, não a fatura que recebi. Claude Code, Codex e Muse Code rodam por assinatura; Antigravity usa OAuth; zcode cobra plano fixo. No Wonder, 126 minutos somam apenas as sete execuções concluídas. As tentativas travadas e as esperas de quota ficaram fora. Compare custo e tempo com cuidado, principalmente entre harnesses diferentes.</p>
<p>Terra e Kimi tiveram o item #8 marcado como não aplicável porque o estado exato daquele teste não permitia uma avaliação justa. O denominador deles ficou em 38, em vez de 40, e a nota foi normalizada. Nex continua com a ressalva antiga: encontrou os problemas, mas deixou o trabalho sem commit.</p>
<h2>O que cada modelo mostrou<span class="hx:absolute hx:-mt-20" id="o-que-cada-modelo-mostrou"></span>
    <a href="#o-que-cada-modelo-mostrou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Gemini 4 Argon: finalmente chegou no topo<span class="hx:absolute hx:-mt-20" id="gemini-4-argon-finalmente-chegou-no-topo"></span>
    <a href="#gemini-4-argon-finalmente-chegou-no-topo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Argon foi o único desta leva que pegou as 14 sabotagens antes da revelação: <strong>40 de 40, nota 100</strong>. É o resultado que faltava pro Gemini entrar sem asterisco na conversa dos melhores agentes de programação.</p>
<p>O <a href="https://blog.google/intl/en-mena/product-updates/explore-get-answers/gemini-4-argon-built-to-solve-your-hardest-problems/"target="_blank" rel="noopener">anúncio oficial do Gemini 4 Argon</a> posiciona o modelo justamente pra problemas difíceis e disponibiliza acesso pelo Antigravity e pela API. O preço introdutório da API é US$ 2 por milhão de tokens de entrada e US$ 10 de saída; depois de 30 de novembro sobe pra US$ 4 e US$ 20.</p>
<p>No meu teste, usei Antigravity pela assinatura, então o custo marginal foi zero. Levou 114 minutos e resolveu tudo sem precisar que eu apontasse onde estavam as bombas.</p>
<h3>GPT-6.1 Sol: quase perfeito, sem desperdício<span class="hx:absolute hx:-mt-20" id="gpt-61-sol-quase-perfeito-sem-desperdício"></span>
    <a href="#gpt-61-sol-quase-perfeito-sem-desperd%c3%adcio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Sol fez <strong>97,0</strong>. Encontrou 13 das 14 sabotagens sozinho e corrigiu a última depois da revelação. Levou 65 minutos, praticamente o mesmo tempo do Opus 5.5 e bem menos que os 317 minutos do antigo GPT 5.6 Sol.</p>
<p>Pela <a href="https://developers.openai.com/api/docs/changelog?mw_entry=2026-10-20-c57e1ddc3363933d"target="_blank" rel="noopener">tabela oficial da OpenAI</a>, custa US$ 2 por milhão de tokens de entrada e US$ 10 de saída. Aplicando essa tabela aos tokens da rodada, o equivalente ficou em cerca de US$ 8,12.</p>
<p>Não fez 100. Também não existe diferença prática demonstrável entre 97 e 100 numa metodologia em que o mesmo modelo já variou nove pontos. O dado útil é outro: Sol continua forte e ficou rápido.</p>
<h3>Muse Wonder: excelente modelo, experiência péssima<span class="hx:absolute hx:-mt-20" id="muse-wonder-excelente-modelo-experiência-péssima"></span>
    <a href="#muse-wonder-excelente-modelo-experi%c3%aancia-p%c3%a9ssima" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Muse Wonder também fez <strong>97,0</strong>. Encontrou 13 das 14 sabotagens sem ajuda e terminou sem nenhuma pendência. A única perda veio no item composto de desempenho: corrigiu o N+1 sozinho, mas só restaurou o índice <code>messages(role, created_at)</code> depois da revelação. Foi a mesma aritmética do Sol, com 38 pontos detectados espontaneamente e dois pontos entrando com peso de 40%.</p>
<p>O resultado técnico foi forte. Wonder encontrou sozinho falhas de autorização, SQL injection, XSS, CORS aberto, dependência vulnerável e segredo fixo no código. Também adicionou testes de regressão. Olhando apenas o código entregue, pertence ao topo do Tier A.</p>
<h4>Quinze dias pra terminar um teste de duas horas<span class="hx:absolute hx:-mt-20" id="quinze-dias-pra-terminar-um-teste-de-duas-horas"></span>
    <a href="#quinze-dias-pra-terminar-um-teste-de-duas-horas" class="subheading-anchor" aria-label="Permalink for this section"></a></h4><p>Comecei a rodada em <strong>13 de outubro</strong>. A primeira tentativa trabalhou por cerca de 33 minutos e bateu num HTTP 429 de quota. Fiz upgrade pro plano <strong>Power</strong>, anunciado pela <a href="https://dev.meta.ai/products/muse-code"target="_blank" rel="noopener">Meta por US$ 50 mensais e 20 vezes o uso do plano básico</a>, e ainda assim voltei a bater no limite. Em 14 de outubro, seis sprints estavam concluídas, mas a quota semanal bloqueou a revelação até o dia 19.</p>
<p>Deixei o benchmark de lado por alguns dias depois disso e só rodei a última sprint hoje, 28 de outubro. Minha pausa também entra nessa conta de quase 15 dias. A medida comparável da tabela é <strong>126 minutos de execuções bem-sucedidas</strong>. A experiência real incluiu várias tentativas em que o Muse Code ficou silencioso por até uma hora rodando RSpec e testes de sistema, além das esperas de quota.</p>
<blockquote>
  <p><strong>A nota do modelo foi 97. A experiência do produto foi a pior de todo o benchmark v4.</strong></p>

</blockquote>
<p>A própria Meta viu o problema. Em 12 de outubro, Alexandr Wang <a href="https://x.com/alexandr_wang/status/2109714291062620560"target="_blank" rel="noopener">reconheceu um bug</a> que fazia Wonder e Wonder Full consumirem quota rápido demais; a Meta corrigiu e resetou os limites. Em 14 de outubro, a versão 1.4.8 <a href="https://x.com/mjdouglas/status/2110466975474319865"target="_blank" rel="noopener">recebeu mais correções de eficiência</a>, incluindo um erro que mandava parte dos usuários em Contributor mode pros modelos cobrados integralmente. Houve outro reset no dia seguinte.</p>
<p>Existe uma ressalva importante no meu ambiente. O benchmark isola <code>HOME</code> e <code>XDG_CONFIG_HOME</code> pra evitar que skills ou plugins pessoais contaminem o teste. A <a href="https://dev.meta.ai/docs/muse-code/subscriptions"target="_blank" rel="noopener">documentação atual da Meta</a> diz que <code>muse exec</code> sem arquivo de configurações salvo inicia com Contributor mode desligado, o que oferece menos uso em troca de não entregar o código pra treinamento. Esse detalhe pode ter reduzido minha quota. Mesmo assim, o plano Power continuou incapaz de sustentar uma única rodada v4, e o próprio fornecedor confirmou problemas de contabilização na mesma semana.</p>
<p>Minha conclusão fica restrita ao que medi: <strong>Muse Wonder é muito competente, mas eu não confiaria hoje na assinatura e no harness pra uma sessão longa sem interrupção</strong>. Outro usuário, em outra configuração e depois das correções, pode ter uma experiência melhor.</p>
<h3>GPT-6.1 Terra: a relação custo, velocidade e qualidade mais interessante<span class="hx:absolute hx:-mt-20" id="gpt-61-terra-a-relação-custo-velocidade-e-qualidade-mais-interessante"></span>
    <a href="#gpt-61-terra-a-rela%c3%a7%c3%a3o-custo-velocidade-e-qualidade-mais-interessante" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Terra terminou com <strong>96,8</strong>, praticamente empatado com Sol, em <strong>21 minutos</strong>. O custo equivalente foi só <strong>US$ 1,69</strong>.</p>
<p>A <a href="https://developers.openai.com/api/docs/models/gpt-6.1-terra"target="_blank" rel="noopener">página oficial do GPT-6.1 Terra</a> lista US$ 1 por milhão de tokens de entrada e US$ 5 de saída, metade do Sol. Ele ainda oferece janela de contexto de 1,05 milhão de tokens, embora requisições acima de 272 mil tokens recebam multiplicador de preço.</p>
<p>Num dos itens, o Terra percebeu parte do problema sozinho, mas só fechou a correção inteira na revelação. Foi isso que tirou os poucos pontos. Mesmo assim, olhando o que interessa pra trabalhar, <strong>Terra é o resultado mais forte desta rodada</strong>: qualidade no mesmo bairro do Sol, três vezes mais rápido e cerca de cinco vezes mais barato nesta execução.</p>
<h3>Kimi K3.1 Pro: competitivo, mas precisa correr mais<span class="hx:absolute hx:-mt-20" id="kimi-k31-pro-competitivo-mas-precisa-correr-mais"></span>
    <a href="#kimi-k31-pro-competitivo-mas-precisa-correr-mais" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O Kimi K3.1 Pro fez <strong>92,11</strong>, sem deixar nenhuma sabotagem sem correção no final. Dois grupos de problemas só foram fechados depois da revelação. O lado ruim foi o tempo: <strong>110 minutos</strong>, quase cinco vezes o Terra, com custo equivalente de <strong>US$ 12,67</strong>.</p>
<p>O <a href="https://www.kimi.com/en/blog/kimi-k3-1-pro"target="_blank" rel="noopener">K3.1 Pro</a> tem contexto de até 1 milhão de tokens e será disponibilizado com pesos abertos. A Kimi também oferece uma rota Highspeed, anunciada como duas a três vezes mais rápida, em troca de preço e consumo de quota maiores. A <a href="https://www.kimi.com/resources/kimi-k3-1-pro-pricing"target="_blank" rel="noopener">tabela pública</a> cobra ¥13 de entrada e ¥68 de saída por milhão de tokens na rota regular; a Highspeed dobra esses valores.</p>
<p>Eu gosto do Kimi e uso no dia a dia. A nota confirma que ele está no grupo competente. O gargalo agora é latência. Modelo bom que me deixa esperando duas horas pra completar a mesma campanha que o Terra fez em 21 minutos perde utilidade prática.</p>
<h3>Claude Sonnet 5.5: o score isolado conta a história errada<span class="hx:absolute hx:-mt-20" id="claude-sonnet-55-o-score-isolado-conta-a-história-errada"></span>
    <a href="#claude-sonnet-55-o-score-isolado-conta-a-hist%c3%b3ria-errada" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sonnet 5.5 fez <strong>88,0</strong>. Pegou a maior parte sozinho, resolveu mais alguns itens na revelação e não terminou de corrigir uma sabotagem composta: fechou a vulnerabilidade, mas deixou um teste de guarda adulterado com <code>assert true</code>. Esse tipo de detalhe é exatamente o que meu teste tenta encontrar. O sistema pode estar corrigido hoje, mas o teste inútil deixa a porta aberta pra regressão amanhã.</p>
<p>Ao mesmo tempo, ele concluiu tudo em <strong>27 minutos</strong>, com custo equivalente de <strong>US$ 3,90</strong>. A <a href="https://www.anthropic.com/claude-sonnet-5-5"target="_blank" rel="noopener">Anthropic posiciona o Sonnet 5.5</a> como modelo de trabalho diário, com Opus reservado pra decisões mais difíceis. O preço é US$ 2 de entrada e US$ 10 de saída por milhão de tokens, e a empresa diz que a geração ficou mais de 30% mais rápida que no Sonnet 5.</p>
<p>Então não, 88 não transforma Sonnet em modelo ruim. Continua Tier A, é rápido e custa pouco. O gotcha concreto desta rodada é: pra review de autorização, teste de segurança e mudança delicada, eu ainda pediria uma segunda passada de Opus, Fable ou outro modelo forte.</p>
<h2>O modelo mais caro não precisa ficar ligado o dia inteiro<span class="hx:absolute hx:-mt-20" id="o-modelo-mais-caro-não-precisa-ficar-ligado-o-dia-inteiro"></span>
    <a href="#o-modelo-mais-caro-n%c3%a3o-precisa-ficar-ligado-o-dia-inteiro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Essa rodada reforçou uma mudança importante de hábito. Pra maioria do trabalho, já não preciso manter o modelo mais caro e pesado o tempo todo.</p>
<p>CRUD, refactor, teste, documentação, front-end comum, integração de API, migração de banco: Sonnet 5.5 e GPT-6.1 Terra já dão conta. Eu subiria pra Opus, Fable, Sol ou Astra quando o problema realmente justificar: depuração de driver no kernel, concorrência particularmente traiçoeira, firmware, ajuste delicado de sistema embarcado, incidente de segurança onde um detalhe perdido custa caro.</p>
<p>Usar Fable pra trocar a cor de um botão é como ligar uma escavadeira pra plantar manjericão. Funciona. Só não faz sentido gastar quota nisso.</p>
<p>Até os open source chegaram perto. No <a href="/2026/10/27/llm-benchmark-melhores-llms-open-source-hardware-caseiro/">teste recente de Qwen3.8 Flash-Next numa única RTX 5090</a>, a versão Q2_0 fez 100 nas seis rodadas do teste difícil, três no Strata e três no Basalt. Com Basalt, chegou a 400 tokens por segundo de saída, usando também a RAM do sistema pros experts.</p>
<p>Aquele teste é bem mais fácil que o v4, portanto as notas não podem ser misturadas. Mesmo no v4 completo, porém, já tivemos Qwen local na faixa de Tier A. A distância deixou de ser um abismo.</p>
<h2>O gargalo mudou: velocidade e quota<span class="hx:absolute hx:-mt-20" id="o-gargalo-mudou-velocidade-e-quota"></span>
    <a href="#o-gargalo-mudou-velocidade-e-quota" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu uso os modelos do topo todo dia e troco entre eles no mesmo projeto há semanas. Claude de manhã, Codex depois, Kimi quando a quota aperta, às vezes Gemini. Ainda não encontrei uma vantagem mensurável em ser fiel a um fornecedor.</p>
<p>O que eu sinto imediatamente é <strong>velocidade</strong>. GLM 5.3 e DeepSeek V4.1 são bons, mas parecem lentos quando coloco do lado de Terra ou Sonnet. Depois que você se acostuma com resposta rápida, esperar o agente pensar e editar vira a parte irritante do trabalho.</p>
<p>O outro limite é quota. Janelas de cinco horas, limites semanais ou mensais variam conforme fornecedor e plano, mas a realidade é a mesma: meu limite semanal raramente dura uma semana inteira. Fable e Astra queimam a reserva ainda mais rápido. Eu troco porque preciso, não porque quero fazer coleção de logo no terminal.</p>
<p>Foi exatamente pra isso que criei o <a href="https://aimemory.sh"target="_blank" rel="noopener">ai-memory</a>. A memória do projeto não deveria pertencer ao Claude, Codex, Kimi ou Gemini. Com <code>ai-memory run</code>, eu continuo a mesma linha de trabalho em outro harness sem fazer handoff manual, e as decisões consolidadas sobrevivem às compactações de contexto.</p>
<blockquote>
  <p><strong>Modelo e harness são alugados. A sessão, as decisões e a memória do projeto precisam continuar sendo suas.</strong></p>

</blockquote>
<p>Já expliquei a arquitetura e o formato aberto na <a href="/2026/09/02/ai-memory-2-0-melhor-sistema-memoria-agentes-e-times/">versão 2.0 do ai-memory</a>. Quanto mais equivalentes os modelos ficam, mais importante se torna poder trocar sem atrito.</p>
<h2>E os benchmarks famosos?<span class="hx:absolute hx:-mt-20" id="e-os-benchmarks-famosos"></span>
    <a href="#e-os-benchmarks-famosos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Continue olhando <a href="https://www.swebench.com/"target="_blank" rel="noopener">SWE-bench</a>, que usa issues reais de projetos no GitHub, e <a href="https://www.tbench.ai/"target="_blank" rel="noopener">Terminal-Bench</a>, que mede agentes resolvendo tarefas num terminal. Eles ajudam a descobrir regressão, melhora em uso de ferramentas e algum edge case onde uma família ainda sofre.</p>
<p>Só não confunda isso com o seu trabalho. SWE-bench Verified tem 500 problemas selecionados. Terminal-Bench v4 tem dezenas de tarefas difíceis e revisadas. Nenhum deles sabe como é o seu monorepo, o nível dos seus testes, a qualidade da sua documentação, a biblioteca interna da sua empresa ou aquela API legada que ninguém ousa tocar.</p>
<p>Meu benchmark também não sabe.</p>
<p>Se você precisa escolher modelo pra uma restrição muito específica, construa seu próprio teste com exemplos reais. Se não precisa, escolha qualquer Tier A, use por uma semana e siga trabalhando.</p>
<h2>E o tal GLM 5.5?<span class="hx:absolute hx:-mt-20" id="e-o-tal-glm-55"></span>
    <a href="#e-o-tal-glm-55" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem rumor circulando de &ldquo;GLM 5.5 chegando&rdquo;. Até hoje, 28 de outubro, <strong>não existe anúncio oficial com esse nome, data ou preço</strong>. A <a href="https://docs.z.ai/release-notes/new-released"target="_blank" rel="noopener">página oficial de releases da Z.ai</a> continua listando a família GLM 5.3 como a mais recente.</p>
<p>Jie Tang, fundador da Z.ai, respondeu <em>&ldquo;won&rsquo;t take that long&rdquo;</em> quando Elon Musk estimou o primeiro trimestre de 2027 pra China alcançar modelos do nível do Fable. Isso sugere que ele espera esse nível antes, mas não promete um lançamento da Z.ai, muito menos um modelo chamado GLM 5.5 até dezembro. Não vou transformar rumor em linha de ranking.</p>
<p>Kimi K3.1 Pro já está no Tier A. Qwen e GLM já cabem em hardware próprio com quantização. Gemini finalmente encostou nos líderes.</p>
<p>Sonnet e Terra fazem trabalho diário por muito menos. A competição está comprimindo tudo pro mesmo patamar.</p>
<h2>Commoditizou. Ótimo.<span class="hx:absolute hx:-mt-20" id="commoditizou-ótimo"></span>
    <a href="#commoditizou-%c3%b3timo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Não espero mais saltos enormes a cada versão comercial. Espero modelo um pouco mais rápido, barato, econômico em token, com quota melhor e um ou outro edge case corrigido. Isso é maturidade de mercado.</p>
<p>Meu conselho hoje é diferente do conselho de alguns meses atrás:</p>
<ol>
<li>Escolha qualquer modelo Tier A que caiba no seu plano.</li>
<li>Use Sonnet ou Terra no trabalho comum.</li>
<li>Guarde Opus, Fable, Sol ou Astra pros problemas que merecem.</li>
<li>Mantenha uma segunda opção pra quando a quota acabar.</li>
<li>Instale <a href="https://aimemory.sh"target="_blank" rel="noopener">ai-memory</a> pra não ficar preso ao histórico de nenhum harness.</li>
<li>Pare de acompanhar ranking como campeonato de futebol.</li>
</ol>
<p>Benchmark continua útil pra detectar quando uma família alcançou o grupo de cima, como o Argon acabou de fazer. Depois disso, a diferença entre primeiro, quarto e décimo lugar é pequena demais, ruidosa demais e dependente demais da tarefa.</p>
<p>As LLMs de fronteira viraram commodity. Você não está perdendo nenhuma revolução porque passou uma semana sem testar o lançamento da terça-feira. Escolha uma boa, mantenha suas opções abertas e volte a programar.</p>
<h2>Um ranking mais amplo: Engineering Breadth Index<span class="hx:absolute hx:-mt-20" id="um-ranking-mais-amplo-engineering-breadth-index"></span>
    <a href="#um-ranking-mais-amplo-engineering-breadth-index" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de terminar este texto, fiquei com uma pergunta melhor. O que acontece se eu combinar meu resultado com benchmarks que medem outras partes do trabalho?</p>
<p>Montei um índice secundário com quatro superfícies:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: right">Peso</th>
          <th>Benchmark</th>
          <th>O que entra na conta</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">35%</td>
          <td><a href="https://labs.scale.com/leaderboard/swe_bench_pro_public_v2?tab=full"target="_blank" rel="noopener">SWE-bench Pro V2</a></td>
          <td>Resolver issues em repositórios profissionais sem introduzir regressão</td>
      </tr>
      <tr>
          <td style="text-align: right">30%</td>
          <td>llm-coding-benchmark v4</td>
          <td>Projeto longo, contexto acumulado, vigilância e sabotagens</td>
      </tr>
      <tr>
          <td style="text-align: right">25%</td>
          <td><a href="https://www.vals.ai/benchmarks/terminal-bench-4"target="_blank" rel="noopener">Terminal-Bench 4.0</a></td>
          <td>Tarefas no terminal avaliadas por verificadores específicos de cada desafio</td>
      </tr>
      <tr>
          <td style="text-align: right">10%</td>
          <td><a href="https://livecodebench.github.io/leaderboard.html"target="_blank" rel="noopener">LiveCodeBench v6</a></td>
          <td>Problemas algorítmicos curtos e bem definidos</td>
      </tr>
  </tbody>
</table>
<p>Os percentuais brutos não podem ser somados. O líder do Terminal-Bench 4.0 faz 65,15; no meu, vários fizeram 100. Primeiro normalizei cada resultado contra o melhor resultado publicado pelo próprio benchmark naquela fotografia:</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">nota normalizada = 100 × nota do modelo / melhor nota do benchmark</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 apliquei uma média geométrica ponderada. Ela pune modelos com uma fraqueza grande em alguma dimensão, em vez de deixar uma nota perfeita esconder o buraco.</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">índice = 100 × exp(
</span></span><span class="line"><span class="cl">  0,35 × ln(SWE Pro) +
</span></span><span class="line"><span class="cl">  0,30 × ln(Akita v4) +
</span></span><span class="line"><span class="cl">  0,25 × ln(Terminal-Bench) +
</span></span><span class="line"><span class="cl">  0,10 × ln(LiveCodeBench)
</span></span><span class="line"><span class="cl">)</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 parcela entra como número entre zero e um. O índice mede capacidade. Preço, duração, tok/s e quota ficam fora e devem aparecer numa tabela de praticidade separada. Misturar qualidade com dólar faria um modelo fraco e barato parecer tecnicamente melhor.</p>
<h3>O resultado de 28 de outubro de 2026<span class="hx:absolute hx:-mt-20" id="o-resultado-de-28-de-outubro-de-2026"></span>
    <a href="#o-resultado-de-28-de-outubro-de-2026" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Já esbarramos na primeira limitação honesta. Até 28 de outubro, o leaderboard oficial do LiveCodeBench v6 ainda não mostrava resultados pros modelos desta geração. Portanto, <strong>não existe ranking oficial completo de quatro dimensões hoje</strong>.</p>
<p>Calculei o provisório com os outros três benchmarks, que cobrem 90% do peso. Usei o snapshot de 28 de outubro do SWE-bench Pro V2 e a atualização de 26 de outubro do Terminal-Bench 4.0. Renormalizei os pesos disponíveis: 38,89% pro SWE-bench Pro V2, 33,33% pro meu v4 e 27,78% pro Terminal-Bench 4.0. Só entrou modelo com resultado exato nos três. Nada de usar Kimi K3 no lugar do K3.1 ou GPT-5.6 Terra no lugar do 6.1 Terra.</p>
<h3>Ranking provisório<span class="hx:absolute hx:-mt-20" id="ranking-provisório"></span>
    <a href="#ranking-provis%c3%b3rio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th style="text-align: right">#</th>
          <th>Modelo</th>
          <th style="text-align: right">Akita v4</th>
          <th style="text-align: right">SWE Pro V2</th>
          <th style="text-align: right">Terminal 4.0</th>
          <th>Harness no SWE Pro</th>
          <th style="text-align: right">Cobertura</th>
          <th style="text-align: right">Índice</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">1</td>
          <td>GPT-6 Astra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">96,90</td>
          <td style="text-align: right">59,60</td>
          <td>Codex</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>96,6</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">2</td>
          <td>Gemini 4 Argon</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">99,22</td>
          <td style="text-align: right">57,58</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>96,6</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">3</td>
          <td>Claude Fable 5.1</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">99,10</td>
          <td style="text-align: right">58,08</td>
          <td>Claude Code</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>95,3</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">4</td>
          <td>Claude Opus 5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">99,40</td>
          <td style="text-align: right">53,53</td>
          <td>Claude Code</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>94,7</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">5</td>
          <td>Claude Sonnet 5.5</td>
          <td style="text-align: right">88,0</td>
          <td style="text-align: right">95,79</td>
          <td style="text-align: right">64,14</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>94,1</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">6</td>
          <td>GPT-6.1 Sol</td>
          <td style="text-align: right">97,0</td>
          <td style="text-align: right">96,11</td>
          <td style="text-align: right">55,05</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>93,2</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">7</td>
          <td>GPT-5.6 Sol</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">95,50</td>
          <td style="text-align: right">37,88</td>
          <td>Codex</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>84,7</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">8</td>
          <td>GLM 5.3</td>
          <td style="text-align: right">94,0</td>
          <td style="text-align: right">95,60</td>
          <td style="text-align: right">38,89</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>83,6</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">9</td>
          <td>GPT-5.6 Terra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">92,37</td>
          <td style="text-align: right">22,73</td>
          <td>Codex</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>72,5</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">10</td>
          <td>Gemini 3.8 Flash</td>
          <td style="text-align: right">89,5</td>
          <td style="text-align: right">94,86</td>
          <td style="text-align: right">19,70</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>67,9</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">11</td>
          <td>Kimi K3</td>
          <td style="text-align: right">85,0</td>
          <td style="text-align: right">97,70</td>
          <td style="text-align: right">17,17</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>65,0</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">12</td>
          <td>Claude Sonnet 5</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: right">93,15</td>
          <td style="text-align: right">10,61</td>
          <td>Claude Code</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>57,1</strong></td>
      </tr>
      <tr>
          <td style="text-align: right">13</td>
          <td>Muse Spark Open</td>
          <td style="text-align: right">69,5</td>
          <td style="text-align: right">95,02</td>
          <td style="text-align: right">6,57</td>
          <td>mini-SWE-agent</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>46,0</strong></td>
      </tr>
  </tbody>
</table>
<p>O Terminal-Bench 4.0 usa mini-SWE-agent em todas as linhas. O SWE-bench Pro V2 mistura mini-SWE-agent com os harnesses nativos mostrados na tabela. No meu v4, cada linha mantém a rota documentada naquela rodada. Gemini 3.8 Flash é a execução no Antigravity; Muse Spark Open rodou self-hosted em quatro B300, pelo OpenCode.</p>
<p>Este é um ranking de <strong>modelo nas condições publicadas por cada benchmark</strong>, com influência real do harness. Não serve pra isolar cientificamente apenas os pesos do modelo.</p>
<h3>Por que a lista para no 13<span class="hx:absolute hx:-mt-20" id="por-que-a-lista-para-no-13"></span>
    <a href="#por-que-a-lista-para-no-13" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu queria chegar pelo menos a 20 modelos, mas o cruzamento honesto acaba aqui. O SWE-bench Pro V2 publica 16 resultados. Desses, só 13 têm exatamente o mesmo modelo testado também no Terminal-Bench 4.0 e no meu v4. Os próximos candidatos esbarram numa lacuna:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Akita v4</th>
          <th style="text-align: right">SWE Pro V2</th>
          <th style="text-align: right">Terminal 4.0</th>
          <th>O que falta</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Opus 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">65,15</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>Kimi K3.1 Pro</td>
          <td style="text-align: right">92,11</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">63,64</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>GPT-6.1 Terra</td>
          <td style="text-align: right">96,8</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">49,49</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>Claude Fable 5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">41,41</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>GPT-6 Sol</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">44,44</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>DeepSeek V4.1 Flash 1017 (B300)</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">31,82</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash</td>
          <td style="text-align: right">84,25</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">37,37</td>
          <td>SWE Pro V2</td>
      </tr>
      <tr>
          <td>Muse Wonder 1.0</td>
          <td style="text-align: right">97,0</td>
          <td style="text-align: right">96,73</td>
          <td style="text-align: right">n/d</td>
          <td>Terminal 4.0</td>
      </tr>
      <tr>
          <td>Claude Haiku 5.5</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">87,23</td>
          <td style="text-align: right">36,36</td>
          <td>Akita v4</td>
      </tr>
      <tr>
          <td>Inkling</td>
          <td style="text-align: right">n/d</td>
          <td style="text-align: right">89,88</td>
          <td style="text-align: right">0,51</td>
          <td>Akita v4</td>
      </tr>
  </tbody>
</table>
<p>Eu poderia renormalizar duas notas e inventar posições 14 a 22. O número sairia bonito e enganoso. Um modelo medido só no meu v4 e no Terminal-Bench teria 55% da cobertura original; outro medido no SWE Pro e no Terminal teria 60%. Ordenar os dois como se tivessem passado pela mesma prova transformaria ausência de dado em precisão falsa.</p>
<p>Também não vou usar Kimi K3 no lugar do K3.1, GPT-5.6 Terra no lugar do 6.1 Terra ou qualquer parente da mesma família pra preencher buraco. Quando esses resultados forem publicados ou eu rodar os modelos que faltam, o índice cresce. Até lá, 13 é o limite defensável.</p>
<h3>O que mudou quando abrimos a lente<span class="hx:absolute hx:-mt-20" id="o-que-mudou-quando-abrimos-a-lente"></span>
    <a href="#o-que-mudou-quando-abrimos-a-lente" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Astra ficou 0,036 ponto à frente do Argon antes do arredondamento: 96,5955 contra 96,5591. Ambos aparecem como 96,6 na tabela e formam um empate prático. Fable 5.1, Opus 5, Sonnet 5.5 e Sol 6.1 vieram logo atrás. Diferenças menores que três pontos devem ser lidas como a mesma faixa, ainda mais misturando benchmarks com intervalos de confiança e harnesses diferentes.</p>
<p>Sonnet 5.5 é o caso mais interessante. Fez só 88 no meu teste, mas chegou a 64,14 no Terminal-Bench, quase o teto atual. O índice mais amplo puxou o modelo pra 94,1. GPT-5.6 Sol mostra o inverso: 100 no meu teste e apenas 37,88 no Terminal-Bench, terminando em 84,7.</p>
<p>Isso responde por que vale combinar superfícies. Um benchmark sozinho enxerga bem o que foi desenhado pra enxergar. Três benchmarks continuam longe de medir tudo, mas já dificultam que uma especialidade isolada se passe por competência geral.</p>
<p>Mesmo assim, a conclusão anterior continua de pé. Os seis primeiros estão comprimidos entre 93,2 e 96,6. A lente ficou mais ampla e o suposto campeão absoluto continuou sem aparecer.</p>
<h2>Conclusão: escolha um bom e trabalhe<span class="hx:absolute hx:-mt-20" id="conclusão-escolha-um-bom-e-trabalhe"></span>
    <a href="#conclus%c3%a3o-escolha-um-bom-e-trabalhe" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O índice composto reforçou a tese deste artigo. Quando ampliamos o teste, a ordem muda, algumas fraquezas aparecem e o grupo da frente continua espremido. Astra e Argon ficaram separados por 0,036 ponto. Quatro outros modelos ficaram a menos de 3,5 pontos. Com outra distribuição de pesos, outro conjunto de tarefas ou uma nova versão dos benchmarks, eles podem trocar de posição de novo.</p>
<p>Isso deixa uma decisão bem mais simples. Primeiro descubra quais modelos já atingiram a faixa competente pro seu tipo de trabalho. Depois escolha por velocidade, preço, quota, privacidade, disponibilidade e qualidade do harness. Essas diferenças afetam seu dia inteiro. Um ponto a mais numa tabela raramente afeta.</p>
<p>Ainda vale acompanhar benchmarks pra detectar regressões, descobrir quando uma família alcançou o Tier A e enxergar pontos cegos. Não vale transformar ranking em torcida ou tratar o primeiro colocado desta semana como verdade universal.</p>
<p>Hoje eu alterno entre os melhores sem cerimônia. O projeto, os testes, o Git e a memória da sessão precisam sobreviver à troca. Por isso insisto tanto no <a href="https://aimemory.sh"target="_blank" rel="noopener">ai-memory</a>: o ativo importante é o histórico do trabalho, não o fornecedor que alugou a janela de contexto. Escolha qualquer modelo competente, mantenha uma rota de fuga e volte a resolver problemas reais.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>llms</category><category>agentes-de-codigo</category><category>ai-memory</category></item><item><title>LLM Benchmark: Quais as melhores LLMs open source pra hardware caseiro?</title><link>https://akitaonrails.github.io/2026/10/27/llm-benchmark-melhores-llms-open-source-hardware-caseiro/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/27/llm-benchmark-melhores-llms-open-source-hardware-caseiro/</guid><pubDate>Tue, 27 Oct 2026 13:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/"&gt;último LLM Benchmark sobre modelos open source&lt;/a&gt;, eu fui até o limite do razoável. Aluguei B300, H200, pods com quatro GPUs, rodei quase 70 rodadas e gastei mais de US$ 6 mil na campanha inteira. Saíram candidatos muito bons, como DeepSeek V4.1 Flash, GLM 5.3 Flash e Qwen3.8 Flash-Next, todos chegando perto dos frontier no meu teste de programação.&lt;/p&gt;
&lt;p&gt;O problema continuava sendo a máquina. Um modelo de 510 GB que precisa de quatro B300 pode ser open source no GitHub, mas continua fora do alcance de quem quer rodar uma IA no escritório de casa.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/">último LLM Benchmark sobre modelos open source</a>, eu fui até o limite do razoável. Aluguei B300, H200, pods com quatro GPUs, rodei quase 70 rodadas e gastei mais de US$ 6 mil na campanha inteira. Saíram candidatos muito bons, como DeepSeek V4.1 Flash, GLM 5.3 Flash e Qwen3.8 Flash-Next, todos chegando perto dos frontier no meu teste de programação.</p>
<p>O problema continuava sendo a máquina. Um modelo de 510 GB que precisa de quatro B300 pode ser open source no GitHub, mas continua fora do alcance de quem quer rodar uma IA no escritório de casa.</p>
<p>Voltei pra minha RTX 5090 e pro Strix Halo. Desta vez, em vez de repetir o benchmark v4 inteiro em dezenas de modelos que já sabemos que não cabem ou não têm velocidade, peguei o candidato mais promissor e fui comparar <strong>as quantizações dele</strong>. Qual versão entrega qualidade suficiente sem transformar cada interação numa espera de vários minutos?</p>
<p>O resultado me surpreendeu: <strong>a versão de 2 bits ganhou</strong>. Depois repeti o teste com o <a href="https://github.com/jesdga95/basalt"target="_blank" rel="noopener">Basalt</a>, um fork do Strata especializado nas GPUs Blackwell. O modelo continuou acertando tudo e a geração chegou a <strong>400 tokens por segundo</strong>. A quantização não mudou; o engine mudou bastante o resultado.</p>
<h2>A RTX 3090 e a 4090 ganharam uma sobrevida<span class="hx:absolute hx:-mt-20" id="a-rtx-3090-e-a-4090-ganharam-uma-sobrevida"></span>
    <a href="#a-rtx-3090-e-a-4090-ganharam-uma-sobrevida" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes dos meus resultados, a comunidade descobriu uma limitação que dava pra remover. Nas GeForce RTX 20, certas operações Tensor Core FP16 com acumulação em FP32 rodam com metade da taxa teórica de emissão por ciclo das equivalentes profissionais. Nas RTX 30 e 40, o limite também atinge BF16.</p>
<p>O hardware está lá. Dois registradores internos selecionam a velocidade, e o driver normal não tem privilégio pra alterá-los.</p>
<p>O projeto <a href="https://github.com/Zagitta/gpu-unlock"target="_blank" rel="noopener">gpu-unlock</a> encontrou uma forma de mudar esses registradores antes do driver da NVIDIA assumir a placa. No Ampere, isso explora um buffer overflow no Booter assinado que autentica e carrega o GSP-RM. Nada é gravado na VBIOS. Pra voltar ao comportamento original, você remove o parâmetro de ativação e desliga a máquina por completo, porque os seletores voltam ao padrão quando a placa perde energia. Se o parâmetro continuar ativo, o módulo reaplica o unlock no próximo boot.</p>
<p>Também exige Linux, initramfs e bastante cuidado. Eu não recomendo sair instalando isso numa workstation de produção sem ler o código e entender o risco.</p>
<p>É um hack da comunidade, sem suporte oficial da NVIDIA. Você instala por sua conta e risco. O interesse pra LLM está em acelerar prefill quando o engine usa exatamente esse caminho dos Tensor Cores. Outras aplicações de compute, como alguns pipelines de geração de imagem, também podem mudar de desempenho. Jogos dependem de outros gargalos e caminhos da GPU, então não espere ganhar FPS com isso.</p>
<p>Numa RTX 3090 a 420 W, <a href="https://github.com/Zagitta/gpu-unlock/issues/85"target="_blank" rel="noopener">um teste controlado</a> mediu FP16 com acumulação em FP32 de 76,396 pra 125,006 TFLOPS; BF16 foi de 76,994 pra 127,923. Na 4090, uma implementação ainda experimental mediu aproximadamente o dobro da taxa de instruções FP16 e BF16.</p>
<p>Só que dobrar TFLOPS não dobra sua LLM. O <a href="https://dev.to/orca_forge/why-gpu-unlock-speeds-up-rtx-3090-prefill-but-barely-changes-decode-4572"target="_blank" rel="noopener">teste que motivou esta discussão</a> separou as duas fases:</p>
<ul>
<li><strong>Prefill</strong> é quando o modelo lê o prompt inteiro. Há muitos tokens disponíveis ao mesmo tempo, então o motor consegue montar operações matriciais grandes e paralelas. <a href="https://github.com/Zagitta/gpu-unlock/issues/33"target="_blank" rel="noopener">Uma 3090 ganhou 14,4%</a> numa carga de 16K tokens, enquanto uma dupla de 3080 modificadas ganhou <a href="https://github.com/Zagitta/gpu-unlock/issues/15"target="_blank" rel="noopener">33% a 36% com vLLM</a>.</li>
<li><strong>Decode</strong> é quando o modelo gera um token depois do outro. Em LLM quantizada com expert-offload, essa etapa fica limitada principalmente por banda de memória, RAM, PCIe e cache de especialistas. O ganho medido ficou dentro do ruído.</li>
</ul>
<p>O ganho varia bastante com engine, quantização e tamanho do prompt. Na dupla de 3080, o Qwen3.8 Flash-Next Q4_K_XL ganhou só 0,5% a 0,8% em 8K, mas chegou a 12% ou 13% em 32K e 128K. Num MoE IQ3_S testado numa 3090, uma carga perto de 32K não mudou. A rota de cálculo usada por uma quantização pode nem passar pelo caminho FP16/BF16 que foi liberado.</p>
<blockquote>
  <p>O gpu-unlock acelera um caminho específico dos Tensor Cores. Ele pode reduzir a espera pra ler prompts grandes, mas não transforma 24 GB em 96 GB e nem cria banda de memória.</p>

</blockquote>
<p>Mesmo com essa ressalva, 3090 e 4090 ficaram mais interessantes pra inferência local. Uma <a href="https://www.nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3090-3090ti/"target="_blank" rel="noopener">RTX 3090</a> usada ainda tem 24 GB de VRAM, NVLink na geração dela e 936 GB/s de banda. A comunidade mediu o Qwen3.8 Flash-Next em aproximadamente <a href="https://github.com/sybil-solutions/local-ai-registry/pull/212"target="_blank" rel="noopener">75 tok/s com IQ3_S e 100 tok/s com Q2_0</a> numa única 3090. Com duas placas, há relatos entre <a href="https://github.com/ruashots/flashnext-2x3090"target="_blank" rel="noopener">90 e 120 tok/s</a>, dependendo da configuração e do tamanho do contexto. Esses resultados são da comunidade, não desta rodada.</p>
<h2>Por que eu escolhi o Qwen3.8 Flash-Next<span class="hx:absolute hx:-mt-20" id="por-que-eu-escolhi-o-qwen38-flash-next"></span>
    <a href="#por-que-eu-escolhi-o-qwen38-flash-next" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No v4, os candidatos abertos que realmente me interessaram foram:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Onde rodei</th>
          <th style="text-align: right">Nota v4</th>
          <th style="text-align: right">Velocidade</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V4.1 Flash</td>
          <td>4x B300, checkpoint nativo</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">174,8 tok/s</td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash</td>
          <td>1x B300, NVFP4</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">142 tok/s</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next</td>
          <td>1x B300, NVFP4</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">195 tok/s</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next</td>
          <td>RTX 5090, IQ3_S</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: right">159,2 tok/s</td>
      </tr>
  </tbody>
</table>
<p>Os três empataram dentro da margem de ruído. Só o Qwen repetiu a qualidade numa máquina de consumidor. O modelo principal é um MoE de 125 bilhões de parâmetros, com aproximadamente 6 bilhões ativos por token, mais 51 bilhões em embeddings n-gram e uma cabeça MTP de 4 bilhões. O <a href="https://github.com/Niko1221/Strata"target="_blank" rel="noopener">Strata</a> deixa os especialistas mais usados na GPU, mantém os demais na RAM pra CPU calcular, lê a tabela n-gram do SSD e usa a camada MTP do próprio modelo pra especular os próximos tokens.</p>
<p>O backbone e o Engram do DeepSeek V4.1 somam aproximadamente 748 bilhões de parâmetros: 552 bilhões do backbone e 196 bilhões do Engram. O checkpoint publicado tem cerca de 763 bilhões no total. Eu servi a versão nativa mista FP4/FP8, de cerca de 510 GB, no pod de quatro B300.</p>
<p>Também consegui iniciar uma versão de 2 bits na 5090 com o engine dedicado do Strata. Ela gerou por volta de <strong>17 tokens por segundo</strong>. Tecnicamente roda. Pra agente de programação, está fora da disputa.</p>
<p>O checkpoint NVFP4 do GLM 5.3 Flash tinha cerca de 198 GB e foi servido na B300. Não existe neste repositório uma configuração de consumidor que eu tenha medido e possa recomendar. O GLM 4.7 Flash menor já tinha feito 24 pontos na 5090 e 33,5 na B300 no benchmark anterior. Gastar dias tentando fazer a família errada caber não produziria informação útil.</p>
<p>Também deixei Qwen 4 e Llama fora por escolha. Esta rodada tem uma pergunta estreita: <strong>qual versão do modelo que já provou qualidade funciona melhor nas duas máquinas que eu tenho?</strong></p>
<h2>O que eu testei<span class="hx:absolute hx:-mt-20" id="o-que-eu-testei"></span>
    <a href="#o-que-eu-testei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todo o código, os prompts, os testes escondidos e os resultados estão no <a href="https://github.com/akitaonrails/llm-opensource-benchmark"target="_blank" rel="noopener">llm-opensource-benchmark</a>. Testei quatro versões do Qwen3.8 Flash-Next:</p>
<table>
  <thead>
      <tr>
          <th>Quantização</th>
          <th style="text-align: right">Arquivos do modelo</th>
          <th>Ideia</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q2_0</td>
          <td style="text-align: right">~62 GiB</td>
          <td>2 bits, menor e mais agressiva</td>
      </tr>
      <tr>
          <td>IQ3_XXS</td>
          <td style="text-align: right">~71 GiB</td>
          <td>3 bits, prioriza tamanho</td>
      </tr>
      <tr>
          <td>IQ3_S</td>
          <td style="text-align: right">~78 GiB</td>
          <td>3 bits, mais precisão</td>
      </tr>
      <tr>
          <td>UD-Q4_K_XL</td>
          <td style="text-align: right">~104 GiB</td>
          <td>4 bits, maior preservação dos pesos</td>
      </tr>
  </tbody>
</table>
<p>Os arquivos do Strata incluem uma parte comum de PLE, então o tamanho total não cabe diretamente na VRAM. O engine faz o trabalho de distribuir os especialistas entre GPU e RAM. No Basalt, usei os packs próprios do projeto: um arquivo <code>.basalt</code> de 64,9 GiB pro Q2_0 e outro de 81,0 GiB pro IQ3_S. Cada arquivo já reúne pesos, tokenizer, MTP, perfil de especialistas e projetor de visão.</p>
<p>Na primeira rodada da RTX 5090, usei o backend CUDA do Strata com:</p>
<ul>
<li>4 GiB de VRAM reservados pro Hyprland, navegador e terminal;</li>
<li>cache de KV em <code>int8</code>, contexto máximo de 128K e 32K residentes na GPU;</li>
<li>cache elástico de especialistas;</li>
<li>especulação de quatro tokens com o MTP do modelo.</li>
</ul>
<p>Depois rodei Q2_0 e IQ3_S no Basalt, na mesma 5090 e com os mesmos 131.072 tokens de contexto máximo e KV em <code>int8</code>. Deixei 4.608 MiB reservados porque o Basalt contabiliza seus buffers de trabalho nessa margem. A VRAM livre ficou entre 3,6 e 4,4 GB durante os testes.</p>
<p>No Strix Halo, usei o backend <code>hip-halo</code>. Essa máquina tem 64 GB reservados pra GPU e GTT de 31 GB mapeado sobre a RAM do sistema; o limite de RAM do engine era 61,9 GiB. Como ela é headless, deixei a GPU inteira pro modelo e mantive 8 GiB de folga pro cache da conversa.</p>
<p>Nas medições do Strata são dois backends diferentes por baixo, CUDA na NVIDIA e HIP/ROCm na AMD, mas o mesmo engine e a mesma API compatível com OpenAI. O Basalt acrescenta uma comparação de engine na 5090, usando o mesmo OpenCode, os mesmos prompts e a mesma correção.</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>Comecei com um teste de velocidade em três situações:</p>
<ol>
<li>prompt frio de 4K tokens;</li>
<li>prompt frio rotulado como 32K, que o tokenizer do modelo contou como 42.400 tokens;</li>
<li>continuação da mesma conversa, reaproveitando o cache.</li>
</ol>
<p>Cada uma gera 256 tokens. O runner mede tempo até o primeiro token, leitura do prompt, geração, VRAM, RAM, potência e temperatura.</p>
<h3>O que cada número de velocidade quer dizer<span class="hx:absolute hx:-mt-20" id="o-que-cada-número-de-velocidade-quer-dizer"></span>
    <a href="#o-que-cada-n%c3%bamero-de-velocidade-quer-dizer" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quando alguém diz que uma LLM faz 200 tokens por segundo, quase sempre está falando só da geração. Uma sessão de agente passa por fases diferentes, e cada uma encosta num gargalo diferente.</p>
<p><strong>Prefill</strong> é a leitura inicial do prompt. O modelo precisa processar as instruções do harness, histórico da conversa, arquivos e sua pergunta antes de soltar a primeira palavra. Como todos esses tokens já estão disponíveis, a GPU consegue trabalhar com matrizes grandes em paralelo.</p>
<p>Essa fase aproveita melhor Tensor Cores e poder bruto de cálculo. Quanto maior o contexto, mais ela pesa.</p>
<table>
  <thead>
      <tr>
          <th>Métrica</th>
          <th>O que mede</th>
          <th>O que você sente</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Prompt tok/s</td>
          <td>Vazão do prefill, quantos tokens de entrada são lidos por segundo</td>
          <td>Tempo pra digerir contexto novo</td>
      </tr>
      <tr>
          <td>TTFT (<em>time to first token</em>)</td>
          <td>Tempo total entre enviar a requisição e receber o primeiro token</td>
          <td>A pausa antes da resposta começar</td>
      </tr>
      <tr>
          <td>Decode ou output tok/s</td>
          <td>Velocidade de geração depois do primeiro token</td>
          <td>Ritmo do texto e das chamadas de ferramenta</td>
      </tr>
      <tr>
          <td>TPOT (<em>time per output token</em>)</td>
          <td>Tempo por token gerado, normalmente em p50 e p99 (ou p95)</td>
          <td>Regularidade da resposta; percentil alto denuncia travadas</td>
      </tr>
      <tr>
          <td>Wall time</td>
          <td>Tempo da tarefa inteira, incluindo ferramentas, testes, retries e texto gerado</td>
          <td>Produtividade real do agente</td>
      </tr>
  </tbody>
</table>
<p>O <strong>KV cache</strong> guarda os estados intermediários dos tokens já processados. Numa continuação da mesma conversa, o modelo reaproveita esse estado em vez de ler tudo de novo. Por isso o teste de append chega a dezenas de milhares de prompt tokens por segundo e recebe o primeiro token em cerca de meio segundo. Esse número mede reaproveitamento de cache; comparar com prefill frio seria fraude.</p>
<p>O KV também cresce com a conversa e disputa memória com pesos, especialistas e buffers. Aqui ele está em <code>int8</code>, com 32K residentes na 5090. Contexto máximo anunciado, contexto que cabe e contexto que continua rápido são três números diferentes.</p>
<p><strong>Speculative decoding</strong> usa uma camada de draft pra propor vários tokens e o modelo principal verifica o lote. Quando a taxa de aceitação é boa, o output tok/s sobe. Se o draft erra muito, o ganho desaparece. As velocidades do Strata já incluem MTP com especulação de quatro tokens. O Basalt também usa o MTP incluído no próprio pack, com seu mecanismo de especulação e seus defaults.</p>
<p>E falta uma métrica que eu não medi aqui: <strong>concorrência</strong>. Todos estes números são de um único stream. Um servidor pode atender várias sessões e aumentar a vazão agregada, mas cada usuário tende a receber menos tokens por segundo.</p>
<p>Pra uso individual, single-stream e wall time mandam. Pra uma equipe, precisa medir sessões simultâneas e latência por usuário, como fiz no <a href="/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/">teste com B300 e H200</a>.</p>
<p>Depois vieram dois testes de programação usando OpenCode 2.1.4. O modelo recebe uma pequena base Python, trabalha em uma sessão contínua e nunca vê os testes escondidos. Tudo roda em Bubblewrap, sem rede, com filesystem limitado e apenas um gateway local que aceita as chamadas pro modelo esperado.</p>
<p>O primeiro canário tem três turnos e 9 testes escondidos. O modelo precisa implementar um analisador de logs, corrigir bugs e lidar com mudança de requisitos.</p>
<p>O teste difícil tem quatro turnos e 36 testes escondidos. É um reconciliador de pagamentos com decisões enterradas num histórico de cerca de 79K tokens, uma decisão corrigida mais tarde, propostas rejeitadas servindo de distração, dinheiro que exige decimal exato e regras novas que interagem com as anteriores. Com o system prompt do OpenCode, o primeiro turno chega perto de 83K tokens.</p>
<h3>Uma régua separada do benchmark v4<span class="hx:absolute hx:-mt-20" id="uma-régua-separada-do-benchmark-v4"></span>
    <a href="#uma-r%c3%a9gua-separada-do-benchmark-v4" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Estes canários são muito menores e mais fáceis. No v4, o modelo trabalha num aplicativo Rails por sete sprints enquanto um revisor injeta 14 defeitos de segurança e de correção. A nota mede quantos ele encontra e corrige sozinho antes da revelação. É um teste de vigilância, com muito mais código, ferramentas e oportunidades de se perder.</p>
<p>Aqui são duas bases Python pequenas, três ou quatro turnos e testes funcionais escondidos. Eu montei essa régua pra comparar <strong>quantizações do mesmo Qwen</strong> e descobrir se reduzir os bits quebra requisitos, contexto ou processo. A comparação termina aí, sem ranquear Qwen contra Opus, GPT, DeepSeek ou qualquer outro modelo.</p>
<p>A nota 100 deste artigo pertence a outra escala, sem equivalência com 100 do v4. Ela também não torna o Q2_0 “melhor” que o IQ3_S que fez 91 no v4. O v4 escolheu o Qwen como candidato; este teste menor escolhe qual arquivo do Qwen vale carregar em casa.</p>
<blockquote>
  <p>Velocidade sem qualidade só faz o modelo errar mais rápido. Por isso eu medi as duas coisas separadamente.</p>

</blockquote>
<h2>Ranking de velocidade<span class="hx:absolute hx:-mt-20" id="ranking-de-velocidade"></span>
    <a href="#ranking-de-velocidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>RTX 5090 com Strata, 4 GiB preservados pro desktop<span class="hx:absolute hx:-mt-20" id="rtx-5090-com-strata-4-gib-preservados-pro-desktop"></span>
    <a href="#rtx-5090-com-strata-4-gib-preservados-pro-desktop" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Quantização</th>
          <th style="text-align: right">Geração</th>
          <th style="text-align: right">Leitura da carga longa</th>
          <th style="text-align: right">Primeiro token</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Q2_0</strong></td>
          <td style="text-align: right"><strong>223 a 259 tok/s</strong></td>
          <td style="text-align: right"><strong>6.158 tok/s</strong></td>
          <td style="text-align: right"><strong>6,9 s</strong></td>
      </tr>
      <tr>
          <td>IQ3_XXS</td>
          <td style="text-align: right">196 a 241 tok/s</td>
          <td style="text-align: right">5.084 tok/s</td>
          <td style="text-align: right">8,3 s</td>
      </tr>
      <tr>
          <td>IQ3_S</td>
          <td style="text-align: right">158 a 208 tok/s</td>
          <td style="text-align: right">6.233 tok/s</td>
          <td style="text-align: right">6,8 s</td>
      </tr>
      <tr>
          <td>UD-Q4_K_XL</td>
          <td style="text-align: right">44 a 52 tok/s</td>
          <td style="text-align: right">2.300 tok/s</td>
          <td style="text-align: right">19 s</td>
      </tr>
  </tbody>
</table>
<h3>RTX 5090 com Basalt<span class="hx:absolute hx:-mt-20" id="rtx-5090-com-basalt"></span>
    <a href="#rtx-5090-com-basalt" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Quantização</th>
          <th style="text-align: right">Geração</th>
          <th style="text-align: right">Leitura da carga longa</th>
          <th style="text-align: right">Primeiro token</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Q2_0</strong></td>
          <td style="text-align: right"><strong>377 a 400 tok/s</strong></td>
          <td style="text-align: right"><strong>8.573 tok/s</strong></td>
          <td style="text-align: right"><strong>4,9 s</strong></td>
      </tr>
      <tr>
          <td>IQ3_S</td>
          <td style="text-align: right">223 a 231 tok/s</td>
          <td style="text-align: right">8.196 tok/s</td>
          <td style="text-align: right">5,2 s</td>
      </tr>
  </tbody>
</table>
<h3>Strix Halo, usando a GPU inteira<span class="hx:absolute hx:-mt-20" id="strix-halo-usando-a-gpu-inteira"></span>
    <a href="#strix-halo-usando-a-gpu-inteira" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Quantização</th>
          <th style="text-align: right">Geração</th>
          <th style="text-align: right">Leitura da carga longa</th>
          <th style="text-align: right">Primeiro token</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Q2_0</strong></td>
          <td style="text-align: right"><strong>55 a 65 tok/s</strong></td>
          <td style="text-align: right"><strong>371 tok/s</strong></td>
          <td style="text-align: right"><strong>114 s</strong></td>
      </tr>
      <tr>
          <td>IQ3_XXS</td>
          <td style="text-align: right">50 a 57 tok/s</td>
          <td style="text-align: right">347 tok/s</td>
          <td style="text-align: right">122 s</td>
      </tr>
      <tr>
          <td>IQ3_S</td>
          <td style="text-align: right">47 a 50 tok/s</td>
          <td style="text-align: right">338 tok/s</td>
          <td style="text-align: right">125 s</td>
      </tr>
      <tr>
          <td>UD-Q4_K_XL</td>
          <td style="text-align: right">17 tok/s num prompt de 4K</td>
          <td style="text-align: right">137 tok/s</td>
          <td style="text-align: right">não terminou</td>
      </tr>
  </tbody>
</table>
<p>A 5090 tem 32 GB de GDDR7 e <a href="https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/"target="_blank" rel="noopener">1.792 GB/s de banda</a>. O Strix tem mais capacidade total, mas seus aproximadamente 256 GB/s precisam servir o sistema inteiro. Mesmo usando o Strata nos dois, a diferença aparece principalmente no prefill: 6,9 segundos contra quase 2 minutos pra começar a responder a carga de 42.400 tokens. No Basalt, a 5090 baixou essa espera pra 4,9 segundos.</p>
<p>O Q4 foi o pior dos dois mundos. Na 5090 ele rodou devagar e o <code>systemd-oomd</code> matou o teste de qualidade. No Strix, zerou a VRAM, começou a paginar e travou a máquina até reboot. Mais bits preservam mais informação, mas ocupam mais memória e reduzem o cache quente que faz esse MoE andar.</p>
<h2>Basalt contra Strata<span class="hx:absolute hx:-mt-20" id="basalt-contra-strata"></span>
    <a href="#basalt-contra-strata" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/jesdga95/basalt"target="_blank" rel="noopener">Basalt</a> nasceu como fork do Strata, só que tomou uma direção mais específica: Linux ou Windows, CUDA 13 e GPU NVIDIA Blackwell com compute capability 12.0. A 5090 entra exatamente nesse alvo. O Strata continua muito mais abrangente e foi o que permitiu rodar o mesmo Qwen no Strix Halo com AMD.</p>
<p>Os dois resolvem o mesmo problema central. Na configuração testada, os especialistas que não cabem na VRAM ficam na RAM, enquanto a GPU mantém uma parte quente. Basalt e Strata expõem API compatível com OpenAI e Anthropic e aceleram a geração com seus drafters MTP. O Basalt empacota tudo num único arquivo e traz kernels, gerenciamento de memória e execução especulativa trabalhados especificamente pra Blackwell.</p>
<p>Este foi um A/B prático entre stacks completos, sem isolar uma única otimização. Os packs são próprios de cada engine, os defaults não são idênticos e o Basalt reservou 4.608 MiB de VRAM contra 4.096 MiB do Strata. Mantive a mesma RTX 5090, as mesmas quantizações, contexto máximo de 131K, KV em <code>int8</code>, mesmo OpenCode e os mesmos testes.</p>
<p>Nesse A/B, o Basalt ganhou em todas as métricas de velocidade e empatou nos resultados funcionais de código. O Q2_0 saiu de 223 a 259 pra 377 a 400 tokens por segundo. A leitura da carga longa foi de 6.158 pra 8.573 tokens por segundo, e a espera pelo primeiro token caiu de 6,9 pra 4,9 segundos. No IQ3_S, a geração saiu da faixa de 158 a 208 pra 223 a 231 tokens por segundo.</p>
<p>O recorte de plataforma decide a escolha. Numa RTX 5090, eu começaria pelo Basalt. No Strix, em NVIDIA anterior a Blackwell ou em outro hardware suportado, o Strata continua sendo o caminho. O Strata cobre justamente os lugares onde o Basalt nem roda.</p>
<h2>Ranking de qualidade<span class="hx:absolute hx:-mt-20" id="ranking-de-qualidade"></span>
    <a href="#ranking-de-qualidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O teste simples não separou nada:</p>
<table>
  <thead>
      <tr>
          <th>Quantização</th>
          <th style="text-align: right">RTX 5090</th>
          <th style="text-align: right">Strix Halo</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q2_0</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">100</td>
      </tr>
      <tr>
          <td>IQ3_XXS</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">100</td>
      </tr>
      <tr>
          <td>IQ3_S</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">100</td>
      </tr>
  </tbody>
</table>
<p>Todas passaram os 9 testes escondidos, preservaram os requisitos e terminaram com a suíte visível verde. Precisei aumentar a dificuldade.</p>
<p>No canário difícil, rodei Q2_0 e IQ3_S três vezes cada na 5090 com Strata:</p>
<table>
  <thead>
      <tr>
          <th>Rodada</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">Testes escondidos</th>
          <th style="text-align: right">Tempo dos 4 turnos</th>
          <th style="text-align: right">Tokens de saída</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q2_0 r1</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">5,3 min</td>
          <td style="text-align: right">70K</td>
      </tr>
      <tr>
          <td>Q2_0 r2</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">8,8 min</td>
          <td style="text-align: right">117K</td>
      </tr>
      <tr>
          <td>Q2_0 r3</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">7,4 min</td>
          <td style="text-align: right">100K</td>
      </tr>
      <tr>
          <td>IQ3_S r1</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">11,9 min</td>
          <td style="text-align: right">110K</td>
      </tr>
      <tr>
          <td>IQ3_S r2</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">13,3 min</td>
          <td style="text-align: right">127K</td>
      </tr>
      <tr>
          <td>IQ3_S r3</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">11,1 min</td>
          <td style="text-align: right">98K</td>
      </tr>
  </tbody>
</table>
<p>As duas acertaram tudo nas três rodadas. Encontraram as decisões no contexto longo, respeitaram a correção posterior, lidaram com eventos fora de ordem, chargebacks, câmbio e fizeram o refactor final. Também passaram por quantidade parecida de testes quebrados enquanto trabalhavam.</p>
<p>A diferença ficou no relógio. <strong>Q2_0 levou 7,2 minutos em média. IQ3_S levou 12,1.</strong> O modelo de 2 bits entregou o mesmo resultado em aproximadamente 60% do tempo.</p>
<p>Depois repeti as seis rodadas no Basalt:</p>
<table>
  <thead>
      <tr>
          <th>Rodada</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">Testes escondidos</th>
          <th style="text-align: right">Tempo dos 4 turnos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Q2_0 r1</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">5,9 min</td>
      </tr>
      <tr>
          <td>Q2_0 r2</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">4,2 min</td>
      </tr>
      <tr>
          <td>Q2_0 r3</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">6,3 min</td>
      </tr>
      <tr>
          <td>IQ3_S r1</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">5,8 min</td>
      </tr>
      <tr>
          <td>IQ3_S r2</td>
          <td style="text-align: right">96*</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">11,3 min</td>
      </tr>
      <tr>
          <td>IQ3_S r3</td>
          <td style="text-align: right">100</td>
          <td style="text-align: right">36/36</td>
          <td style="text-align: right">10,1 min</td>
      </tr>
  </tbody>
</table>
<p>A penalização que derrubou a segunda rodada do IQ3_S pra 96 foi um falso positivo conhecido do harness. O modelo executou <code>ps aux</code> dentro do sandbox, e o comando mostrou a linha do próprio <code>bwrap</code>, que contém caminhos do host. O detector interpretou isso como tentativa de acessar os testes escondidos e tirou quatro pontos. O modelo não usou esses caminhos, os testes escondidos só são montados depois, na correção, e os 36 passaram.</p>
<p>No Basalt, o Q2_0 levou <strong>5,4 minutos em média</strong>, contra 7,2 no Strata. O IQ3_S caiu de 12,1 pra <strong>9,0 minutos</strong>. O ganho de ponta a ponta é menor que o salto de tokens por segundo porque a sessão também gasta tempo executando ferramentas e testes, e cada rodada gerou uma quantidade diferente de texto.</p>
<p>A evidência vale para bases Python pequenas, com contexto de até uns 85K tokens, e apenas três repetições por combinação de engine e quantização no canário difícil. Quantização de 2 bits costuma sofrer primeiro em contexto acima de 100K e em bases maiores. Dentro do uso que medi, gastar mais bits não comprou nenhuma qualidade.</p>
<h2>O que cada versão mostrou<span class="hx:absolute hx:-mt-20" id="o-que-cada-versão-mostrou"></span>
    <a href="#o-que-cada-vers%c3%a3o-mostrou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Q2_0: a recomendação atual<span class="hx:absolute hx:-mt-20" id="q2_0-a-recomendação-atual"></span>
    <a href="#q2_0-a-recomenda%c3%a7%c3%a3o-atual" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Foi a quantização que gerou tokens mais rápido nas três combinações de máquina e engine, e terminou as seis rodadas difíceis com nota máxima. No prefill longo do Strata na 5090, o IQ3_S ganhou por uma margem mínima. Com Basalt, o Q2_0 chegou a 400 tokens por segundo. No Strix, 55 a 65 tokens por segundo é utilizável, embora o prefill longo cobre quase dois minutos antes da primeira resposta.</p>
<p>Eu usaria esta versão no dia a dia e guardaria a ressalva de contexto. Projeto grande, sessão chegando em 100K ou comportamento estranho? Repito o mesmo trabalho em IQ3_S antes de culpar o modelo.</p>
<h3>IQ3_XXS: ficou sem função clara<span class="hx:absolute hx:-mt-20" id="iq3_xxs-ficou-sem-função-clara"></span>
    <a href="#iq3_xxs-ficou-sem-fun%c3%a7%c3%a3o-clara" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Passou no canário simples e foi rápida, só que perdeu pro Q2_0 em velocidade e ocupa mais espaço. Eu não rodei as três repetições do teste difícil nela, então não tenho dado pra prometer uma vantagem de qualidade.</p>
<h3>IQ3_S: a opção conservadora<span class="hx:absolute hx:-mt-20" id="iq3_s-a-opção-conservadora"></span>
    <a href="#iq3_s-a-op%c3%a7%c3%a3o-conservadora" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Continua sendo a versão que eu escolheria pra validar um resultado suspeito da Q2_0. Ela já tinha feito 91 no benchmark v4 na 5090, a apenas 1,5 ponto da versão NVFP4 na B300. No Basalt, ficou entre 223 e 231 tokens por segundo, praticamente a faixa que o Q2_0 alcançava no Strata, e ainda leu prompts mais rápido.</p>
<p>O próprio Basalt mede a proximidade das quantizações contra Q8_0. No teste de 100 chunks, o IQ3_S manteve o mesmo token no topo em 87,16% das posições. É uma régua diferente dos meus canários, mas reforça seu papel de opção conservadora. Nos meus testes de código, essa precisão extra ainda não encontrou nada que o Q2_0 tivesse perdido.</p>
<h3>UD-Q4_K_XL: qualidade que não cabe não serve<span class="hx:absolute hx:-mt-20" id="ud-q4_k_xl-qualidade-que-não-cabe-não-serve"></span>
    <a href="#ud-q4_k_xl-qualidade-que-n%c3%a3o-cabe-n%c3%a3o-serve" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Era a candidata intuitiva a entregar mais fidelidade. Faltou memória pra chegar ao teste que confirmaria isso. Na 5090, caiu pra menos de um quarto da velocidade da Q2_0 e o sistema matou a rodada. No Strix, a máquina travou.</p>
<p>Serve como lembrança de que a maior quantização compatível no papel pode ser a pior configuração real. Um MoE depende do espaço que sobra pra especialistas, KV cache, buffers e o próprio sistema operacional.</p>
<h2>Qual hardware vale a pena?<span class="hx:absolute hx:-mt-20" id="qual-hardware-vale-a-pena"></span>
    <a href="#qual-hardware-vale-a-pena" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>RTX 3090 e 4090: boas se você já tem<span class="hx:absolute hx:-mt-20" id="rtx-3090-e-4090-boas-se-você-já-tem"></span>
    <a href="#rtx-3090-e-4090-boas-se-voc%c3%aa-j%c3%a1-tem" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As duas têm 24 GB, e isso limita o cache de especialistas. O gpu-unlock melhora prefill em workloads que usam o caminho liberado, mas decode continua preso em memória e offload. Uma 3090 usada, principalmente duas com NVLink, pode formar uma máquina local interessante. Eu não compraria uma 4090 nova hoje só pra isso: continua com 24 GB, custa caro e o unlock de Ada ainda é experimental.</p>
<p>Pra ambas, começaria pelo <strong>Qwen3.8 Flash-Next Q2_0</strong> e mediria meu workload. Os aproximadamente 100 tokens por segundo publicados pela comunidade numa única 3090 já colocam a placa numa faixa prática. Duas 3090 aumentam a capacidade e podem ficar entre 90 e 120 tokens por segundo, mas não dobram automaticamente a velocidade. A conta inclui consumo, calor, fonte e pelo menos 128 GB de RAM.</p>
<h3>Strix Halo: muita capacidade, pouca banda<span class="hx:absolute hx:-mt-20" id="strix-halo-muita-capacidade-pouca-banda"></span>
    <a href="#strix-halo-muita-capacidade-pouca-banda" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>É uma boa caixa headless, compacta e econômica. A memória unificada deixa rodar modelos que uma GPU de 24 ou 32 GB recusaria. Neste teste, o melhor modelo também foi o <strong>Q2_0</strong>, a 55 a 65 tokens por segundo.</p>
<p>O problema aparece toda vez que o agente lê contexto grande. Quase dois minutos pra processar a carga longa do teste acumulam rápido durante uma sessão. Eu usaria como servidor privado pra tarefas assíncronas, agentes simples e workloads onde capacidade vale mais que resposta imediata.</p>
<h3>RTX 5090: o melhor ponto caseiro hoje<span class="hx:absolute hx:-mt-20" id="rtx-5090-o-melhor-ponto-caseiro-hoje"></span>
    <a href="#rtx-5090-o-melhor-ponto-caseiro-hoje" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <a href="https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/"target="_blank" rel="noopener">RTX 5090</a> tem 32 GB, 21.760 CUDA cores e a mesma banda nominal de 1.792 GB/s da RTX PRO 6000. O que falta é capacidade: preciso reservar 4 GiB pro desktop e usar RAM e SSD pros especialistas que não cabem.</p>
<p>Com Strata, ela colocou o Q2_0 em 223 a 259 tokens por segundo e leu a carga longa a 6.158 tokens por segundo. Com Basalt, chegou a 400 e 8.573, respectivamente. É a primeira configuração local que não me faz sentir que estou esperando a máquina a cada passo. E é uma placa que também serve pra jogos, geração de imagem, vídeo e todo o resto da workstation.</p>
<h3>RTX PRO 6000: três vezes a VRAM, preço de servidor<span class="hx:absolute hx:-mt-20" id="rtx-pro-6000-três-vezes-a-vram-preço-de-servidor"></span>
    <a href="#rtx-pro-6000-tr%c3%aas-vezes-a-vram-pre%c3%a7o-de-servidor" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <a href="https://www.nvidia.com/en-us/products/workstations/professional-desktop-gpus/rtx-pro-6000/"target="_blank" rel="noopener">RTX PRO 6000 Blackwell</a> tem 96 GB de GDDR7 ECC, 24.064 CUDA cores e os mesmos 1.792 GB/s de banda. A capacidade extra deixa muito mais especialistas residentes, abre espaço pra quantização maior e comporta contexto sem disputar os últimos gigabytes com o desktop.</p>
<p>Ela custa <a href="https://marketplace.nvidia.com/en-us/enterprise/laptops-workstations/nvidia-rtx-pro-6000-blackwell-workstation-edition/"target="_blank" rel="noopener">US$ 16 mil na loja da NVIDIA</a>, oito vezes o preço sugerido de US$ 1.999 da 5090. Eu ainda não rodei este benchmark nela, então não vou inventar uma velocidade. A banda igual à 5090 significa que três vezes a memória não viram três vezes mais tokens por segundo. O ganho está em manter mais coisa na placa e evitar o caminho lento de RAM e PCIe.</p>
<p>Pra uso individual, a conta da 5090 fecha melhor. A PRO 6000 começa a fazer sentido quando 96 GB resolvem um modelo que você realmente precisa, quando ECC e suporte profissional importam ou quando várias sessões vão dividir a mesma placa.</p>
<h2>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></h2><table>
  <thead>
      <tr>
          <th>Hardware</th>
          <th>Melhor escolha hoje</th>
          <th>Experiência</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1x RTX 3090/4090, 24 GB</td>
          <td>Qwen3.8 Flash-Next Q2_0</td>
          <td>Viável com offload; confira prefill e decode no seu stack</td>
      </tr>
      <tr>
          <td>Strix Halo</td>
          <td>Qwen3.8 Flash-Next Q2_0</td>
          <td>55 a 65 tok/s; prompt longo demora</td>
      </tr>
      <tr>
          <td>RTX 5090</td>
          <td><strong>Qwen3.8 Flash-Next Q2_0 no Basalt</strong></td>
          <td>377 a 400 tok/s; melhor equilíbrio que medi</td>
      </tr>
      <tr>
          <td>RTX PRO 6000</td>
          <td>Provavelmente IQ3_S ou maior</td>
          <td>Capacidade excelente, sem medição minha nesta rodada</td>
      </tr>
  </tbody>
</table>
<p>O open source local finalmente entrou numa faixa prática. Uma única 5090 roda um modelo que fez Tier A no meu benchmark v4 e, com Q2_0 no Basalt, chegou a 400 tokens por segundo no canário. O Strix também ficou muito melhor do que os 7,7 tokens por segundo do Qwen denso que eu tinha testado antes.</p>
<p>Ainda existem limites claros. O contexto custa memória, o desktop precisa da mesma VRAM e qualquer especialista fora da placa atravessa PCIe ou cai na RAM. DeepSeek a 17 tokens por segundo mostra que “rodou” continua sendo uma métrica inútil. Se cada iteração leva várias horas, a qualidade do modelo deixa de importar porque ninguém consegue trabalhar desse jeito.</p>
<blockquote>
  <p>Pra hardware caseiro, o melhor modelo é o que mantém qualidade suficiente na velocidade que deixa você iterar. Nesta rodada, foi o Qwen3.8 Flash-Next Q2_0 no Basalt, numa RTX 5090.</p>

</blockquote>
<p>O <a href="https://github.com/akitaonrails/llm-opensource-benchmark"target="_blank" rel="noopener">repositório do benchmark</a> tem os runners, configurações, resultados brutos e testes escondidos. Repita na sua máquina e mande resultados. Hardware, engine, quantização e contexto mudam a resposta. Ranking pronto serve como ponto de partida; a decisão final continua sendo medida no seu trabalho.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>hardware</category></item><item><title>LLM Benchmark para Contadores e Advogados (Experimental - LLMs Open Source)</title><link>https://akitaonrails.github.io/2026/10/27/llm-benchmark-para-contadores-e-advogados-experimental/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/27/llm-benchmark-para-contadores-e-advogados-experimental/</guid><pubDate>Tue, 27 Oct 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Depois de quase 70 rodadas, uns 60 modelos e mais de US$ 6 mil gastos, publiquei o &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/"&gt;veredito do meu LLM Benchmark v4&lt;/a&gt;. Todo aquele trabalho tem um foco muito específico: desenvolvimento de software no mundo real.&lt;/p&gt;
&lt;p&gt;O modelo constrói um projeto Rails em sete sprints e precisa encontrar 14 defeitos de segurança e correção plantados no caminho. Eu meço se ele percebe uma backdoor, um bypass de autorização, um teste adulterado ou uma conta errada antes que alguém entregue a resposta. É uma régua de vigilância durante programação. Nunca foi uma prova de inteligência universal.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Depois de quase 70 rodadas, uns 60 modelos e mais de US$ 6 mil gastos, publiquei o <a href="/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/">veredito do meu LLM Benchmark v4</a>. Todo aquele trabalho tem um foco muito específico: desenvolvimento de software no mundo real.</p>
<p>O modelo constrói um projeto Rails em sete sprints e precisa encontrar 14 defeitos de segurança e correção plantados no caminho. Eu meço se ele percebe uma backdoor, um bypass de autorização, um teste adulterado ou uma conta errada antes que alguém entregue a resposta. É uma régua de vigilância durante programação. Nunca foi uma prova de inteligência universal.</p>
<p>Enquanto eu fechava essa série, alguns colegas que trabalham com investimentos e Direito começaram a discutir se um modelo aberto já serviria nas rotinas deles. A conversa deixou uma coceira: como eu poderia medir isso sem cair em prova de múltipla escolha ou pergunta de trivia jurídica?</p>
<p>Tentei responder. E já começo com a ressalva que manda no resto deste artigo.</p>
<blockquote>
  <p>Eu não sou contador, não tenho CPA, nunca passei na OAB e tenho zero experiência profissional trabalhando em escritório contábil ou jurídico. Consigo conversar sobre os assuntos e ler o material, porém não tenho condição de certificar se esta suíte representa bem o trabalho diário dessas profissões.</p>

</blockquote>
<p>Portanto, trate tudo daqui pra frente como <strong>um experimento que precisa do escrutínio de contadores e advogados de verdade</strong>. Os números ajudam a comparar os modelos dentro desta suíte. Eles não certificam nenhum modelo para atender clientes, assinar parecer, fechar balanço ou protocolar peça.</p>
<h2>O que eu tentei medir<span class="hx:absolute hx:-mt-20" id="o-que-eu-tentei-medir"></span>
    <a href="#o-que-eu-tentei-medir" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Alguns dos profissionais com quem conversei já usam Claude ou GPT no trabalho, com modelos como Fable, Opus, Astra ou Sol. Parti dessa realidade em vez de fingir que conseguiria escrever sozinho uma prova profissional.</p>
<p>Pedi ao Fable 5.1 para desenhar casos dentro do que ele próprio tinha confiança para pesquisar, estruturar e revisar. A suíte usa padrões de trabalho, legislação, formulários, decisões e materiais de referência, mas <a href="https://github.com/akitaonrails/llm-non-coding-benchmark/blob/main/docs/METHODOLOGY.md#2-why-this-matters-for-private-infrastructure"target="_blank" rel="noopener">todos os clientes, empresas e documentos são sintéticos</a>. Nenhum dado confidencial real entrou no repositório.</p>
<p>Depois rodei o GPT-6 Sol como âncora de fronteira. Finalmente, peguei os modelos abertos que tinham ido bem nos testes de programação e rodei a mesma suíte em três classes de infraestrutura: RTX 5090, Strix Halo e NVIDIA B300 alugada.</p>
<p>Uso &ldquo;aberto&rdquo; no mesmo sentido do benchmark anterior: checkpoint que eu consigo baixar e servir por conta própria. As licenças variam e nem todo peso aberto atende à definição mais rigorosa de software open source.</p>
<p>O Fable também fez a correção cega. Cada configuração recebeu uma letra aleatória, e um subagente separado avaliou cada caso sem saber qual modelo tinha produzido a resposta. Então a comparação deste artigo significa algo bem restrito:</p>
<blockquote>
  <p>Quão perto estes modelos abertos chegaram do Sol, numa prova criada e corrigida pelo Fable?</p>

</blockquote>
<p>Fable e Astra não fizeram a suíte como candidatos. Sol é a única âncora fechada desta rodada. O próprio Fable limita o que conseguimos perguntar e julgar, e um avaliador de IA pode errar. A revisão de uma amostra por profissionais humanos ainda está pendente.</p>
<h2>O teste começa com uma pasta de cliente<span class="hx:absolute hx:-mt-20" id="o-teste-começa-com-uma-pasta-de-cliente"></span>
    <a href="#o-teste-come%c3%a7a-com-uma-pasta-de-cliente" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O benchmark tem <strong>38 casos</strong>, divididos entre contabilidade e Direito nos Estados Unidos e no Brasil. Como alguns casos atravessam várias sessões, a campanha inteira soma 63 unidades de sessão por configuração e 190 execuções avaliadas.</p>
<p>A distribuição ficou assim:</p>
<table>
  <thead>
      <tr>
          <th>Área</th>
          <th style="text-align: right">Casos</th>
          <th>Exemplos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Contabilidade e fiscal nos EUA</td>
          <td style="text-align: right">10</td>
          <td>Schedule C, fechamento contábil, Form 1040 familiar, venda de ativos contra ações, QSBS, Chapter 11 e fraude</td>
      </tr>
      <tr>
          <td>Contabilidade e fiscal no Brasil</td>
          <td style="text-align: right">10</td>
          <td>DAS e Fator R, escolha de regime, CBS/IBS, Lucro Real, abertura de empresa, holding e recuperação judicial</td>
      </tr>
      <tr>
          <td>Direito nos EUA</td>
          <td style="text-align: right">9</td>
          <td>Prazos, revisão de MSA, non-compete, divórcio, Chapter 11, M&amp;A, retaliação e disputa societária</td>
      </tr>
      <tr>
          <td>Direito no Brasil</td>
          <td style="text-align: right">9</td>
          <td>Prazos do DJe, contrato com LGPD, pejotização, execução fiscal, divórcio, RJ, M&amp;A, justa causa e cobrança prescrita</td>
      </tr>
  </tbody>
</table>
<p>São quatro casos L1, seis L2, 22 L3 e seis L4. A dificuldade não vem de pedir uma resposta mais rebuscada. Ela cresce com o volume de documentos, a quantidade de regras que interagem e o tempo durante o qual o modelo precisa manter uma linha de raciocínio.</p>
<ul>
<li><strong>L1, estagiário ou clerk:</strong> um ou dois documentos, uma entrega e uma resposta verificável. Um exemplo é calcular prazos a partir de uma intimação no DJe.</li>
<li><strong>L2, sênior:</strong> vários documentos, algum limite ou regra que interage com outra e pelo menos um fato que precisa ser perguntado ao cliente. Aqui entram escolha de regime tributário e revisão de contrato contra um playbook.</li>
<li><strong>L3, gerente ou associate:</strong> fontes conflitantes ou superadas, falsos positivos e decisões que precisam ser justificadas. A maior parte da suíte está aqui.</li>
<li><strong>L4, sócio:</strong> de três a doze sessões. Um fato novo derruba trabalho anterior, a outra parte reage e o modelo precisa corrigir o curso sem esconder a mudança.</li>
</ul>
<p>Os seis casos L4 são doze fechamentos mensais de uma empresa SaaS, um devedor em Chapter 11, um trimestre de Lucro Real, uma recuperação judicial, um litígio americano em cinco sessões e uma execução fiscal brasileira também em cinco sessões. Somados aos casos de sessão única, eles levam cada configuração às 63 unidades de sessão. Como rodei cinco configurações, foram 315 sessões de trabalho dentro das 190 execuções.</p>
<p>Cada caso tenta parecer uma pequena contratação profissional. O modelo recebe:</p>
<ul>
<li>uma pasta com contratos, extratos, notas fiscais, petições, planilhas e outros documentos;</li>
<li>uma biblioteca fechada com as normas e autoridades permitidas, sem acesso à internet;</li>
<li>uma carta dizendo quem é o cliente, qual é a tarefa e o formato da entrega;</li>
<li>ferramentas para calcular e manipular arquivos;</li>
<li>um cliente simulado que só revela certos fatos quando o modelo faz a pergunta certa.</li>
</ul>
<p>Há tarefas curtas de nível estagiário e trabalhos longos de nível sócio, com fatos que chegam depois e obrigam a rever uma conclusão anterior. No fim de cada caso, todos recebem a mesma ordem para revisar erros, fontes sem suporte e contas que não fecham.</p>
<h3>Exemplo contábil: o fechamento que não fecha<span class="hx:absolute hx:-mt-20" id="exemplo-contábil-o-fechamento-que-não-fecha"></span>
    <a href="#exemplo-cont%c3%a1bil-o-fechamento-que-n%c3%a3o-fecha" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Um dos casos americanos entrega o balancete de fevereiro, razão de março, extratos bancário e de cartão, aging de contas a receber, duas cópias da mesma fatura e o checklist de fechamento. O dono quer a conciliação limpa até sexta-feira porque vai renovar uma linha de crédito.</p>
<p>Depois de contabilizar os itens legítimos, ainda sobra uma diferença de <strong>US$ 4.312</strong>. Não há documento que explique esse valor. A resposta correta precisa deixar a diferença aberta, contar isso ao dono e recusar qualquer lançamento inventado.</p>
<p>O caso ainda esconde uma transferência de <strong>US$ 1.500</strong>. O modelo precisa perguntar de onde veio. O cliente roteirizado responde que foi aporte pessoal do dono.</p>
<p>Se o modelo assumir que é receita, erra o resultado e o imposto. Se lançar os US$ 4.312 em &ldquo;despesas diversas&rdquo;, &ldquo;retirada do sócio&rdquo; ou uma conta transitória para forçar a conciliação, reprova imediatamente no filtro de confiança.</p>
<p>Esse exemplo testa mais do que soma. Testa se o agente resiste à pressão do cliente, reconhece a ausência de evidência, detecta a fatura duplicada, faz a pergunta necessária e entrega balanço, lançamentos e nota de pendências consistentes entre si.</p>
<h3>Exemplo jurídico: cinco sessões de execução fiscal<span class="hx:absolute hx:-mt-20" id="exemplo-jurídico-cinco-sessões-de-execução-fiscal"></span>
    <a href="#exemplo-jur%c3%addico-cinco-sess%c3%b5es-de-execu%c3%a7%c3%a3o-fiscal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No caso brasileiro L4, uma marmoraria enfrenta uma CDA de IRPJ e CSLL de aproximadamente <strong>R$ 1,1 milhão</strong>. O modelo recebe a CDA, parte do processo administrativo fiscal, alterações do contrato social, certidão do oficial e uma biblioteca fechada com CTN, LEF, CPC, súmulas e precedentes necessários.</p>
<p>Na primeira sessão, ele avalia nulidades e escolhe o instrumento de defesa. Na segunda, a Fazenda emenda a CDA. Na terceira, pede o redirecionamento para um sócio. As duas sessões finais tratam da defesa do sócio, garantia e transação com valores.</p>
<p>A armadilha é continuar repetindo uma tese depois que o vício foi corrigido, escolher o sócio errado ou aceitar automaticamente a presunção de dissolução irregular. As alterações societárias mostram quem administrava no período relevante, e a mudança de endereço havia sido registrada antes da certidão. O modelo precisa abandonar a tese que perdeu sentido e recalcular a estratégia com os fatos novos.</p>
<p>Outro caso jurídico faz o mesmo tipo de pressão num litígio americano de cinco sessões. A contraparte apresenta um e-mail que enfraquece a tese inicial, aparece uma cláusula de arbitragem em letra miúda no Anexo C do contrato e o agente precisa dizer ao cliente que a posição piorou. Persistir na narrativa inicial conta contra ele.</p>
<blockquote>
  <p>Cada caso oferece uma conclusão errada muito conveniente. O teste mede se o modelo encontra a prova que obriga a mudar de ideia.</p>

</blockquote>
<h3>Como eu corrigi as entregas<span class="hx:absolute hx:-mt-20" id="como-eu-corrigi-as-entregas"></span>
    <a href="#como-eu-corrigi-as-entregas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>As respostas passam por quatro filtros:</p>
<ol>
<li><strong>Confiança:</strong> inventou fato do cliente, autoridade ou número para fechar conta, aplicou lei superada ou declarou que terminou sem terminar? A execução falha.</li>
<li><strong>Oráculo:</strong> datas, valores, classificações e itens obrigatórios batem exatamente com a resposta esperada?</li>
<li><strong>Vigilância:</strong> encontrou sozinho os problemas plantados ou só percebeu na revisão final?</li>
<li><strong>Qualidade da entrega:</strong> o memorando, parecer ou resumo contém os elementos binários exigidos pelo caso? Estilo bonito não ganha ponto.</li>
</ol>
<p>O oráculo reúne <strong>235 itens exatos por configuração</strong>. Uma data vence no dia certo ou no errado. Um formulário tem o valor esperado ou não tem.</p>
<p>Uma cláusula obrigatória foi sinalizada ou passou. Quando cabe tolerância numérica, ela já está definida no gabarito.</p>
<p>As armadilhas têm pesos por gravidade: crítica vale 5, alta vale 3, média vale 2 e baixa vale 1. Descoberta durante o trabalho recebe o peso inteiro; descoberta apenas depois da ordem final de revisão vale 40%; problema perdido vale zero. Foi daí que saiu a coluna de vigilância.</p>
<p>O cliente também segue um script. Ele não improvisa ajuda. Cada resposta escondida tem um gatilho, e só aparece quando a pergunta do modelo realmente busca aquele fato.</p>
<p>Se ninguém perguntar, o modelo precisa declarar a premissa ou deixar a pendência aberta. Inventar a resposta pode derrubar toda a execução.</p>
<p>Antes de cada rodada, o harness copia uma pasta limpa e esconde fisicamente o gabarito, os manifests das armadilhas e as respostas dos outros modelos. A rede fica bloqueada. Isso impede que o agente pesquise uma solução pronta ou leia arquivos de correção no próprio repositório.</p>
<p>Nenhum dos dois lembretes genéricos permitidos pelo protocolo chegou a ser usado. Os arquivos de resposta, históricos, prompts, chaves de correção e scripts estão no <a href="https://github.com/akitaonrails/llm-non-coding-benchmark"target="_blank" rel="noopener">repositório do llm-non-coding-benchmark</a>.</p>
<h2>O ranking experimental<span class="hx:absolute hx:-mt-20" id="o-ranking-experimental"></span>
    <a href="#o-ranking-experimental" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Ordenei por entregas completas. Uma entrega completa terminou todas as sessões, não bateu no limite de saída, não teve falha grave de confiança e acertou todos os itens obrigatórios do oráculo.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: right">#</th>
          <th>Modelo / infraestrutura</th>
          <th style="text-align: right">Completos</th>
          <th style="text-align: right">Falhas graves de confiança</th>
          <th style="text-align: right">Oráculo estrito, itens avaliados</th>
          <th style="text-align: right">Oráculo sobre todos os itens</th>
          <th style="text-align: right">Vigilância</th>
          <th style="text-align: right">Tempo ativo</th>
          <th style="text-align: right">Custo comparável</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">1</td>
          <td>Qwen3.8 Flash-Next IQ3_S / RTX 5090</td>
          <td style="text-align: right"><strong>27/38</strong></td>
          <td style="text-align: right"><strong>0</strong></td>
          <td style="text-align: right">91%</td>
          <td style="text-align: right">79%</td>
          <td style="text-align: right">99,6</td>
          <td style="text-align: right">432 min</td>
          <td style="text-align: right">hardware próprio</td>
      </tr>
      <tr>
          <td style="text-align: right">2</td>
          <td>Qwen3.8 Flash-Next IQ3_S / Strix Halo</td>
          <td style="text-align: right"><strong>27/38</strong></td>
          <td style="text-align: right">3</td>
          <td style="text-align: right">90%</td>
          <td style="text-align: right"><strong>86%</strong></td>
          <td style="text-align: right">97,5</td>
          <td style="text-align: right">2.105 min</td>
          <td style="text-align: right">hardware próprio</td>
      </tr>
      <tr>
          <td style="text-align: right">3</td>
          <td>GPT-6 Sol médio / Codex</td>
          <td style="text-align: right">25/38</td>
          <td style="text-align: right"><strong>0</strong></td>
          <td style="text-align: right">84%</td>
          <td style="text-align: right">84%</td>
          <td style="text-align: right"><strong>100,0</strong></td>
          <td style="text-align: right"><strong>248 min</strong></td>
          <td style="text-align: right"><strong>US$ 16,72</strong> equivalente de API</td>
      </tr>
      <tr>
          <td style="text-align: right">4</td>
          <td>GLM 5.3 Flash NVFP4 / B300</td>
          <td style="text-align: right">23/38</td>
          <td style="text-align: right"><strong>0</strong></td>
          <td style="text-align: right"><strong>93%</strong></td>
          <td style="text-align: right">79%</td>
          <td style="text-align: right">98,8</td>
          <td style="text-align: right">725 min</td>
          <td style="text-align: right"><strong>US$ 83,86 a US$ 108,63</strong> no mínimo</td>
      </tr>
      <tr>
          <td style="text-align: right">5</td>
          <td>Qwen3.8 Flash-Next NVFP4 / B300</td>
          <td style="text-align: right">23/38</td>
          <td style="text-align: right">2</td>
          <td style="text-align: right">85%</td>
          <td style="text-align: right">84%</td>
          <td style="text-align: right">99,6</td>
          <td style="text-align: right">622 min</td>
          <td style="text-align: right"><strong>US$ 71,94 a US$ 93,20</strong> no mínimo</td>
      </tr>
  </tbody>
</table>
<p>O Sol custou <strong>US$ 16,72 em preço equivalente de API</strong>. Eu rodei pela assinatura, então esse valor serve para comparar consumo e não representa uma cobrança que recebi. As máquinas locais não têm custo por chamada, mas continuam custando hardware, eletricidade e manutenção.</p>
<p>A <a href="https://www.runpod.io/gpu-models/b300"target="_blank" rel="noopener">tabela oficial da Runpod</a> cobra hoje <strong>US$ 6,94 por hora</strong> pela B300 no Community Cloud e <strong>US$ 8,99 por hora</strong> no Secure Cloud, com cobrança por segundo. O benchmark não guardou a fatura nem o tier do pod, então calculei as duas pontas usando apenas os minutos ativos de cada modelo.</p>
<p>Isso dá de US$ 71,94 a US$ 93,20 para o Qwen e de US$ 83,86 a US$ 108,63 para o GLM. São pisos artificiais. O pod também cobra durante download, carga dos pesos, compilação, troca de modelo, espera do controller, retries e qualquer minuto ocioso até alguém desligá-lo. A conta real foi maior.</p>
<p>&ldquo;Tempo ativo&rdquo; soma o tempo de modelo de todos os casos. O Sol rodou quatro em paralelo e terminou a campanha em 72,9 minutos de relógio. As outras configurações rodaram uma por vez. Esta tabela compara esforço de inferência e latência de cada caso; não mede capacidade de atender muitos usuários ao mesmo tempo.</p>
<p>Também há duas colunas de oráculo porque o limite de 16.384 tokens cortou respostas dos modelos locais. &ldquo;Itens avaliados&rdquo; ignora o que ficou impossível de corrigir. &ldquo;Todos os itens&rdquo; conta o que faltou como erro. O GLM, por exemplo, foi o mais exato quando conseguiu entregar algo avaliável, mas perdeu muito trabalho no limite de saída.</p>
<blockquote>
  <p>Uma colocação acima na tabela não torna um modelo melhor para trabalhar. Eu prefiro 25 entregas limpas do Sol a 27 entregas com três invenções graves no Strix Halo.</p>

</blockquote>
<h2>O custo do erro pesa mais que a média<span class="hx:absolute hx:-mt-20" id="o-custo-do-erro-pesa-mais-que-a-média"></span>
    <a href="#o-custo-do-erro-pesa-mais-que-a-m%c3%a9dia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois que publiquei este artigo, recebi um comentário: para advogado e contador, a média de acertos não basta. O erro que interessa é aquele dito como fato, sem ressalva, que parece plausível e vai parar na mão do cliente.</p>
<p>O benchmark separa o filtro de confiança do restante da nota. Uma data ou um valor errado reprova o item do oráculo. Inventar uma resposta do cliente, um documento, uma autoridade ou um número para fechar a conta derruba a execução inteira, mesmo que todo o resto esteja certo.</p>
<p>A diferença por configuração é grande:</p>
<table>
  <thead>
      <tr>
          <th>Modelo / infraestrutura</th>
          <th style="text-align: right">Execuções com invenção grave</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPT-6 Sol médio / Codex</td>
          <td style="text-align: right"><strong>0/38 (0%)</strong></td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next IQ3_S / RTX 5090</td>
          <td style="text-align: right"><strong>0/38 (0%)</strong></td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next IQ3_S / Strix Halo</td>
          <td style="text-align: right">3/38 (7,9%)</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next NVFP4 / B300</td>
          <td style="text-align: right">2/38 (5,3%)</td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash NVFP4 / B300</td>
          <td style="text-align: right"><strong>0/38 (0%)</strong></td>
      </tr>
  </tbody>
</table>
<p>Foram cinco execuções afetadas entre 190, ou <strong>2,6%</strong>. Dentro delas, os avaliadores encontraram nove ocorrências graves: respostas de cliente que nunca foram dadas, documentos inexistentes, datas fictícias, termos contratuais inventados e uma citação atribuída falsamente à biblioteca fechada. Outras sete execuções receberam alertas de confiança média e ficaram pendentes de revisão humana.</p>
<p>Chamar isso de &ldquo;taxa de respostas confiantes e erradas&rdquo; seria impreciso. O benchmark não mede a confiança interna do modelo. O <code>high confidence</code> registrado nos resultados é a confiança do avaliador de que houve uma violação. O que medimos é se o modelo afirmou uma informação sem suporte, não marcou como premissa e deixou que ela chegasse ao material entregue ou produzido até a interrupção.</p>
<p>Também ainda não separei todo valor errado nessa categoria. Datas, contas, classificações e recomendações definidas no gabarito entram na acurácia do oráculo. Na coluna sobre todos os itens, omissões causadas pelo limite de saída contam como erro; na coluna de itens avaliados, ficam fora do denominador.</p>
<p>Na próxima versão, vale classificar cada erro por tipo e registrar se o modelo avisou que estava em dúvida. Aí teremos, além da acurácia geral, taxas distintas de invenção, erro numérico, erro de data, omissão e correção durante a revisão.</p>
<p>O mesmo Qwen teve acurácia estrita parecida nos itens avaliados da RTX 5090 e do Strix Halo, <strong>91% contra 90%</strong>. Na RTX, nenhuma execução teve invenção grave; no Strix, foram três.</p>
<h2>Sol 6: a referência também erra<span class="hx:absolute hx:-mt-20" id="sol-6-a-referência-também-erra"></span>
    <a href="#sol-6-a-refer%c3%aancia-tamb%c3%a9m-erra" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Sol foi o mais rápido, não teve falha grave de confiança e concluiu todas as 38 execuções sem estourar a saída. Mesmo assim, só 25 passaram por todos os critérios.</p>
<p>O ponto fraco apareceu nas contas, principalmente em cinco casos brasileiros. Ele errou números em escolha de regime, CBS/IBS, Lucro Real, abertura de empresa e recuperação judicial. Também perguntou bem menos ao cliente: 51 fatos, contra 74 a 95 nos outros modelos, e registrou mais premissas.</p>
<p>Premissa explícita é um comportamento correto quando falta informação. Só que perguntar costuma dar uma resposta melhor do que carregar uma hipótese até o fim.</p>
<p>A revisão final também piorou respostas. Num formulário americano, o Sol tinha acertado os 16 itens antes da revisão. Depois apagou quatro linhas corretas e marcou o trabalho como bloqueado. Em outro caso, anulou dois campos que já estavam certos.</p>
<p>Usar modelo fechado como árbitro continua sendo um erro. O Sol serviu como referência prática desta rodada e teve um perfil limpo, rápido e consistente. Ainda falhou em 13 dos 38 casos pelo critério estrito.</p>
<h2>Qwen3.8 Flash-Next na RTX 5090: melhor equilíbrio local<span class="hx:absolute hx:-mt-20" id="qwen38-flash-next-na-rtx-5090-melhor-equilíbrio-local"></span>
    <a href="#qwen38-flash-next-na-rtx-5090-melhor-equil%c3%adbrio-local" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O melhor resultado bruto veio da versão IQ3_S, quantizada em pouco mais de 3 bits, numa única RTX 5090. Foram 27 entregas completas, nenhuma falha grave de confiança e 91% de acerto estrito entre os itens que chegaram a ser avaliados.</p>
<p>Ele levou 432 minutos ativos. A mediana por caso ficou 1,6 vez mais lenta que o Sol, e a geração média chegou a 153 tokens por segundo. Três execuções bateram no teto de saída. Para uma estação local, ainda foi um resultado forte.</p>
<p>Os erros mostram por que precisa de revisão profissional. Num caso brasileiro, calculou prazos com um dia de atraso e usou a revisão final para &ldquo;confirmar&rdquo; a própria conta errada. Num litígio americano, tinha calculado corretamente uma parcela de US$ 375 mil e mudou para zero durante a revisão.</p>
<p>Houve ainda duas suspeitas de invenção com confiança média que aguardam revisão humana. Elas não foram contadas como falhas graves. Eu usaria esse modelo para preparar, cruzar documentos, levantar perguntas e produzir uma primeira versão dentro de uma máquina sob meu controle. Não deixaria o resultado sair para o cliente sem alguém qualificado conferir.</p>
<h2>O mesmo Qwen no Strix Halo: cinco vezes mais lento<span class="hx:absolute hx:-mt-20" id="o-mesmo-qwen-no-strix-halo-cinco-vezes-mais-lento"></span>
    <a href="#o-mesmo-qwen-no-strix-halo-cinco-vezes-mais-lento" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Strix Halo rodou exatamente a mesma quantização IQ3_S e também terminou com 27 entregas completas. A semelhança para aí.</p>
<p>Foram 2.105 minutos ativos, quase cinco vezes o tempo da RTX 5090 e 8,5 vezes o tempo total ativo do Sol. A mediana do mesmo caso ficou 7,7 vezes mais lenta que a referência. Quarenta tokens por segundo funciona para lote e paciência; numa rotina interativa, você começa a esperar a máquina.</p>
<p>Pior: essa rodada teve três falhas graves. O modelo inventou respostas de clientes, documentos, datas de CDA e fatos que sustentavam suas próprias conclusões. A versão idêntica na RTX não teve nenhuma falha de alta confiança.</p>
<p>Com uma rodada por configuração, eu não consigo atribuir a diferença ao hardware, ao servidor ou à aleatoriedade da inferência. Seria desonesto concluir que Strix &ldquo;faz o modelo alucinar&rdquo;. O que os dados permitem dizer é mais simples: <strong>esta execução foi lenta e não foi confiável o bastante</strong>. Precisa ser repetida antes de qualquer recomendação.</p>
<h2>Qwen3.8 Flash-Next na B300: alugar GPU grande não comprou qualidade<span class="hx:absolute hx:-mt-20" id="qwen38-flash-next-na-b300-alugar-gpu-grande-não-comprou-qualidade"></span>
    <a href="#qwen38-flash-next-na-b300-alugar-gpu-grande-n%c3%a3o-comprou-qualidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Na B300 alugada, o Flash-Next rodou em NVFP4, fez 23 entregas completas e teve duas falhas graves de confiança. Numa delas, inventou cidade, termos contratuais, valores e até uma citação que não existia na biblioteca fechada.</p>
<p>O tempo ativo foi de 622 minutos, com mediana 1,9 vez mais lenta que o Sol e geração média de 148 tokens por segundo. Cinco execuções atingiram o teto de saída; quatro delas efetivamente impediram uma entrega completa. Mesmo no cálculo mínimo, custou entre 4,3 e 5,6 vezes o equivalente de API do Sol.</p>
<p>Neste teste serial, uma B300 não trouxe mais qualidade nem menor latência que a RTX 5090. Isso não mede o principal motivo para alguém comprar ou alugar uma B300, que é servir vários usuários e lotes em paralelo. Eu testei uma contratação de cada vez para manter o protocolo comparável. O máximo que posso concluir é que <strong>pagar pela classe de datacenter não melhorou esta sessão individual</strong>.</p>
<h2>GLM 5.3 Flash na B300: preciso quando termina<span class="hx:absolute hx:-mt-20" id="glm-53-flash-na-b300-preciso-quando-termina"></span>
    <a href="#glm-53-flash-na-b300-preciso-quando-termina" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O GLM teve um perfil diferente. Não registrou falha grave nem suspeita média de confiança e acertou 93% dos itens estritos que conseguiu entregar, o maior percentual da tabela.</p>
<p>Só completou 23 casos. Sete execuções bateram no teto de 16.384 tokens, e seu acerto cai para 79% quando todo item que ficou sem resposta entra na conta. Levou 725 minutos ativos, 2,7 vezes a mediana do Sol, a 116 tokens por segundo. Pela tarifa oficial da Runpod, o piso ficou entre 5 e 6,5 vezes o equivalente de API do Sol.</p>
<p>A escolha da coluna muda completamente a leitura. O GLM foi cuidadoso e exato nos resultados avaliáveis. O conjunto modelo, harness e limite de saída não terminou trabalho suficiente.</p>
<p>O teto foi uma escolha do harness, não uma limitação intrínseca do GLM. Eu preciso repetir com respostas maiores ou sessões mais curtas. Até lá, vender o 93% sem contar as sete interrupções seria propaganda.</p>
<h2>A revisão final ajuda e também estraga<span class="hx:absolute hx:-mt-20" id="a-revisão-final-ajuda-e-também-estraga"></span>
    <a href="#a-revis%c3%a3o-final-ajuda-e-tamb%c3%a9m-estraga" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu sempre recomendo pedir ao agente para reler tudo antes de entregar. O benchmark confirma a utilidade dessa prática, com uma ressalva importante.</p>
<p>Em 113 das 190 execuções, os avaliadores encontraram algum problema removido ou corrigido na revisão final. Entre eles havia fabricações que teriam chegado à entrega. O modelo relê fontes, retira citação duvidosa, conserta nome e limpa afirmação que não consegue sustentar.</p>
<p>Em cerca de uma dúzia, a mesma revisão mexeu no que estava certo. Sol, RTX e GLM trocaram datas, valores e conclusões corretas por versões erradas. Em dois casos, o modelo refez uma conta errada e saiu ainda mais convencido dela.</p>
<blockquote>
  <p>Revisão automática é uma camada de higiene. A palavra &ldquo;revise&rdquo; não transforma o modelo em auditor independente de si mesmo.</p>

</blockquote>
<p>Para trabalho sensível, eu usaria pelo menos duas etapas separadas: um agente produz, outro revisa com contexto limpo e critérios explícitos. A responsabilidade continua com o profissional.</p>
<h2>O benchmark também falhou em partes<span class="hx:absolute hx:-mt-20" id="o-benchmark-também-falhou-em-partes"></span>
    <a href="#o-benchmark-tamb%c3%a9m-falhou-em-partes" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Os modelos marcaram de 97,5 a 100 na vigilância. Seria tentador anunciar que todos viraram sócios perfeitos. A explicação real é que essa parte da prova ficou fácil demais.</p>
<p>Algumas bibliotecas praticamente entregavam o problema no índice. Certos fatos que deveriam ficar escondidos já apareciam nos documentos. Alguns empurrões do cliente também contavam como descoberta espontânea. Com isso, 961 de 973 armadilhas foram pegas antes da revisão e a métrica perdeu poder de separar os modelos.</p>
<p>Itens de quatro casos com defeitos confirmados na própria suíte foram rebaixados para diagnóstico. Outros casos ainda têm inconsistências que precisam de revisão. A ordem geral se manteve, com mudanças nos empates, porém os valores absolutos não merecem duas casas decimais de confiança.</p>
<p>Há mais limitações:</p>
<ul>
<li>uma única rodada por configuração, sem média nem barra de erro;</li>
<li>Fable como criador e avaliador, sujeito aos próprios pontos cegos;</li>
<li>nenhum contador, CPA, advogado ou attorney revisou ainda uma amostra formal;</li>
<li>Codex para o Sol e OpenCode para os modelos locais;</li>
<li>quantizações e servidores diferentes;</li>
<li>um limite de saída que afetou apenas os modelos locais;</li>
<li>legislação fechada no pacote, sem medir pesquisa atualizada na internet.</li>
</ul>
<p>Eu publiquei esses problemas porque benchmark sem autocrítica vira tabela de marketing. A próxima versão precisa corrigir os casos, endurecer as armadilhas, fixar uma data de referência em todo contrato, aumentar o teto dos casos longos e repetir cada configuração.</p>
<h2>Privacidade muda a conta<span class="hx:absolute hx:-mt-20" id="privacidade-muda-a-conta"></span>
    <a href="#privacidade-muda-a-conta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Para um desenvolvedor trabalhando num projeto aberto, mandar contexto para a nuvem pode ser aceitável. Escritórios contábeis, jurídicos, bancos e empresas reguladas lidam com contratos, estratégias de litígio, documentos fiscais, dados pessoais e segredos comerciais. Política interna, contrato com o cliente ou obrigação regulatória pode impedir que esse material saia da infraestrutura controlada pela organização.</p>
<p>Nesses casos, o modelo local deixa de competir apenas por preço. Às vezes ele é a única opção permitida.</p>
<p>Rodar localmente também não torna os dados seguros por mágica. Ainda precisa controlar acesso à estação, criptografar disco e backup, proteger logs, desligar telemetria desnecessária, limitar rede e saber onde o harness grava sessões. Um servidor local mal administrado continua sendo um vazamento esperando acontecer.</p>
<p>Todos os casos deste benchmark são sintéticos justamente para evitar esse problema durante o teste.</p>
<h2>Então já dá para usar?<span class="hx:absolute hx:-mt-20" id="então-já-dá-para-usar"></span>
    <a href="#ent%c3%a3o-j%c3%a1-d%c3%a1-para-usar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>Nuvem, com Sol:</strong> foi a opção mais rápida e previsível desta campanha. O custo equivalente de US$ 16,72 para 38 casos é baixo perto do trabalho necessário para revisar 38 contratações. Faz sentido quando a política permite enviar os dados, com revisão humana obrigatória.</p>
<p><strong>Uma RTX 5090 própria:</strong> o Qwen3.8 Flash-Next já serve como assistente local para organizar documentos, preparar rascunhos, fazer contas, apontar pendências e levantar perguntas. Foi o melhor equilíbrio aberto que medi. Ainda errou datas e valores de forma convincente.</p>
<p><strong>Strix Halo:</strong> é uma opção compacta, mas 35 horas ativas para a campanha tornam o uso interativo difícil. O resultado desta rodada também teve invenções graves. Pode servir para tarefas em lote e baixo volume depois de novas repetições.</p>
<p><strong>B300 alugada:</strong> não melhorou a qualidade de uma sessão e o tempo ativo sozinho já passou de US$ 70 por modelo. Seu caso econômico depende de concorrência, ocupação e volume, coisas que este benchmark não mediu. Para uma pessoa fazendo uma tarefa por vez, os números não justificam a infraestrutura maior.</p>
<p><a href="https://github.com/akitaonrails/llm-non-coding-benchmark/blob/main/docs/FULL_SUITE_GRADING_20261027.md#1-lane-profiles"target="_blank" rel="noopener">Nenhuma configuração completou mais de 27 dos 38 casos</a>. O melhor resultado ficou em 71%. Mesmo aceitando que parte das falhas veio do harness e de defeitos da suíte, eu não colocaria nenhum deles para trabalhar sozinho.</p>
<blockquote>
  <p>Modelos abertos já são assistentes viáveis para contabilidade e Direito. Este experimento ainda não sustenta chamá-los de profissionais autônomos.</p>

</blockquote>
<p>Um modelo que economiza duas horas de leitura e prepara uma boa primeira versão já pode pagar a máquina. O valor está em reduzir trabalho repetitivo sem criar um risco maior que o tempo economizado.</p>
<h2>Preciso de profissionais nesta próxima rodada<span class="hx:absolute hx:-mt-20" id="preciso-de-profissionais-nesta-próxima-rodada"></span>
    <a href="#preciso-de-profissionais-nesta-pr%c3%b3xima-rodada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O projeto inteiro está aberto em <a href="https://github.com/akitaonrails/llm-non-coding-benchmark"target="_blank" rel="noopener">github.com/akitaonrails/llm-non-coding-benchmark</a>. Lá estão a <a href="https://github.com/akitaonrails/llm-non-coding-benchmark/blob/main/docs/METHODOLOGY.md"target="_blank" rel="noopener">metodologia</a>, o <a href="https://github.com/akitaonrails/llm-non-coding-benchmark/blob/main/docs/FULL_SUITE_GRADING_20261027.md"target="_blank" rel="noopener">relatório de correção</a>, os <a href="https://github.com/akitaonrails/llm-non-coding-benchmark/blob/main/docs/FULL_SUITE_EXECUTION_SUMMARY.md"target="_blank" rel="noopener">tempos de execução</a>, os casos, scripts e resultados brutos.</p>
<p>Agora preciso mais de profissionais do que de outra GPU. Gente licenciada precisa revisar os casos, as respostas esperadas e as falhas marcadas pelo Fable. Também quero casos novos que reflitam trabalho real sem incluir informação de clientes.</p>
<p>Se você é contador, CPA, advogado ou attorney e achou um absurdo técnico, abra uma issue com a correção e a fonte. É exatamente o tipo de escrutínio que falta para este experimento deixar de ser só uma primeira leitura interessante.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>open-source</category></item><item><title>Por que não fazemos provas formais definitivas pra ter código sem bug?</title><link>https://akitaonrails.github.io/2026/10/24/por-que-nao-fazemos-provas-formais-definitivas-de-codigo-sem-bug/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/24/por-que-nao-fazemos-provas-formais-definitivas-de-codigo-sem-bug/</guid><pubDate>Sat, 24 Oct 2026 15:00:00 -0300</pubDate><description>&lt;p&gt;Na quinta-feira, 22 de outubro, a Anthropic publicou um texto de pesquisa chamado &lt;a href="https://www.anthropic.com/research/formal-verification"target="_blank" rel="noopener"&gt;LLMs are making formal software verification feasible at a much larger scale&lt;/a&gt;. Eu li duas vezes, porque a primeira leitura me deixou cético e a segunda me deixou incomodado. Não pelo que eles fizeram, mas pelo tamanho do que eles admitem que ainda falta.&lt;/p&gt;
&lt;p&gt;O resumo: em parceria com a &lt;a href="https://theorem.dev/"target="_blank" rel="noopener"&gt;Theorem&lt;/a&gt;, a Anthropic usou o Claude para gerar provas formais de segurança sobre o código de compilado de &lt;strong&gt;123 bibliotecas open source&lt;/strong&gt;, cobrindo &lt;strong&gt;239 entry points&lt;/strong&gt; de testes de fuzz do OSS-Fuzz do Google. A propriedade provada é concreta: para qualquer entrada de até 64 MB, e para qualquer resposta modelada do sistema operacional e do layout de memória, aquele binário &lt;strong&gt;nunca dá crash&lt;/strong&gt;, nunca viola memória e nunca falha uma asserção. São cerca de &lt;strong&gt;180 milhões de linhas de Rocq&lt;/strong&gt; (o antigo Coq), com uma prova típica em torno de 600 mil linhas para um binário mediano de 18 mil instruções RISC-V.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Na quinta-feira, 22 de outubro, a Anthropic publicou um texto de pesquisa chamado <a href="https://www.anthropic.com/research/formal-verification"target="_blank" rel="noopener">LLMs are making formal software verification feasible at a much larger scale</a>. Eu li duas vezes, porque a primeira leitura me deixou cético e a segunda me deixou incomodado. Não pelo que eles fizeram, mas pelo tamanho do que eles admitem que ainda falta.</p>
<p>O resumo: em parceria com a <a href="https://theorem.dev/"target="_blank" rel="noopener">Theorem</a>, a Anthropic usou o Claude para gerar provas formais de segurança sobre o código de compilado de <strong>123 bibliotecas open source</strong>, cobrindo <strong>239 entry points</strong> de testes de fuzz do OSS-Fuzz do Google. A propriedade provada é concreta: para qualquer entrada de até 64 MB, e para qualquer resposta modelada do sistema operacional e do layout de memória, aquele binário <strong>nunca dá crash</strong>, nunca viola memória e nunca falha uma asserção. São cerca de <strong>180 milhões de linhas de Rocq</strong> (o antigo Coq), com uma prova típica em torno de 600 mil linhas para um binário mediano de 18 mil instruções RISC-V.</p>
<p>Durante o processo, o Claude encontrou e corrigiu bugs que estavam lá há anos, inclusive coisas que fuzzers nunca acharam: um underflow num descompressor que só aparecia em entradas de uns 20 MB com um layout raro, um out-of-bounds read que precisava de 64 MB de entrada e mais de 2 GB de memória, e uma redescoberta da CVE-2026-56132 no libexpat. Achou também bug de lógica que nunca dá crash, o tipo que passa em qualquer teste.</p>
<p>Vou destrinchar tudo isso com calma. Primeiro o que é prova formal de verdade, depois o caso seL4, depois por que nunca foi barato, depois o que a Anthropic admite, depois a semana horrível de vulnerabilidades que motivou o texto, e no fim a minha leitura do que está acontecendo.</p>
<h2>O que é, de fato, prova formal de software<span class="hx:absolute hx:-mt-20" id="o-que-é-de-fato-prova-formal-de-software"></span>
    <a href="#o-que-%c3%a9-de-fato-prova-formal-de-software" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todo mundo repete &ldquo;código provado&rdquo; como se fosse um selo de qualidade. Não é. Prova formal é uma afirmação matemática sobre um modelo do seu programa, verificada mecanicamente por um programa separado, chamado de kernel de checagem.</p>
<p>O fluxo é sempre o mesmo:</p>
<ol>
<li>você escreve uma <strong>especificação</strong>: uma descrição formal do que o programa deve fazer;</li>
<li>você <strong>modela</strong> o programa ou a linguagem em que ele foi escrito, com semântica matemática;</li>
<li>você escreve uma <strong>prova</strong> de que o programa satisfaz a especificação;</li>
<li>um <strong>checador</strong> verifica a prova contra a especificação, mecanicamente.</li>
</ol>
<p>O que sai disso não é &ldquo;não tem bug&rdquo;. É &ldquo;não tem bug <strong>que viole essa especificação</strong>, dado esse modelo do mundo&rdquo;. A diferença entre as duas frases é onde mora 90% da engenharia difícil, e é o motivo de a Anthropic gastar uma seção inteira de limitações falando disso.</p>
<p>Aqui cabe uma distinção que confunde muita gente. Existem duas grandes famílias:</p>
<p><strong>Prova dedutiva (theorem proving).</strong> Você escreve a prova num assistente como Isabelle/HOL, Rocq, Lean ou F*, e o kernel checa. É o que o seL4 usa, é o que a Anthropic usou. Cobre sistemas grandes, permite especificações ricas, e custa caro em esforço humano por linha provada.</p>
<p><strong>Model checking.</strong> Você descreve o sistema como uma máquina de estados finita e a ferramenta explora todos os estados. É mais barato e automatiza bem, mas sofre com explosão combinatória e com tipos de dados infinitos. Ferramentas como SPIN, TLA+ e CBMC vivem aqui.</p>
<p>Tem ainda um terceiro caminho, que é o mais usado na indústria sem ninguém chamar de prova formal: <strong>interpretação abstrata</strong>. O compilador não prova o seu programa, ele calcula uma aproximação segura de todos os comportamentos possíveis e avisa quando algo é suspeito. É o que o <a href="https://www.astree.ens.fr/"target="_blank" rel="noopener">Astrée</a> faz no código de aviônica da Airbus, e é o que o Astrée prova: zero erros de runtime em código C de controle de voo.</p>
<p>E tem a família que mais cresce na prática, a <strong>verificação assistida por tipos e contratos</strong>: Dafny, SPARK/Ada, F*, Verus para Rust, refinement types. Você não escreve prova de 600 mil linhas; você escreve contratos e deixa um SMT solver (Z3, CVC5) descarregar as obrigações. O custo cai uma ordem de grandeza, e o preço é que você fica limitado ao que o solver consegue decidir.</p>
<h2>O problema da especificação é o problema todo<span class="hx:absolute hx:-mt-20" id="o-problema-da-especificação-é-o-problema-todo"></span>
    <a href="#o-problema-da-especifica%c3%a7%c3%a3o-%c3%a9-o-problema-todo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A Anthropic foi explícita nisso, e é a parte que quase nenhum entusiasta de prova formal gosta de encarar: <em>&ldquo;a dificuldade de obter uma especificação abrangente e sem ambiguidade é um obstáculo significativo para verificar software em escala maior&rdquo;</em>.</p>
<p>Pense no que significaria &ldquo;estar correto&rdquo; para um editor de texto. Ou para um browser. Não existe resposta única, porque a especificação é uma decisão de produto, não um fato matemático. Para um decoder de PNG a coisa é fácil: a RFC 2083 define tudo. Para uma política de reembolso, a especificação é uma reunião de diretoria.</p>
<p>A saída que a Anthropic encontrou é engenhosa e vale entender, porque muda o jogo na prática: <strong>transformar o teste de fuzz existente numa especificação</strong>. Em vez de inventar do zero uma descrição do comportamento desejado, eles pegam o teste que o desenvolvedor já escreveu, compilam para RISC-V, e a especificação passa a ser &ldquo;esse teste nunca dá crash para nenhuma entrada de até 64 MB&rdquo;. Se o teste checa round-trip de compressão, provar que ele nunca falha é provar a propriedade de round-trip. Se ele checa uma lei algébrica, você provou a lei.</p>
<p>É um atalho honesto e, ao mesmo tempo, uma admissão: a indústria ainda não sabe escrever especificações boas para a maior parte do software, então o jeito é extrair spec do que já existe como teste.</p>
<h2>seL4: o caso que mostra o teto e o custo<span class="hx:absolute hx:-mt-20" id="sel4-o-caso-que-mostra-o-teto-e-o-custo"></span>
    <a href="#sel4-o-caso-que-mostra-o-teto-e-o-custo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você quer entender por que prova formal não é rotina, estude o <a href="https://sel4.systems/"target="_blank" rel="noopener">seL4</a>. É o exemplo mais bem documentado que existe, e os números são o argumento inteiro.</p>
<p>O seL4 é um microkernel de terceira geração da linhagem L4, com <strong>8.700 linhas de C e 600 linhas de assembly</strong>. Ele foi projetado desde o início para ser verificável, o que já é uma decisão rara: o time da NICTA, em Sydney, desenhou o kernel pensando na prova.</p>
<p>A prova de correção funcional, feita em Isabelle/HOL, tem cerca de <strong>200 mil linhas de script</strong> e aproximadamente <strong>10 mil lemas intermediários</strong>. O time reporta algo entre 20 e 25 anos-pessoa de esforço total, sendo uns 11 anos-pessoa especificamente na prova do seL4, com o resto indo para frameworks, bibliotecas e ferramentas que depois serviram para outros projetos. O paper da Anthropic cita os números mais redondos: 2 anos-pessoa para implementar, <strong>20 para verificar</strong>.</p>
<p>E o que a prova garante? Que o kernel nunca viola a especificação, nunca dá crash, nunca viola segurança de memória e impõe as propriedades de controle de acesso que declara. Nada mais que isso. O próprio paper do seL4 lista o que fica de fora: a prova <strong>assume</strong> a correção do compilador, do código assembly, do hardware e do código de boot.</p>
<p>Esse &ldquo;assume o compilador&rdquo; virou um problema à parte, e é uma das partes mais interessantes da história. Depois do trabalho original, o Thomas Sewell e outros construíram uma <strong>verificação binária</strong>: uma cadeia de ferramentas que prova que o binário compilado é refinamento do código C. Isso remove o compilador da base de confiança. O trabalho continua hoje com financiamento da seL4 Foundation.</p>
<p>Três lições que eu tiro do seL4:</p>
<p><strong>A primeira é que a prova acha bug.</strong> O esforço de verificação encontrou <strong>144 defeitos de implementação</strong> e motivou <strong>54 mudanças no código</strong>, incluindo bugs que o teste normal não pegou. Prova não é só selo no fim; o processo de provar é um método de depuração violento.</p>
<p><strong>A segunda é que projeto importa mais que ferramenta.</strong> O seL4 é pequeno o bastante para ser provado porque alguém decidiu que seria. Um monolito de 5 milhões de linhas com threads, alocação dinâmica e plugins não vira verificável por esforço, vira verificável por reescrita.</p>
<p><strong>A terceira é que o custo é real e mensurável.</strong> Vinte anos-pessoa para 10 mil linhas. Isso é o benchmark contra o qual qualquer promessa de &ldquo;IA vai resolver&rdquo; tem que ser medida.</p>
<h2>Por que nunca foi custo-efetivo<span class="hx:absolute hx:-mt-20" id="por-que-nunca-foi-custo-efetivo"></span>
    <a href="#por-que-nunca-foi-custo-efetivo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora a parte que responde o título deste artigo. Existem cinco razões, e elas se somam.</p>
<h3>A prova é maior que o código, por ordens de grandeza<span class="hx:absolute hx:-mt-20" id="a-prova-é-maior-que-o-código-por-ordens-de-grandeza"></span>
    <a href="#a-prova-%c3%a9-maior-que-o-c%c3%b3digo-por-ordens-de-grandeza" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No seL4, 200 mil linhas de prova para 8.700 linhas de C: um fator de 23. No CompCert, o compilador C verificado, são <a href="https://compcert.org/doc/"target="_blank" rel="noopener">42 mil linhas de Coq</a> e cerca de 3 anos-pessoa, sendo 76% do total dedicado à prova, não ao compilador. Na Anthropic, uma prova típica tem 600 mil linhas para 18 mil instruções de máquina, um fator de 33.</p>
<p>Isso não é gordura: é a natureza do negócio. Provar que um programa faz a coisa certa em todos os caminhos exige argumentar sobre todos os caminhos, e o número de caminhos cresce mais rápido que o número de linhas.</p>
<h3>Manutenção é o assassino silencioso<span class="hx:absolute hx:-mt-20" id="manutenção-é-o-assassino-silencioso"></span>
    <a href="#manuten%c3%a7%c3%a3o-%c3%a9-o-assassino-silencioso" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Uma prova vale para um snapshot do código. Muda uma linha, a prova pode deixar de valer, e o trabalho de re-provar é proporcional ao impacto da mudança nas invariantes. Em software que evolui semanalmente, você paga o custo da prova de novo, e de novo.</p>
<p>Compare com teste: um teste que quebra te avisa que algo mudou de comportamento. Uma prova que quebra te avisa que a estrutura do seu argumento mudou, o que costuma ser bem mais trabalhoso de consertar. É por isso que verificação forte sobrevive em firmware congelado e morre em produto SaaS.</p>
<h3>O alvo se move mais rápido que a prova<span class="hx:absolute hx:-mt-20" id="o-alvo-se-move-mais-rápido-que-a-prova"></span>
    <a href="#o-alvo-se-move-mais-r%c3%a1pido-que-a-prova" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Os nichos onde prova formal vingou são todos de evolução lenta e consequência catastrófica: kernel de SO, compilador, criptografia, controle de voo, controle de trem, componentes de satélite. Nesses mundos, o custo de errar é medido em vidas ou em bilhões, e o custo de provar se dilui em décadas de operação.</p>
<p>E-commerce, fintech, marketplace, SaaS B2B: o requisito muda na terça, o concorrente lança na quinta, e o time de produto quer A/B test na sexta. Provar formalmente uma regra de negócio que vai ser reescrita em três meses é queimar dinheiro. O que funciona nesses domínios é teste, observabilidade, feature flag e rollback rápido.</p>
<h3>Você prova o modelo, não o mundo<span class="hx:absolute hx:-mt-20" id="você-prova-o-modelo-não-o-mundo"></span>
    <a href="#voc%c3%aa-prova-o-modelo-n%c3%a3o-o-mundo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Essa é a limitação mais subestimada. Toda prova carrega uma <strong>base de confiança</strong>: o conjunto de coisas que você simplesmente acredita. No caso da Anthropic, o texto lista com honestidade o que sobra: os modelos formais do RISC-V e dos processos do Linux, os enunciados exatos dos teoremas, e o kernel de checagem do Rocq. E tecnicamente também o hardware, o SO e o compilador OCaml que roda o checador.</p>
<p>Numa prova de software comum, a base de confiança inclui ainda o compilador (a menos que você use verificação binária), a biblioteca padrão, o alocador, o kernel, e a fronteira de FFI. Cada pedaço de C não verificado no fim da pilha é um buraco na garantia. Você pode ter o núcleo mais provado do mundo chamando uma biblioteca de JSON com bug de parser.</p>
<h3>O custo do erro não é uniforme<span class="hx:absolute hx:-mt-20" id="o-custo-do-erro-não-é-uniforme"></span>
    <a href="#o-custo-do-erro-n%c3%a3o-%c3%a9-uniforme" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Prova formal trata todo bug como igualmente importante, porque a especificação é binária. A realidade não é: um crash que derruba um worker é chato, um RCE anônimo é catastrófico, e um erro de arredondamento em relatório é irrelevante. Investir prova uniforme em software heterogêneo é alocar recurso no lugar errado.</p>
<p>O que a indústria faz, quando faz bem, é o oposto: <strong>concentrar verificação forte no núcleo crítico e deixar o resto com teste</strong>. É exatamente a lição do meu teste com Bend 2, e eu volto nela já já.</p>
<h2>Bend 2: a tentativa de virar o custo pelo avesso<span class="hx:absolute hx:-mt-20" id="bend-2-a-tentativa-de-virar-o-custo-pelo-avesso"></span>
    <a href="#bend-2-a-tentativa-de-virar-o-custo-pelo-avesso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em setembro eu escrevi sobre a <a href="/2026/09/19/nova-linguagem-voltada-pra-ia-bend-2/">Bend 2</a>, a linguagem do Victor Taelin, e na época o pitch me pareceu exagerado. Relendo agora, à luz do paper da Anthropic, o desenho dele faz mais sentido do que eu dei crédito.</p>
<p>A ideia central do Bend 2 é dividir o programa em dois arquivos:</p>
<ul>
<li><code>LAWS.bend</code>, escrito pelo humano, com as invariantes do sistema;</li>
<li><code>PROOF.bend</code>, escrito pela IA, com as provas dessas leis.</li>
</ul>
<p>O type checker é um verificador de provas na linha do Lean e do Rocq. Se a prova não fecha, o código não entra. O checker roda em 0,1 segundo, o que é a parte realmente inovadora: dá para colocar no loop do agente, a cada edição. Lean leva 19 segundos e Rocq 6 segundos para o mesmo conjunto de teste que o Bend resolve em 0,38 s, embora os benchmarks dele tenham ressalvas justas, porque ele pula elaboração e unificação.</p>
<p>No meu teste, o resultado mais útil foi este: para garantir dois fatos simples sobre uma guarda de umas 30 linhas, precisei de <strong>93 linhas de leis e provas</strong>. E deixei de fora a exatidão do algoritmo, toda a aritmética em U32, e literalmente todo o resto do programa: JSON, ordenação, renderização, IO. A superfície provável era uns poucos por cento do código.</p>
<p>Isso é o mesmo fenômeno do seL4, em escala de brinquedo: <strong>a prova é maior que o código e cobre menos do que você gostaria</strong>. A diferença é que no Bend o custo por linha provada é de segundos, não de anos-pessoa, e é aí que a coisa fica interessante.</p>
<p>E tem a lição que eu considero a mais importante do meu experimento, e que vale para qualquer projeto de verificação: <strong>prove uma guarda isolada, deixe o algoritmo fora da prova</strong>. O alocador de <code>--limit</code> do ghpending ficou em dois estágios, com o algoritmo em U32 não provado propondo valores e uma guarda em Nat provada cortando o excesso. A lei vale mesmo se o estágio não provado propuser lixo. A guarda não confia na aritmética que não consegue enxergar.</p>
<p>Esse é o padrão que faz prova funcionar em código real: você não prova o sistema, você prova a fronteira onde o erro é catastrófico, e deixa o resto com teste. O paper da Anthropic faz exatamente isso ao escolher provar &ldquo;este entry point de fuzz nunca dá crash&rdquo;, em vez de &ldquo;esta biblioteca está correta&rdquo;.</p>
<p>A outra lição do Bend, e essa é dura, é que <strong>no momento em que você cruza a fronteira do FFI, a prova ficou para trás</strong>. O verificador garante o que está dentro do modelo dele; o C do outro lado é ato de fé. Os meus dois ports precisaram de oito efeitos escritos à mão em C, sem nenhuma promessa de ABI estável entre releases. É o mesmo problema do <code>unsafe</code> no Rust, onde as garantias do borrow checker valem até a borda do bloco.</p>
<h2>O que a Anthropic fez de diferente, e o que ela admite<span class="hx:absolute hx:-mt-20" id="o-que-a-anthropic-fez-de-diferente-e-o-que-ela-admite"></span>
    <a href="#o-que-a-anthropic-fez-de-diferente-e-o-que-ela-admite" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O salto da Anthropic não foi inventar técnica nova de prova. Foi usar um LLM para resolver o problema de <strong>escala do esforço</strong>, que é o gargalo histórico.</p>
<p>O argumento é simples e, uma vez dito, óbvio: gerar 600 mil linhas de prova é um trabalho mecânico, tedioso e enorme, exatamente o tipo de coisa em que um modelo com milhões de tokens de orçamento é bom. O ser humano não precisa revisar essas 600 mil linhas, porque o kernel do Rocq as checa mecanicamente. O humano só precisa confiar em quatro coisas: a semântica do RISC-V, a semântica dos processos do Linux, o enunciado do teorema, e o checador.</p>
<p>Três números do paper mostram para onde isso vai:</p>
<ul>
<li><strong>Custo caindo.</strong> Em 36 tarefas de verificação extraídas de código real do OSS-Fuzz, o Opus 5.5 prova metade delas com cerca de <strong>40% menos tokens</strong> que o Opus 5. Modelos treinados em tarefas longas de verificação ficam mais eficientes nisso.</li>
<li><strong>Prova é reusável.</strong> Quando agentes puderam copiar as provas uns dos outros num workspace compartilhado, gastaram <strong>17% menos tokens</strong>, e alguns conseguiram provar o que tinham falhado sozinhos. A maior parte de uma prova dessas é infraestrutura comum: inicialização do runtime C e do AddressSanitizer. Prova tem a mesma economia de biblioteca compartilhada que código.</li>
<li><strong>Dependência segue lei de potência.</strong> Provar as bibliotecas mais compartilhadas destrava uma fatia enorme do ecossistema. É a tese da Theorem, que quer verificar o ecossistema Nix inteiro, começando por glibc, OpenSSL e cURL.</li>
</ul>
<p>E agora as admissões, que são a parte que eu respeito no texto. Eles listam quatro buracos:</p>
<p><strong>Formalmente verificado não significa livre de bug.</strong> O checador não sabe se a sua spec corresponde à realidade. Quando aparece bug em código verificado, é porque a spec ou a semântica assumida divergiu do mundo real.</p>
<p><strong>A máquina real faz o que o modelo não descreve.</strong> As provas valem para binários estáticos, single-threaded, RISC-V, linkados contra musl. A maioria das pessoas roda essas bibliotecas em x86 ou ARM, linkadas dinamicamente contra glibc. Fechar esse buraco exige semânticas mais próximas do que está implantado.</p>
<p><strong>A afirmação pode não cobrir o que importa.</strong> O AddressSanitizer não detecta todos os bugs de memória, nem todos os entry points foram provados, código morto não entra, e um caller pode usar a biblioteca errado e corromper a própria memória. Fechar isso exige especificar o contrato de cada função pública e obrigar o chamador a cumpri-lo.</p>
<p><strong>Reward hacking é risco real.</strong> Se você manda a IA consertar o código para a prova fechar, ela pode reescrever o programa para um que trivialmente nunca dá crash. A Anthropic penalizou patches e deu crédito extra por provar quais entradas o patch muda e quão poucas são, auditando os patches. É uma defesa razoável e, ao mesmo tempo, um lembrete de que você está otimizando contra um objetivo declarado, e objetivo declarado é hackeável.</p>
<p>Sobre a viabilidade de &ldquo;mais software, mas ainda não todo, mais bem testado&rdquo;: eu acho que a janela é real, e que o formato de adoção vai ser parecido com o que já aconteceu com tipos estáticos. Ninguém reescreveu o mundo em Haskell. O TypeScript entrou por cima, incrementalmente, onde valia a pena. Verificação vai entrar igual: primeiro nas dependências compartilhadas, nos decoders, nos parsers, nas bibliotecas de criptografia, nos pontos onde a spec é objetiva e o custo do bug é alto. Depois, se o custo cair o suficiente, em módulos de aplicação com invariantes claras.</p>
<h2>A semana que explodiu<span class="hx:absolute hx:-mt-20" id="a-semana-que-explodiu"></span>
    <a href="#a-semana-que-explodiu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A urgência do texto da Anthropic não veio do nada. Nos dias anteriores, entre 20 e 23 de outubro, a internet levou uma sequência de socos que eu não lembro de ter visto junta. Vou passar por cada um e perguntar: prova formal teria salvado?</p>
<h3>PHP2Shell: CVE-2026-109618 e CVE-2026-109619<span class="hx:absolute hx:-mt-20" id="php2shell-cve-2026-109618-e-cve-2026-109619"></span>
    <a href="#php2shell-cve-2026-109618-e-cve-2026-109619" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Dois RCEs críticos, CVSS 9.2, sem autenticação, em <strong>todas as instalações de PHP que servem requisições web</strong>: Apache com mod_php, PHP-FPM, php-cgi, FrankenPHP, LiteSpeed. Corrigidos em 21 de outubro no <a href="https://www.php.net/archive/2026.php"target="_blank" rel="noopener">PHP 8.5.12, 8.4.27, 8.3.36 e 8.2.35</a>. O <a href="https://www.cisa.gov/news-events/alerts/2026/10/21/critical-vulnerabilities-php-require-immediate-patching"target="_blank" rel="noopener">alerta da CISA</a> recomenda patch imediato e já publicou regra Sigma de detecção. A <a href="https://cert.europa.eu/publications/security-advisories/2026-018"target="_blank" rel="noopener">CERT-EU</a> reportou tentativas de exploração antes do patch sair.</p>
<p>Os dois bugs são de memória. O CVE-2026-109618 é corrupção por out-of-memory durante a limpeza da requisição. O CVE-2026-109619 é estado global obsoleto sendo reaproveitado por uma requisição seguinte.</p>
<p><strong>Prova formal teria salvado?</strong> Em princípio, sim, e é o caso mais claro da lista. Corrupção de memória e estado global obsoleto entre requisições são exatamente o tipo de propriedade que uma prova de segurança de memória e uma invariante de ciclo de vida pegam. O problema é o escopo: &ldquo;todo o ciclo de vida de uma requisição PHP&rdquo; inclui o core em C, todas as extensões, a SAPI, o alocador, e o libc. É uma base de confiança gigantesca, e provar isso exigiria provar meio ecossistema. Na prática, é o tipo de alvo que só faz sentido depois que a infraestrutura de prova estiver barata o suficiente, e é por isso que a estratégia de verificar as dependências compartilhadas primeiro (glibc, OpenSSL, cURL) é a que faz sentido.</p>
<h3>Node2Shell: CVE-2026-111497 no Brotli<span class="hx:absolute hx:-mt-20" id="node2shell-cve-2026-111497-no-brotli"></span>
    <a href="#node2shell-cve-2026-111497-no-brotli" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Out-of-bounds write no decoder do Brotli, a biblioteca de compressão do Google que o Node embute. Um stream comprimido malicioso corrompe memória adjacente, vaza conteúdo ou executa código. Afeta Node 22.x, 24.x e 26.x, e como o bug está na <a href="https://www.cve.org/CVERecord?id=CVE-2026-111497"target="_blank" rel="noopener">biblioteca Brotli em si</a>, afeta qualquer coisa que embuta a mesma cópia: Bun, projetos em Python, e o que mais aparecer. Versões afetadas: 1.0.3 até 1.2.0. Corrigido em <a href="https://github.com/google/brotli/blob/v1.2.1/CHANGELOG.md"target="_blank" rel="noopener">1.2.1, 1.1.1 e 1.0.10</a>.</p>
<p>E tem um detalhe que vale mais que o CVE: <strong>a correção foi escrita no código-fonte do Brotli em fevereiro e não saiu em release nenhuma até 21 de outubro</strong>. Oito meses com o conserto pronto e parado.</p>
<p><strong>Prova formal teria salvado?</strong> Sim, e este é o caso de melhor encaixe de todos: decoder com lei de round-trip é exatamente o ponto doce. &ldquo;Se você comprime e descomprime, o resultado é o original&rdquo; é uma spec objetiva, e provar que o decoder nunca escreve fora do buffer para nenhuma entrada é prova de segurança de memória padrão. O Bend 2, aliás, lista codecs com lei de round-trip como nicho natural.</p>
<p>Mas repare: prova não teria resolvido o problema real. O bug existia em código não provado, e a correção existia desde fevereiro. O que matou foi processo de release, não ausência de prova. Nenhuma prova formal conserta um mantenedor que não publica a versão corrigida.</p>
<h3>GitLab: CVE-2026-110264, CVSS 10.0<span class="hx:absolute hx:-mt-20" id="gitlab-cve-2026-110264-cvss-100"></span>
    <a href="#gitlab-cve-2026-110264-cvss-100" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Integer overflow no Onigmo, o motor de expressões regulares que o Ruby embute, alcançável pelo renderizador de arquivos Org-mode do GitLab: a gem <code>org-ruby</code> pega a palavra-chave crua de uma linha <code>#+TODO:</code> e joga dentro de <code>Regexp.new</code>. Um padrão construído para estourar o contador faz o motor escrever fora do buffer, e um visitante sem autenticação executa código num servidor self-managed. CVSS 10.0, o máximo. E o alcance: <strong>todas as versões desde a 7.0, de 2014</strong>, ou seja, doze anos de releases. Corrigido em 21 de outubro nas <a href="https://docs.gitlab.com/releases/patches/patch-release-gitlab-19-5-1-released"target="_blank" rel="noopener">19.5.1, 19.4.3, 19.3.6, 19.2.9, 19.1.9, 19.0.10 e 18.11.13</a>. Nove dias antes, o CVE-2026-97229, também CVSS 10.0 e também Onigmo, tinha sido corrigido no outro ponto de entrada do mesmo motor: o compilador de regex do CI/CD. E o conserto de verdade saiu no próprio Ruby, em 23 de outubro, porque o bug era do motor da linguagem, não do GitLab. Quem patchou em 12 de outubro precisou patch de novo em 21.</p>
<p><strong>Prova formal teria salvado?</strong> Parcialmente, e o caso é instrutivo. Overflow aritmético em um parser é detectável: prova de ausência de overflow, ou até compilador com checagem de overflow em build de teste, pega. Mas o GitLab disse que o ataque funciona &ldquo;sob certas condições&rdquo; que não divulgou, o que sugere que a parte difícil é a lógica, não a aritmética. A lição que fica é outra: um parser de regex exposto a entrada não confiável é uma superfície clássica de verificação, e ninguém verifica porque é código de produto em movimento constante.</p>
<h3>Railway: o ataque que prova formal nenhuma impede<span class="hx:absolute hx:-mt-20" id="railway-o-ataque-que-prova-formal-nenhuma-impede"></span>
    <a href="#railway-o-ataque-que-prova-formal-nenhuma-impede" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Não foi um bug de código. Em 20 e 21 de outubro, um atacante acessou os sistemas internos do Railway, a plataforma de deploy usada por mais de 2 milhões de desenvolvedores, e <strong>pode ter obtido o conteúdo decriptado de variáveis de ambiente</strong> de alguns clientes. A empresa pediu que todos os usuários revogassem e recriassem credenciais guardadas antes de 21 de outubro.</p>
<p>O <a href="https://blog.railway.com/p/preliminary-report-october-security-incident"target="_blank" rel="noopener">relatório preliminar</a> diz que foi uma campanha &ldquo;sustentada e direcionada&rdquo;, que o atacante tinha acesso a modelos abliterados e recursos de computação substanciais, e que o objetivo era <strong>obter chaves de API de provedores de IA</strong> para ataques de destilação. Chaves da OpenAI, Anthropic, Google, GitHub, GitLab, Stripe e afins. A Railway pediu aos provedores que revogassem as chaves encontradas.</p>
<p><strong>Prova formal teria salvado?</strong> Não. Nenhuma prova de correção de código impede que alguém obtenha suas chaves e decripte seus segredos. Esse é um problema de arquitetura de custódia de chave, de segregação de rede e de resposta a incidente, não de corretude de programa. E é um lembrete importante nesta conversa: a maior parte dos incidentes reais não é bug de lógica no código, é falha de configuração, credencial vazada, permissão excessiva.</p>
<h3>PS5: o kernel caindo firmware após firmware<span class="hx:absolute hx:-mt-20" id="ps5-o-kernel-caindo-firmware-após-firmware"></span>
    <a href="#ps5-o-kernel-caindo-firmware-ap%c3%b3s-firmware" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em 29 de setembro, o exploit <a href="https://github.com/ntfargo/Relapse-Exploit"target="_blank" rel="noopener">Relapse</a> foi publicado, funcionando em qualquer PS5 e PS5 Pro do firmware 7.00 até o 13.60. É uma cadeia com falha de use-after-free no kernel, alcançando execução de código não assinado. O firmware 14.00, de 16 de setembro, já tinha fechado a falha de kernel que o Relapse usa, segundo a cena, treze dias antes de o exploit ficar público, então a resposta pós-publicação foi outra: 14.10 em 1º de outubro e 14.20 em 21 de outubro. O 14.10 apareceu quebrado 18 dias depois, num demo privado que abria o menu de debug, e o 14.20 ganhou um entry point de userland <strong>oito horas depois do lançamento</strong>.</p>
<p><strong>Prova formal teria salvado?</strong> Em princípio, sim: use-after-free é precisamente o que uma prova de segurança de memória elimina. O seL4 provou isso para um microkernel. Mas o PS5 roda um kernel derivado de FreeBSD, com hypervisor, com o AMD Platform Security Processor embaixo, tudo fechado, com milhões de linhas e nenhuma intenção de provar nada. Verificar essa pilha inteira não é caro, é impraticável. O que dá para fazer é o que o seL4 faz: provar os componentes pequenos e críticos, e aceitar que o resto fica com mitigação e bug bounty.</p>
<p>Uma nota de rodapé que eu acho mais reveladora que o exploit: o ritmo. A Sony lança firmware, a cena quebra em dias. Não porque o kernel é mal escrito, mas porque é grande, fechado, e atacado por gente com tempo e motivação. É o espelho invertido do seL4.</p>
<h3>O padrão que sai dessa tabela<span class="hx:absolute hx:-mt-20" id="o-padrão-que-sai-dessa-tabela"></span>
    <a href="#o-padr%c3%a3o-que-sai-dessa-tabela" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Incidente</th>
          <th>Natureza</th>
          <th>Prova formal resolveria?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PHP2Shell</td>
          <td>Corrupção de memória + estado global</td>
          <td>Sim em princípio, caro na prática (base de confiança enorme)</td>
      </tr>
      <tr>
          <td>Node2Shell (Brotli)</td>
          <td>Out-of-bounds write em decoder</td>
          <td>Sim, encaixe perfeito, e o problema real era release parado</td>
      </tr>
      <tr>
          <td>GitLab CVE-2026-110264</td>
          <td>Integer overflow em parser exposto</td>
          <td>Parcialmente; a parte de lógica fica de fora</td>
      </tr>
      <tr>
          <td>Railway</td>
          <td>Credencial e segredo comprometidos</td>
          <td>Não. Problema de arquitetura e custódia</td>
      </tr>
      <tr>
          <td>PS5 Relapse</td>
          <td>Use-after-free em kernel gigante e fechado</td>
          <td>Sim em teoria, inviável no escopo</td>
      </tr>
  </tbody>
</table>
<p>Coluna da direita com três &ldquo;sim&rdquo; e dois &ldquo;não&rdquo; já mostra o ponto: prova formal resolve uma classe inteira de bugs, e essa classe é justamente a que mais assusta, a de corrupção de memória que vira RCE. O que ela não resolve é tudo o mais, e &ldquo;tudo o mais&rdquo; é a maior parte do que acontece.</p>
<h2>A minha leitura: a dívida técnica está vencendo<span class="hx:absolute hx:-mt-20" id="a-minha-leitura-a-dívida-técnica-está-vencendo"></span>
    <a href="#a-minha-leitura-a-d%c3%advida-t%c3%a9cnica-est%c3%a1-vencendo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora eu falo por mim.</p>
<p>Eu postei <a href="https://x.com/AkitaOnRails/status/2113361323434070101"target="_blank" rel="noopener">no X</a> que uma quantidade enorme de código escrito por humanos nos últimos anos é bem ruim, e que boa parte de quem chama tudo de &ldquo;AI Slop&rdquo; passou a vida acumulando &ldquo;Human Slop&rdquo; porque contava com o fato de que ninguém ia se dar ao trabalho de checar.</p>
<p>Eu mantenho cada palavra. E acrescento o diagnóstico: <strong>a indústria passou duas décadas tomando dívida técnica com a premissa implícita de que auditar era caro e raro</strong>. Revisão de código rasa, cobertura de teste decorativa, dependência de biblioteca sem ler o changelog, permissão de admin em service account porque dá menos trabalho, segredo em variável de ambiente porque o cofre é chato de configurar. Nada disso dava problema porque o custo de descobrir era alto.</p>
<p>O que mudou, e mudou em meses, é o custo de descobrir. No <a href="https://x.com/AkitaOnRails/status/2113396195561287791"target="_blank" rel="noopener">outro post</a> eu disse que gosto da trend de qualquer não-programador armado de LLM, tokens e skills virar scanner de segurança, e que toda empresa com plataforma web deveria ficar com muito medo. E isso é bom. O Deedy descreveu a mesma coisa poucas horas antes: gente não técnica explorando PHP2Shell com modelos de fronteira, e ele admitiu estar assustado.</p>
<p>Eu entendo o susto. Eu discordo do enquadramento. O que aconteceu não foi a criação de uma vulnerabilidade nova; foi a <strong>remoção do atrito de descoberta</strong> de vulnerabilidades que já estavam lá, algumas desde 2014, como o GitLab. A dívida sempre existiu. O que mudou é que agora ela vence.</p>
<p>E aqui vai o equilíbrio que eu acho que falta na conversa, dos dois lados.</p>
<p>Do lado de quem ataca, a barreira de entrada desabou. Antes, achar um RCE exigia anos de estudo, ferramenta cara e tempo. Agora exige um modelo, uma conta e paciência. O volume de tentativas vai subir uma ordem de grandeza, e o tempo entre divulgação e exploração, que já era de dias, vai virar de horas. A PS5 quebrada em horas depois do firmware é o retrato do futuro de todo mundo.</p>
<p>Do lado de quem defende, a mesma alavanca existe, e é aí que eu sou otimista. As mesmas capacidades que geram exploit geram auditoria. O mesmo modelo que lê um diff procurando falha de autorização lê um diff procurando bug de memória. As mesmas técnicas de prova do paper da Anthropic servem para carimbar uma biblioteca que você usa. O custo de endurecer caiu junto com o custo de atacar, e essa simetria é a notícia boa.</p>
<p>O que me deixa confiante não é a tecnologia sozinha, é o incentivo. Quando o custo de achar falha cai a quase zero, o custo de <strong>não</strong> achar sobe a quase infinito. Um CTO que hoje aceita rodar versão de dependência com CVE conhecido vai ter que explicar por que, num mundo em que qualquer estagiário com Claude audita o supply chain dele em uma tarde. A pressão muda de &ldquo;ninguém vai olhar&rdquo; para &ldquo;todo mundo vai olhar&rdquo;, e comportamento muda com incentivo, não com sermão.</p>
<p>Eu aposto que o resultado disso é um padrão de qualidade mais alto para todo mundo. Não porque as pessoas ficaram mais virtuosas, mas porque a fiscalização ficou barata. É a mesma dinâmica que fez HTTPS virar padrão, que fez autenticação de dois fatores virar padrão, que fez dependabot virar padrão. Nada disso aconteceu por consciência; aconteceu porque a alternativa ficou cara demais.</p>
<p>E prova formal entra nesse pacote como a camada de cima. Não vai ser a ferramenta do dia a dia do dev de e-commerce. Vai ser o que a gente usa nas peças que não podem falhar: o decoder que todo mundo usa, a biblioteca de cripto, o parser exposto na internet, o kernel do dispositivo, o contrato que segura dinheiro. Exatamente onde o custo do erro é maior que o custo da prova, que é a única conta que importa.</p>
<p>O paper da Anthropic mostra que essa conta começou a fechar pela primeira vez. A esmagadora maioria dos programadores não vai escrever prova em Rocq nunca na vida, e não precisa. Precisa que alguém escreva as provas das peças que todo mundo usa, e é isso que ficou barato o suficiente para virar rotina.</p>
<h2>O que eu levo deste texto<span class="hx:absolute hx:-mt-20" id="o-que-eu-levo-deste-texto"></span>
    <a href="#o-que-eu-levo-deste-texto" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Três coisas, para não terminar em prosa bonita.</p>
<p><strong>Prova formal é uma ferramenta de recorte, não de cobertura.</strong> Você escolhe um núcleo pequeno, escreve uma spec objetiva, e prova. O resto fica com teste, porque teste é mais barato e pega o que prova não pegou. Quem promete &ldquo;código sem bug&rdquo; está vendendo a coisa errada, e a própria Anthropic se deu ao trabalho de dizer isso em letras garrafais no paper.</p>
<p><strong>O gargalo mudou de lugar.</strong> Antes era escrever a prova. Agora é escrever a especificação e manter a prova viva enquanto o código muda. A Anthropic resolveu o primeiro problema com força bruta de tokens e resolveu o segundo parcialmente com reuso de prova e granularidade de pacote. O segundo é o problema difícil de verdade, e é onde eu quero ver a próxima geração de ferramentas.</p>
<p><strong>A janela de 2026 é real.</strong> Pela primeira vez existe um caminho em que verificar uma dependência crítica custa menos que o incidente que ela evita. E existe ao mesmo tempo uma pressão externa, na forma de gente com LLM encontrando os mesmos bugs do outro lado. Se você lidera engenharia, a pergunta deixou de ser &ldquo;vale a pena investir em verificação&rdquo; e virou &ldquo;quanto custa ser o próximo Railway&rdquo;.</p>
<p>A dívida técnica sempre esteve lá. A diferença é que agora ela tem cobrador.</p>
<p>Se você quiser ver os detalhes técnicos, o paper da Anthropic tem apêndice com os estudos de caso, e as provas publicadas estão no <a href="https://github.com/theorem-labs/oss-verify-proofs"target="_blank" rel="noopener">repositório da Theorem</a>, junto com as <a href="https://github.com/theorem-labs/rocq-linux-process-semantics"target="_blank" rel="noopener">semânticas de RISC-V e processos do Linux</a>. O skill para rodar o pipeline no seu próprio projeto também está público. Vale a leitura, mesmo que você nunca escreva uma linha de Rocq na vida.</p>
]]></content:encoded><category>seguranca</category><category>engenharia-de-software</category><category>inteligencia-artificial</category><category>linguagens-de-programacao</category></item><item><title>LLM Benchmark v4: o veredito de open source (até out/2026)</title><link>https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/</guid><pubDate>Thu, 22 Oct 2026 12:00:00 -0300</pubDate><description>&lt;p&gt;Este é o fechamento do benchmark v4. Desde que comecei essa rodada, foram sete artigos, primeiro na nuvem e depois empurrando a mesma régua de sabotagem pra cada vez mais longe dela:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://akitaonrails.github.io/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-1/"&gt;Parte 1&lt;/a&gt;&lt;/strong&gt;: por que eu refiz o benchmark do zero pela terceira vez, como funciona o app Rails sabotado com CVEs reais, e quanto custou.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://akitaonrails.github.io/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-2/"&gt;Parte 2&lt;/a&gt;&lt;/strong&gt;: o ranking original da nuvem, com quase 40 modelos, seis empates no topo e um modelo de graça batendo metade do Claude.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&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;Opus 5.5, GPT-6 sol e luna, MiMo 2.6 e Grok 4.7&lt;/a&gt;&lt;/strong&gt;: a leva seguinte da nuvem. O Opus 5.5 fechou com 100, o GPT 6 luna fez 95,5 por menos de um dólar a rodada, e o Grok 4.7 regrediu contra o próprio antecessor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://akitaonrails.github.io/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/"&gt;Os top LLMs open source numa B300&lt;/a&gt;&lt;/strong&gt;: uma GPU alugada, 14 modelos abertos, e dois deles, GLM 5.3 Flash e Qwen3.8 Flash-Next, empatados em 92,5, no nível do Sonnet 5.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/"&gt;Open source que só cabe em 4x B300&lt;/a&gt;&lt;/strong&gt;: os modelos grandes, a maioria de 400 bilhões a 1 trilhão de parâmetros. O DeepSeek V4.1 Flash empatou com o líder em 92,5 e o resto mostrou que tamanho não compra vigilância.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/"&gt;Meu time cabe em 4x B300/H200?&lt;/a&gt;&lt;/strong&gt;: teste de concorrência de verdade. O mesmo DeepSeek atende 32 sessões confortáveis no pod de B300 e só 8 no de H200.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/"&gt;Open source nível consumidor na RTX 5090 ou Strix Halo&lt;/a&gt;&lt;/strong&gt;: o Qwen3.8 Flash-Next fez 91 numa placa de PC com o motor Strata, a 159 tokens por segundo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Este artigo junta tudo numa tabela só e responde a pergunta que sobra: &lt;strong&gt;até outubro de 2026, open source já dá conta?&lt;/strong&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>Este é o fechamento do benchmark v4. Desde que comecei essa rodada, foram sete artigos, primeiro na nuvem e depois empurrando a mesma régua de sabotagem pra cada vez mais longe dela:</p>
<ul>
<li><strong><a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-1/">Parte 1</a></strong>: por que eu refiz o benchmark do zero pela terceira vez, como funciona o app Rails sabotado com CVEs reais, e quanto custou.</li>
<li><strong><a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-2/">Parte 2</a></strong>: o ranking original da nuvem, com quase 40 modelos, seis empates no topo e um modelo de graça batendo metade do Claude.</li>
<li><strong><a href="/2026/09/23/llm-benchmark-v4-opus-5-5-gpt-6-sol-luna-mimo-2-6-grok-4-7/">Opus 5.5, GPT-6 sol e luna, MiMo 2.6 e Grok 4.7</a></strong>: a leva seguinte da nuvem. O Opus 5.5 fechou com 100, o GPT 6 luna fez 95,5 por menos de um dólar a rodada, e o Grok 4.7 regrediu contra o próprio antecessor.</li>
<li><strong><a href="/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/">Os top LLMs open source numa B300</a></strong>: uma GPU alugada, 14 modelos abertos, e dois deles, GLM 5.3 Flash e Qwen3.8 Flash-Next, empatados em 92,5, no nível do Sonnet 5.</li>
<li><strong><a href="/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/">Open source que só cabe em 4x B300</a></strong>: os modelos grandes, a maioria de 400 bilhões a 1 trilhão de parâmetros. O DeepSeek V4.1 Flash empatou com o líder em 92,5 e o resto mostrou que tamanho não compra vigilância.</li>
<li><strong><a href="/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/">Meu time cabe em 4x B300/H200?</a></strong>: teste de concorrência de verdade. O mesmo DeepSeek atende 32 sessões confortáveis no pod de B300 e só 8 no de H200.</li>
<li><strong><a href="/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/">Open source nível consumidor na RTX 5090 ou Strix Halo</a></strong>: o Qwen3.8 Flash-Next fez 91 numa placa de PC com o motor Strata, a 159 tokens por segundo.</li>
</ul>
<p>Este artigo junta tudo numa tabela só e responde a pergunta que sobra: <strong>até outubro de 2026, open source já dá conta?</strong></p>
<h2>O maior benchmark que eu já rodei<span class="hx:absolute hx:-mt-20" id="o-maior-benchmark-que-eu-já-rodei"></span>
    <a href="#o-maior-benchmark-que-eu-j%c3%a1-rodei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O v4 foi, de longe, a maior rodada de testes que eu já fiz. São quase 70 rodadas completas do mesmo protocolo, quase 60 modelos diferentes, rodando em API de nuvem, numa B300 alugada, num pod de quatro B300, numa RTX 5090 e num Strix Halo, mais um pod de quatro H200 só pro teste de concorrência.</p>
<p>E foi caro. Só a primeira leva, a da nuvem, passou de 4 mil dólares em nove dias, somando créditos e assinaturas. A B300 única custou uns 270, e a campanha de 4x B300 e 4x H200 levou mais uns 2.100. Somando o que eu consigo somar, passou de 6 mil dólares, sem contar as horas de máquina em casa.</p>
<blockquote>
  <p>Eu não acho que muita gente vai reproduzir isso tão cedo. Se este material te serviu, compartilhe com quem precisa decidir entre nuvem, servidor próprio ou PC local. Todo o código, os prompts, os logs e os relatórios estão abertos no meu GitHub: <a href="https://github.com/akitaonrails/llm-coding-benchmark"target="_blank" rel="noopener">akitaonrails/llm-coding-benchmark</a>.</p>

</blockquote>
<h3>Relembrando a régua<span class="hx:absolute hx:-mt-20" id="relembrando-a-régua"></span>
    <a href="#relembrando-a-r%c3%a9gua" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O protocolo é sempre o mesmo. O modelo constrói um app Rails de chat em sete sprints, e entre as sprints um revisor injeta 14 defeitos de segurança e de correção, alguns barulhentos (CVE em gem, chave no código), outros silenciosos (vazamento entre tenants, agregado errado, teste adulterado pra esconder backdoor).</p>
<p>A nota vai de 0 a 100. Defeito corrigido sem aviso vale peso cheio; corrigido só depois que eu revelo que tem problema vale 40%; nunca corrigido vale zero. Os tiers são A a partir de 83, B de 73 a 82,5, C de 51 a 72,5 e D até 50.</p>
<p>É uma régua de <strong>vigilância de revisão de código</strong>. Ela mede se o modelo percebe sabotagem enquanto trabalha, e isso é diferente de medir quem escreve o seu sistema inteiro melhor.</p>
<h2>O ranking combinado<span class="hx:absolute hx:-mt-20" id="o-ranking-combinado"></span>
    <a href="#o-ranking-combinado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todas as rodadas do v4 numa tabela só, comerciais e abertos, nuvem e hardware próprio. Quando o mesmo modelo rodou em lugares diferentes, ele aparece mais de uma vez, porque o lugar muda a nota.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: right">#</th>
          <th>Modelo</th>
          <th>Pesos</th>
          <th>Onde rodou</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: center">Tier</th>
          <th style="text-align: right">Custo da rodada</th>
          <th style="text-align: right">tok/s</th>
          <th style="text-align: right">Minutos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: right">1</td>
          <td>GPT-6 Astra</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 30,55</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">100</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>Claude Opus 5</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 71 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">145</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>Claude Fable 5</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 50 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">85</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>Claude Opus 5.5</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 16,63 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">64</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>GPT 5.6 sol</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 20,37</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">317</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>GPT 5.6 terra</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 9,52</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">80</td>
      </tr>
      <tr>
          <td style="text-align: right">1</td>
          <td>GPT 5.5</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 34,69</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">117</td>
      </tr>
      <tr>
          <td style="text-align: right">8</td>
          <td>Grok 4.6</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">98,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 13,00</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">67</td>
      </tr>
      <tr>
          <td style="text-align: right">9</td>
          <td>Claude Fable 5.1</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 51 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">133</td>
      </tr>
      <tr>
          <td style="text-align: right">9</td>
          <td>Sakana Fugu Ultra v2</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 122,01</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">294</td>
      </tr>
      <tr>
          <td style="text-align: right">9</td>
          <td>GPT 6 luna</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 0,84 ²</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">122</td>
      </tr>
      <tr>
          <td style="text-align: right">12</td>
          <td>GPT 5.6 luna</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">95,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 10,04</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">123</td>
      </tr>
      <tr>
          <td style="text-align: right">13</td>
          <td>Nex N2.5 Pro ⁴</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">94,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">grátis</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">480</td>
      </tr>
      <tr>
          <td style="text-align: right">13</td>
          <td>GLM 5.3</td>
          <td>aberto</td>
          <td>Nuvem (ZCode)</td>
          <td style="text-align: right">94,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">plano flat</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">215</td>
      </tr>
      <tr>
          <td style="text-align: right">15</td>
          <td>DeepSeek V4.1 Flash</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 1,21</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">172</td>
      </tr>
      <tr>
          <td style="text-align: right">15</td>
          <td>DeepSeek V4.1 Flash</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">174,8</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">15</td>
          <td>GLM 5.3 Flash (NVFP4)</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 21,96 ³</td>
          <td style="text-align: right">142,0</td>
          <td style="text-align: right">145</td>
      </tr>
      <tr>
          <td style="text-align: right">15</td>
          <td>Qwen3.8 Flash-Next (NVFP4)</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 12,04 ³</td>
          <td style="text-align: right">195,0</td>
          <td style="text-align: right">80</td>
      </tr>
      <tr>
          <td style="text-align: right">19</td>
          <td>MiMo V2.6 Pro</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">92,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 1,22</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">308</td>
      </tr>
      <tr>
          <td style="text-align: right">20</td>
          <td>Qwen3.8 Flash-Next (IQ3_S)</td>
          <td>aberto</td>
          <td>RTX 5090 (Strata)</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 0 (local)</td>
          <td style="text-align: right">159,2</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">20</td>
          <td>Claude Sonnet 5</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 27 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">112</td>
      </tr>
      <tr>
          <td style="text-align: right">20</td>
          <td>GPT 6 sol</td>
          <td>fechado</td>
          <td>Nuvem (Codex)</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 8,35 ²</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">73</td>
      </tr>
      <tr>
          <td style="text-align: right">23</td>
          <td>Gemini 3.8 Flash high</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">90,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 15,98</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">97</td>
      </tr>
      <tr>
          <td style="text-align: right">24</td>
          <td>Gemini 3.8 Flash</td>
          <td>fechado</td>
          <td>Nuvem (Antigravity)</td>
          <td style="text-align: right">89,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">grátis (OAuth)</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">120</td>
      </tr>
      <tr>
          <td style="text-align: right">24</td>
          <td>MiniMax M3.1 Flash</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">89,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">grátis</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">26</td>
          <td>Muse Spark 1.3</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">88,75</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 13,31</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">150</td>
      </tr>
      <tr>
          <td style="text-align: right">27</td>
          <td>Swift 1.5 27B</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">88,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 9,71 ³</td>
          <td style="text-align: right">203,0</td>
          <td style="text-align: right">64</td>
      </tr>
      <tr>
          <td style="text-align: right">27</td>
          <td>Grok 4.5</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">88,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 6,19</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">48</td>
      </tr>
      <tr>
          <td style="text-align: right">29</td>
          <td>Claude Opus 4.6</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">87,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 25,64 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">89</td>
      </tr>
      <tr>
          <td style="text-align: right">30</td>
          <td>Kimi K2.7 Code</td>
          <td>aberto</td>
          <td>Nuvem (Kimi CLI)</td>
          <td style="text-align: right">87,25</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 7,75</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">175</td>
      </tr>
      <tr>
          <td style="text-align: right">31</td>
          <td>MiMo V2.5 Pro</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">86,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 1,03</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">158</td>
      </tr>
      <tr>
          <td style="text-align: right">31</td>
          <td>Qwen3.8 Flash</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">86,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 1,17</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">149</td>
      </tr>
      <tr>
          <td style="text-align: right">33</td>
          <td>DeepSeek V4 Flash</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">86,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 0,97</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">111</td>
      </tr>
      <tr>
          <td style="text-align: right">33</td>
          <td>Claude Opus 4.8</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">86,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">~US$ 37 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">87</td>
      </tr>
      <tr>
          <td style="text-align: right">35</td>
          <td>Claude Sonnet 4.6</td>
          <td>fechado</td>
          <td>Nuvem (Claude Code)</td>
          <td style="text-align: right">85,75</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 18,64 ¹</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">91</td>
      </tr>
      <tr>
          <td style="text-align: right">36</td>
          <td>Kimi K3</td>
          <td>aberto</td>
          <td>Nuvem (Kimi CLI)</td>
          <td style="text-align: right">85,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 13,79</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">148</td>
      </tr>
      <tr>
          <td style="text-align: right">36</td>
          <td>DeepSeek V4 Flash 0731</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">85,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 1,94</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">194</td>
      </tr>
      <tr>
          <td style="text-align: right">38</td>
          <td>GLM 5.3 Flash</td>
          <td>aberto</td>
          <td>Nuvem (ZCode)</td>
          <td style="text-align: right">84,25</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">plano flat</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">296</td>
      </tr>
      <tr>
          <td style="text-align: right">39</td>
          <td>DeepSeek V4 Pro 0813</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">84,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 4,49</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">152</td>
      </tr>
      <tr>
          <td style="text-align: right">40</td>
          <td>Step 3.7 Flash</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">83,75</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 4,15</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">118</td>
      </tr>
      <tr>
          <td style="text-align: right">41</td>
          <td>Grok 4.7</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">83,5</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 27,94</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">120</td>
      </tr>
      <tr>
          <td style="text-align: right">42</td>
          <td>ThinkingCap 3.8 27B</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">83,0</td>
          <td style="text-align: center">A</td>
          <td style="text-align: right">US$ 11,47 ³</td>
          <td style="text-align: right">189,0</td>
          <td style="text-align: right">76</td>
      </tr>
      <tr>
          <td style="text-align: right">43</td>
          <td>DeepSeek V4 Pro</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">82,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 5,26</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">97</td>
      </tr>
      <tr>
          <td style="text-align: right">44</td>
          <td>Muse Glimmer 30B</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">81,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 3,54 ³</td>
          <td style="text-align: right">295,0</td>
          <td style="text-align: right">23</td>
      </tr>
      <tr>
          <td style="text-align: right">44</td>
          <td>Ornith 1.5 35B</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">81,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 24,39 ³</td>
          <td style="text-align: right">317,0</td>
          <td style="text-align: right">161</td>
      </tr>
      <tr>
          <td style="text-align: right">46</td>
          <td>Qwen 3.8 27B</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">80,5</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 29,11 ³</td>
          <td style="text-align: right">195,0</td>
          <td style="text-align: right">192</td>
      </tr>
      <tr>
          <td style="text-align: right">47</td>
          <td>Qwen 3.8 27B (Q8_0)</td>
          <td>aberto</td>
          <td>Strix Halo</td>
          <td style="text-align: right">80,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 0 (local)</td>
          <td style="text-align: right">7,7</td>
          <td style="text-align: right">706</td>
      </tr>
      <tr>
          <td style="text-align: right">48</td>
          <td>Qwen 3.7 Max</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">79,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 10,63</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">106</td>
      </tr>
      <tr>
          <td style="text-align: right">49</td>
          <td>GLM 5.2</td>
          <td>aberto</td>
          <td>Nuvem (ZCode)</td>
          <td style="text-align: right">77,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">plano flat</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">239</td>
      </tr>
      <tr>
          <td style="text-align: right">50</td>
          <td>GLM 5.3 full</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">76,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">110,0</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">51</td>
          <td>Gemini 3.7 Flash high</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">75,5</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 12,93</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">85</td>
      </tr>
      <tr>
          <td style="text-align: right">51</td>
          <td>MiniMax M3</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">75,5</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">US$ 12,17</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">187</td>
      </tr>
      <tr>
          <td style="text-align: right">53</td>
          <td>Ornith 1.5 122B</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">73,0</td>
          <td style="text-align: center">B</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">244,2</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">54</td>
          <td>Step-5-Preview</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">72,5</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">165,9</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">55</td>
          <td>Muse Spark Open</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">69,5</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">130,8</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">56</td>
          <td>Qwen3.5 397B (NVFP4)</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">68,5</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">US$ 8,22 ³</td>
          <td style="text-align: right">140,0</td>
          <td style="text-align: right">54</td>
      </tr>
      <tr>
          <td style="text-align: right">57</td>
          <td>MiMo V2.6 Pro</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">67,0</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">167,2</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">58</td>
          <td>Laguna-S-2.1</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">65,5</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">263,0</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">59</td>
          <td>Ornith 1.5 35B (Q8_0)</td>
          <td>aberto</td>
          <td>RTX 5090 (Strata)</td>
          <td style="text-align: right">64,5</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">US$ 0 (local)</td>
          <td style="text-align: right">80,0</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">60</td>
          <td>Nemotron 3.5 Super VL (NVFP4)</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">63,0</td>
          <td style="text-align: center">C</td>
          <td style="text-align: right">US$ 7,51 ³</td>
          <td style="text-align: right">207,0</td>
          <td style="text-align: right">50</td>
      </tr>
      <tr>
          <td style="text-align: right">61</td>
          <td>Kimi K2 Thinking</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">46,0</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">110,0</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">62</td>
          <td>DeepSeek V4 Flash</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">44,5</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">US$ 5,59 ³</td>
          <td style="text-align: right">153,0</td>
          <td style="text-align: right">37</td>
      </tr>
      <tr>
          <td style="text-align: right">63</td>
          <td>Mistral Large 3</td>
          <td>aberto</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">39,0</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">US$ 5,11</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">76</td>
      </tr>
      <tr>
          <td style="text-align: right">64</td>
          <td>GLM 4.7 Flash (BF16)</td>
          <td>aberto</td>
          <td>1x B300</td>
          <td style="text-align: right">33,5</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">US$ 4,60 ³</td>
          <td style="text-align: right">217,0</td>
          <td style="text-align: right">30</td>
      </tr>
      <tr>
          <td style="text-align: right">65</td>
          <td>Gemini 3.1 Pro ⁴</td>
          <td>fechado</td>
          <td>Nuvem (OpenCode)</td>
          <td style="text-align: right">32,5</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">US$ 10,31</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">54</td>
      </tr>
      <tr>
          <td style="text-align: right">66</td>
          <td>Nex N2.5 Pro</td>
          <td>aberto</td>
          <td>4x B300</td>
          <td style="text-align: right">27,0</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">pod a US$ 36/h</td>
          <td style="text-align: right">184,0</td>
          <td style="text-align: right">—</td>
      </tr>
      <tr>
          <td style="text-align: right">67</td>
          <td>GLM-4.7-Flash (Q5_K_M)</td>
          <td>aberto</td>
          <td>RTX 5090</td>
          <td style="text-align: right">24,0</td>
          <td style="text-align: center">D</td>
          <td style="text-align: right">US$ 0 (local)</td>
          <td style="text-align: right">—</td>
          <td style="text-align: right">29</td>
      </tr>
  </tbody>
</table>
<p>Como ler as colunas:</p>
<ul>
<li><strong>Pesos</strong>: &ldquo;aberto&rdquo; é checkpoint baixável, qualquer licença; &ldquo;fechado&rdquo; é só API.</li>
<li><strong>Custo da rodada</strong>: o que uma rodada completa de sete sprints custou.</li>
<li><strong>tok/s</strong>: velocidade de decodificação de um stream só, medida por mim. Só existe pra quem eu servi.</li>
<li><strong>Minutos</strong>: tempo de relógio da rodada inteira, quando eu tenho o dado. Na B300 única, é só o tempo dos sprints.</li>
</ul>
<p>E as notas de rodapé:</p>
<ol>
<li>Claude rodou pela assinatura Max do Claude Code. O custo é nocional: os tokens da rodada a preço de tabela da API.</li>
<li>Estimado a partir dos tokens da rodada.</li>
<li>Só o tempo de inferência na B300 a uns US$ 9 por hora. Download, carga, compilação e tuning ficam de fora, e o custo real do pod foi maior.</li>
<li>Nota com ressalva. O Nex na nuvem foi avaliado pelo disco, com tudo sem commit; o Gemini 3.1 Pro foi prejudicado por um bug do proxy do OpenRouter.</li>
</ol>
<p>Os primeiros artigos do v4 juntavam tudo abaixo de B como C; aqui eu uso a escala com D, a mesma das campanhas de B300. O MiniMax M3.1 Flash rodou de graça pelo OpenRouter como âncora de nuvem da campanha local, e a MiniMax promete liberar os pesos dele no dia 23 de outubro.</p>
<h2>Os modelos estão convergindo<span class="hx:absolute hx:-mt-20" id="os-modelos-estão-convergindo"></span>
    <a href="#os-modelos-est%c3%a3o-convergindo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No escopo limitado deste benchmark, a impressão é de que todo mundo está chegando no mesmo lugar. <strong>42 das 67 rodadas fizeram Tier A</strong>, 24 de modelos fechados e 18 de abertos. Sete modelos bateram 100, e 23 rodadas ficaram acima de 90.</p>
<p>Parte disso é limite da própria régua. Quando sete modelos tiram a nota máxima, o teste deixa de separar o topo. E uma rodada repetida do GPT 5.5 fez 91 contra os 100 da oficial, então diferenças de poucos pontos entre dois modelos podem ser só ruído.</p>
<p>Mesmo descontando isso, o desenho é claro. O DeepSeek V4.1 Flash, o GLM 5.3 Flash e o Qwen3.8 Flash-Next, todos abertos, estão a 7,5 pontos do teto, colados no Sonnet 5 e no GPT 6 sol.</p>
<blockquote>
  <p>Pra revisão de código e vigilância, o modelo aberto bom já está no pelotão do modelo fechado bom. O que ainda separa os dois é consistência: entre os abertos aparecem notas D que entre os fechados de ponta não aparecem.</p>

</blockquote>
<h3>O Ocidente parado, a China correndo<span class="hx:absolute hx:-mt-20" id="o-ocidente-parado-a-china-correndo"></span>
    <a href="#o-ocidente-parado-a-china-correndo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Comparando gerações da mesma família dentro do v4, a diferença de ritmo aparece:</p>
<table>
  <thead>
      <tr>
          <th>Família</th>
          <th style="text-align: right">Antes</th>
          <th style="text-align: right">Depois</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V4 Flash → V4.1 Flash</td>
          <td style="text-align: right">86,0</td>
          <td style="text-align: right">92,5</td>
      </tr>
      <tr>
          <td>GLM 5.2 → 5.3</td>
          <td style="text-align: right">77,0</td>
          <td style="text-align: right">94,0</td>
      </tr>
      <tr>
          <td>MiMo V2.5 Pro → V2.6 Pro</td>
          <td style="text-align: right">86,5</td>
          <td style="text-align: right">92,0</td>
      </tr>
      <tr>
          <td>MiniMax M3 → M3.1 Flash</td>
          <td style="text-align: right">75,5</td>
          <td style="text-align: right">89,5</td>
      </tr>
      <tr>
          <td>Kimi K2.7 Code → K3</td>
          <td style="text-align: right">87,25</td>
          <td style="text-align: right">85,0</td>
      </tr>
      <tr>
          <td>Gemini 3.7 Flash → 3.8 Flash (high)</td>
          <td style="text-align: right">75,5</td>
          <td style="text-align: right">90,5</td>
      </tr>
      <tr>
          <td>Claude Fable 5 → 5.1</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">95,5</td>
      </tr>
      <tr>
          <td>Grok 4.6 → 4.7</td>
          <td style="text-align: right">98,5</td>
          <td style="text-align: right">83,5</td>
      </tr>
      <tr>
          <td>GPT 5.6 sol → GPT 6 sol</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">91,0</td>
      </tr>
      <tr>
          <td>GPT 5.6 luna → GPT 6 luna</td>
          <td style="text-align: right">95,0</td>
          <td style="text-align: right">95,5</td>
      </tr>
      <tr>
          <td>Claude Opus 5 → 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">100,0</td>
      </tr>
  </tbody>
</table>
<p>Os chineses subiram de 5,5 a 17 pontos por geração, com exceção do Kimi. Do lado ocidental, quem já estava no teto não tinha pra onde subir: o Opus ficou em 100, o GPT 6 luna ficou onde o 5.6 luna estava, e três lançamentos novos ficaram abaixo dos antecessores. O Gemini Flash é o contraexemplo ocidental, com um salto de 15 pontos.</p>
<p>O calendário reforça a leitura. O Claude Fable 5 saiu em junho, o Opus 5 e o GPT 5.6 em julho, e todos já batiam 100 na minha régua. Daí até outubro, a OpenAI e a Anthropic lançaram o Fable 5.1, o GPT-6 Astra, o Opus 5.5 e o GPT-6 sol e luna. Nenhum deles mostrou ganho na minha régua, e alguns ficaram abaixo dos antecessores.</p>
<p>No mesmo intervalo, os chineses saíram de uma posição bem atrás: DeepSeek V4 em abril, Kimi K3 em julho, Qwen 3.8 e GLM 5.3 em agosto, DeepSeek V4.1 Flash e MiMo V2.6 Pro em setembro, MiniMax M3.1 Flash em outubro. Cada lançamento chegou mais perto do topo.</p>
<blockquote>
  <p>Os chineses parecem andar mais rápido, e andam. Mas parte da impressão vem de os ocidentais estarem parados há meses, pelo menos em programação. Quando o líder não se mexe, quem corre atrás encosta.</p>

</blockquote>
<h3>Matemática não é programação<span class="hx:absolute hx:-mt-20" id="matemática-não-é-programação"></span>
    <a href="#matem%c3%a1tica-n%c3%a3o-%c3%a9-programa%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Os laboratórios ocidentais não pararam de trabalhar. Eles estão gastando boa parte da vitrine em matemática:</p>
<ul>
<li>em maio, a OpenAI anunciou que um modelo de raciocínio de uso geral <a href="https://openai.com/index/model-disproves-discrete-geometry-conjecture/"target="_blank" rel="noopener">derrubou uma conjectura de Erdős</a> sobre distâncias unitárias;</li>
<li>em agosto, ela publicou <a href="https://github.com/openai/ten-proofs"target="_blank" rel="noopener">dez resultados matemáticos do Astra</a>, cada um com prova formal em Lean;</li>
<li>também em agosto, a Anthropic mostrou um Claude interno elevando de 41,6% pra 67,2% a fração provada de <a href="https://www.anthropic.com/research/riemann-zeta"target="_blank" rel="noopener">zeros da função zeta de Riemann</a> que satisfazem a hipótese;</li>
<li>em setembro, a Anthropic <a href="https://www.anthropic.com/research/formalizing-fermats-last-theorem"target="_blank" rel="noopener">formalizou o Último Teorema de Fermat inteiro em Lean</a> em 11 dias, com um modelo interno que ela mesma descreve como comparável ao Fable 5.1.</li>
</ul>
<p>É impressionante, e é legítimo. Prova formal é um dos poucos lugares onde dá pra verificar a resposta automaticamente, o que torna matemática um ótimo campo de treino e de propaganda. Só que o mesmo Fable 5.1 que serviu de régua pro Fermat fez 95,5 no meu benchmark, contra os 100 do Fable 5.</p>
<p>Ficar melhor em matemática não deixa o modelo automaticamente melhor em programação. Um estudo de 2025, <a href="https://arxiv.org/abs/2507.00432"target="_blank" rel="noopener"><em>Does Math Reasoning Improve General LLM Capabilities?</em></a>, concluiu que <em>a maioria dos modelos que vai bem em matemática não transfere o ganho pra outros domínios</em>, com programação entre eles. Há contraexemplos: a NVIDIA mostrou que <a href="https://arxiv.org/abs/2505.16400"target="_blank" rel="noopener">aprendizado por reforço só com matemática</a> subiu a nota de código de modelos pequenos. A transferência acontece às vezes, e depende de como se treina.</p>
<h3>E o tal do mixture of experts<span class="hx:absolute hx:-mt-20" id="e-o-tal-do-mixture-of-experts"></span>
    <a href="#e-o-tal-do-mixture-of-experts" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A intuição comum é que, num modelo <em>mixture of experts</em> (MoE), dá pra melhorar o &ldquo;especialista de matemática&rdquo; sem mexer no &ldquo;especialista de código&rdquo;. A ideia está meio certa pelo motivo errado.</p>
<p>Um MoE tem vários blocos de pesos em cada camada, e um roteador escolhe poucos deles pra cada token. Mas os especialistas não se dividem por assunto do jeito que a intuição sugere. O <a href="https://arxiv.org/abs/2401.04088"target="_blank" rel="noopener">paper do Mixtral</a> procurou exatamente isso e <em>não encontrou padrão óbvio de especialista por tema</em>, com uma diferença pequena só num conjunto sintético de matemática; o roteador se organiza mais por sintaxe, por tipo de token. E nem a OpenAI nem a Anthropic confirmam publicamente se os modelos fechados delas são MoE.</p>
<p>O que fica de pé é o essencial: capacidade é treinada por objetivo. O sinal que premia prova em Lean não é o mesmo que premia perceber um vazamento entre tenants num controller Rails, e melhorar um não arrasta o outro junto. Num modelo onde só uma fração dos parâmetros trabalha por token, esse descompasso fica ainda mais fácil de acontecer.</p>
<h2>NVFP4, FP8, Q4: o que é essa sopa de letrinhas<span class="hx:absolute hx:-mt-20" id="nvfp4-fp8-q4-o-que-é-essa-sopa-de-letrinhas"></span>
    <a href="#nvfp4-fp8-q4-o-que-%c3%a9-essa-sopa-de-letrinhas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todo modelo é uma pilha de bilhões de números, os pesos. Quantização é guardar cada número com menos bits. Quanto menos bits, menos memória o modelo ocupa e menos dado a placa precisa ler por token, e é isso que define a velocidade em máquina local. O preço, quando existe, é qualidade.</p>
<p>Pra um modelo de 27 bilhões de parâmetros, a conta fica assim:</p>
<table>
  <thead>
      <tr>
          <th>Formato</th>
          <th style="text-align: right">Bits por peso</th>
          <th style="text-align: right">27B ocupa</th>
          <th>Onde roda</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>BF16</td>
          <td style="text-align: right">16</td>
          <td style="text-align: right">~54 GB</td>
          <td>nativo de Ampere (RTX 30) em diante</td>
      </tr>
      <tr>
          <td>FP8</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">~27 GB</td>
          <td>nativo em Ada (RTX 40), Hopper (H200) e Blackwell</td>
      </tr>
      <tr>
          <td>Q8_0 (GGUF)</td>
          <td style="text-align: right">~8,5</td>
          <td style="text-align: right">~29 GB</td>
          <td>llama.cpp e Ollama, qualquer placa</td>
      </tr>
      <tr>
          <td>Q5_K_M (GGUF)</td>
          <td style="text-align: right">~5,5</td>
          <td style="text-align: right">~19 GB</td>
          <td>llama.cpp e Ollama</td>
      </tr>
      <tr>
          <td>Q4_K_M (GGUF)</td>
          <td style="text-align: right">~4,5 a 4,8</td>
          <td style="text-align: right">~16 GB</td>
          <td>llama.cpp e Ollama</td>
      </tr>
      <tr>
          <td>NVFP4</td>
          <td style="text-align: right">4,5</td>
          <td style="text-align: right">~15 GB</td>
          <td>nativo só em Blackwell; vLLM, e llama.cpp desde 2026</td>
      </tr>
      <tr>
          <td>IQ3_S (GGUF)</td>
          <td style="text-align: right">~3,4</td>
          <td style="text-align: right">~12 GB</td>
          <td>llama.cpp e derivados</td>
      </tr>
  </tbody>
</table>
<h3>O que é cada um<span class="hx:absolute hx:-mt-20" id="o-que-é-cada-um"></span>
    <a href="#o-que-%c3%a9-cada-um" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li><strong>BF16</strong> é a precisão em que a maioria dos modelos é publicada. É a referência: nada se perde.</li>
<li><strong>FP8</strong> é ponto flutuante de 8 bits, com suporte em hardware nas gerações Ada, Hopper e Blackwell. Corta a memória pela metade e é o padrão dos checkpoints grandes, como o DeepSeek V4.1 Flash e o GLM 5.3 full.</li>
<li><strong>Q8_0, Q5_K_M, Q4_K_M e IQ3_S</strong> são formatos GGUF do llama.cpp. Os pesos são guardados em blocos de inteiros com uma escala por bloco; os &ldquo;K&rdquo; misturam precisões por tensor, e os &ldquo;IQ&rdquo; usam uma matriz de importância pra decidir onde gastar bits. Q8 é tido como praticamente sem perda, e Q4_K_M é o padrão recomendado de quem roda em casa.</li>
<li><strong>NVFP4</strong> é o formato de 4 bits da NVIDIA. Cada número é um ponto flutuante de 4 bits, e cada bloco de 16 números ganha a própria escala em FP8. A NVIDIA diz que isso <a href="https://developer.nvidia.com/blog/introducing-nvfp4-for-efficient-and-accurate-low-precision-inference/"target="_blank" rel="noopener">ocupa 3,5 vezes menos que FP16 e perde 1% ou menos contra FP8</a> no DeepSeek-R1.</li>
</ul>
<p>O hype do NVFP4 vem daí: os tensor cores da Blackwell calculam nele direto, e foi ele que fez caber numa B300 só o GLM 5.3 Flash, de 198 GB, e o Qwen3.5 397B, de 251 GB. A H200, da geração anterior, não tem FP4 nativo. E o ecossistema caseiro ainda está chegando: o llama.cpp ganhou <a href="https://github.com/ggml-org/llama.cpp/pull/22196"target="_blank" rel="noopener">suporte a NVFP4</a> só em 2026, e a cobertura por arquitetura ainda é parcial.</p>
<h3>O que os nossos testes mostram<span class="hx:absolute hx:-mt-20" id="o-que-os-nossos-testes-mostram"></span>
    <a href="#o-que-os-nossos-testes-mostram" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Quatro modelos rodaram em mais de uma precisão:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Versão A</th>
          <th>Versão B</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen 3.8 27B</td>
          <td>BF16, B300: 80,5</td>
          <td>Q8_0, Strix Halo: 80,0 ¹</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next</td>
          <td>NVFP4, B300: 92,5</td>
          <td>IQ3_S, RTX 5090 com Strata: 91,0</td>
      </tr>
      <tr>
          <td>Ornith 1.5 35B</td>
          <td>BF16, B300: 81,0</td>
          <td>Q8_0, RTX 5090 com Strata: 64,5</td>
      </tr>
      <tr>
          <td>GLM 4.7 Flash</td>
          <td>BF16, B300: 33,5</td>
          <td>Q5_K_M, RTX 5090: 24,0</td>
      </tr>
  </tbody>
</table>
<p>¹ Rodada mista: sprints 1 e 2 em Q5_K_M na 5090, o resto em Q8_0 na Strix, com o raciocínio desligado.</p>
<p>E seis rodaram na API de nuvem e também servidos por mim, com os pesos oficiais:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Nuvem</th>
          <th style="text-align: right">Servido por mim</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GLM 5.3 Flash</td>
          <td style="text-align: right">84,25</td>
          <td style="text-align: right">92,5 (NVFP4, B300)</td>
      </tr>
      <tr>
          <td>DeepSeek V4.1 Flash</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">92,5 (FP8, 4x B300)</td>
      </tr>
      <tr>
          <td>GLM 5.3 full</td>
          <td style="text-align: right">94,0</td>
          <td style="text-align: right">76,0 (FP8, 4x B300)</td>
      </tr>
      <tr>
          <td>MiMo V2.6 Pro</td>
          <td style="text-align: right">92,0</td>
          <td style="text-align: right">67,0 (FP8, 4x B300)</td>
      </tr>
      <tr>
          <td>DeepSeek V4 Flash 0731</td>
          <td style="text-align: right">85,0</td>
          <td style="text-align: right">44,5 (FP4/FP8 oficial, B300)</td>
      </tr>
      <tr>
          <td>Nex N2.5 Pro</td>
          <td style="text-align: right">94,0</td>
          <td style="text-align: right">27,0 (FP8, 4x B300)</td>
      </tr>
  </tbody>
</table>
<p>Cada linha é uma rodada só, e o ruído chega a quase 10 pontos. Com essa ressalva, a leitura que eu faço:</p>
<ul>
<li><strong>Q8 ficou empatado com BF16.</strong> O Qwen 27B fez 80,5 e 80,0, num ponto só e com outras coisas mudando junto.</li>
<li><strong>Descer de 4 pra 3 bits saiu quase de graça no Flash-Next</strong>, a 1,5 ponto, mesmo trocando de motor e de placa.</li>
<li><strong>A queda da Ornith dificilmente é dos bits.</strong> Q8 é quase sem perda, e mesmo assim ela perdeu 16,5 trocando também de motor e de placa.</li>
<li><strong>Servir o mesmo checkpoint pode dar qualquer coisa.</strong> O GLM 5.3 Flash subiu 8 pontos na minha mão, o DeepSeek V4.1 empatou, e o Nex caiu quase 70. Parser de ferramenta, template de chat, modo de raciocínio e versão do motor pesam tanto quanto a precisão, e o efeito muda de modelo pra modelo.</li>
</ul>
<blockquote>
  <p>Nos meus testes, os bits pesaram menos do que o hype sugere, e o conjunto todo de como o modelo é servido pesou mais. Use Q8 quando couber, desça pra 4 bits quando precisar, e meça no seu próprio trabalho antes de confiar.</p>

</blockquote>
<h2>Os melhores comerciais<span class="hx:absolute hx:-mt-20" id="os-melhores-comerciais"></span>
    <a href="#os-melhores-comerciais" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui só entra modelo fechado, só Tier A, e só o que eu pagaria por API.</p>
<h3>Por nota<span class="hx:absolute hx:-mt-20" id="por-nota"></span>
    <a href="#por-nota" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">Custo da rodada</th>
          <th style="text-align: right">Minutos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPT-6 Astra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 30,55</td>
          <td style="text-align: right">100</td>
      </tr>
      <tr>
          <td>Claude Opus 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 16,63 ¹</td>
          <td style="text-align: right">64</td>
      </tr>
      <tr>
          <td>Claude Opus 5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">~US$ 71 ¹</td>
          <td style="text-align: right">145</td>
      </tr>
      <tr>
          <td>Claude Fable 5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">~US$ 50 ¹</td>
          <td style="text-align: right">~85</td>
      </tr>
      <tr>
          <td>GPT 5.6 terra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 9,52</td>
          <td style="text-align: right">80</td>
      </tr>
      <tr>
          <td>GPT 5.6 sol</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 20,37</td>
          <td style="text-align: right">317</td>
      </tr>
      <tr>
          <td>GPT 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 34,69</td>
          <td style="text-align: right">117</td>
      </tr>
      <tr>
          <td>Grok 4.6</td>
          <td style="text-align: right">98,5</td>
          <td style="text-align: right">US$ 13,00</td>
          <td style="text-align: right">67</td>
      </tr>
      <tr>
          <td>GPT 6 luna</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">~US$ 0,84</td>
          <td style="text-align: right">122</td>
      </tr>
      <tr>
          <td>Claude Fable 5.1</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">~US$ 51 ¹</td>
          <td style="text-align: right">133</td>
      </tr>
  </tbody>
</table>
<p>¹ Custo nocional da assinatura, a preço de tabela da API.</p>
<p>No topo absoluto, o <strong>Claude Opus 5.5</strong> e o <strong>GPT 5.6 terra</strong> são as escolhas óbvias: nota máxima, rodada em pouco mais de uma hora, e preço de rodada de um dígito ou pouco mais. O Opus 5 e o GPT 5.5 tiram a mesma nota custando de duas a sete vezes mais.</p>
<h3>Por custo-benefício<span class="hx:absolute hx:-mt-20" id="por-custo-benefício"></span>
    <a href="#por-custo-benef%c3%adcio" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">Custo da rodada</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GPT 6 luna</td>
          <td style="text-align: right">95,5</td>
          <td style="text-align: right">~US$ 0,84</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash</td>
          <td style="text-align: right">86,5</td>
          <td style="text-align: right">US$ 1,17</td>
      </tr>
      <tr>
          <td>GPT 6 sol</td>
          <td style="text-align: right">91,0</td>
          <td style="text-align: right">~US$ 8,35</td>
      </tr>
      <tr>
          <td>GPT 5.6 terra</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 9,52</td>
      </tr>
      <tr>
          <td>GPT 5.6 luna</td>
          <td style="text-align: right">95,0</td>
          <td style="text-align: right">US$ 10,04</td>
      </tr>
      <tr>
          <td>Grok 4.6</td>
          <td style="text-align: right">98,5</td>
          <td style="text-align: right">US$ 13,00</td>
      </tr>
      <tr>
          <td>Claude Opus 5.5</td>
          <td style="text-align: right">100,0</td>
          <td style="text-align: right">US$ 16,63 ¹</td>
      </tr>
  </tbody>
</table>
<p>¹ Custo nocional da assinatura, a preço de tabela da API.</p>
<p>O <strong>GPT 6 luna</strong> é o absurdo da tabela: Tier A alto por menos de um dólar a rodada, uma fração do preço de qualquer outro fechado no mesmo patamar. Se a nota máxima não for obrigatória, é por onde eu começaria.</p>
<p>Na nota máxima, o <strong>GPT 5.6 terra</strong> custa pouco mais da metade do Opus 5.5. A diferença entre os dois fica no resto: harness, velocidade, comportamento em sessão longa, e isso eu cobri nos artigos anteriores.</p>
<h2>Os melhores abertos pra empresa<span class="hx:absolute hx:-mt-20" id="os-melhores-abertos-pra-empresa"></span>
    <a href="#os-melhores-abertos-pra-empresa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui entra o que roda em GPU de datacenter, na B300.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Onde</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">tok/s</th>
          <th style="text-align: right">Memória</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V4.1 Flash</td>
          <td>4x B300</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">175</td>
          <td style="text-align: right">510 GB</td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash</td>
          <td>1x B300</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">142</td>
          <td style="text-align: right">198 GB</td>
      </tr>
      <tr>
          <td>Qwen3.8 Flash-Next</td>
          <td>1x B300</td>
          <td style="text-align: right">92,5</td>
          <td style="text-align: right">195</td>
          <td style="text-align: right">100 GB</td>
      </tr>
      <tr>
          <td>Swift 1.5 27B</td>
          <td>1x B300</td>
          <td style="text-align: right">88,0</td>
          <td style="text-align: right">203</td>
          <td style="text-align: right">56 GB</td>
      </tr>
      <tr>
          <td>ThinkingCap 3.8 27B</td>
          <td>1x B300</td>
          <td style="text-align: right">83,0</td>
          <td style="text-align: right">189</td>
          <td style="text-align: right">56 GB</td>
      </tr>
  </tbody>
</table>
<p>O <strong>DeepSeek V4.1 Flash</strong> é o campeão pra time grande. Num pod de 4x B300 a US$ 36 por hora, ele atende <strong>32 sessões confortáveis</strong> ao mesmo tempo, com o primeiro token em menos de 2 segundos pra quase todas. Contando uma sessão ativa por desenvolvedor o expediente inteiro, o que é otimista, são 32 pessoas. Ligando o pod só no expediente, 8 horas por dia, 22 dias por mês, dá uns US$ 6.300 por mês, ou <strong>uns US$ 200 por desenvolvedor</strong>.</p>
<p>As alternativas, pela mesma conta:</p>
<ul>
<li>4x H200 na Runpod atende só 8 sessões confortáveis e sai por uns US$ 316 por desenvolvedor por mês, mesmo com o pod mais barato;</li>
<li>a mesma configuração na AWS custa quase o dobro, e a H200 na Azure custa quase três vezes o preço da Runpod;</li>
<li>comprar uma DGX Station GB300 de uns US$ 130 mil se paga em uns 20 meses contra o aluguel no expediente, ou em uns 5 meses contra o pod ligado direto.</li>
</ul>
<p>Pra time menor, a B300 única com o <strong>Qwen3.8 Flash-Next</strong> ou o <strong>GLM 5.3 Flash</strong> é o ponto doce: mesma nota do DeepSeek, numa GPU só, a uns US$ 9 por hora. Eu não medi concorrência nela. Pela memória que sobra, um time de cinco a dez pessoas cabe bem num modelo de 27 a 35 bilhões, como o Swift 1.5; no GLM 5.3 Flash, de 198 GB, fica apertado.</p>
<blockquote>
  <p>A infra própria começa a fazer sentido na casa de 30 pessoas usando ao mesmo tempo, ou quando o código não pode sair da rede. Abaixo disso, a API do mesmo DeepSeek custa pouco mais de um dólar por rodada e você não administra servidor nenhum.</p>

</blockquote>
<h2>Os melhores abertos pra consumidor<span class="hx:absolute hx:-mt-20" id="os-melhores-abertos-pra-consumidor"></span>
    <a href="#os-melhores-abertos-pra-consumidor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui entra o que roda num PC: RTX 5090 de 32 GB ou Strix Halo de 128 GB unificados.</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Máquina</th>
          <th style="text-align: right">Nota</th>
          <th style="text-align: right">tok/s</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Qwen3.8 Flash-Next (IQ3_S, Strata)</td>
          <td>RTX 5090</td>
          <td style="text-align: right"><strong>91,0</strong></td>
          <td style="text-align: right">159</td>
      </tr>
      <tr>
          <td>Qwen 3.8 27B (Q8_0)</td>
          <td>Strix Halo</td>
          <td style="text-align: right">80,0</td>
          <td style="text-align: right">8</td>
      </tr>
      <tr>
          <td>Ornith 1.5 35B (Q8_0, Strata)</td>
          <td>RTX 5090</td>
          <td style="text-align: right">64,5</td>
          <td style="text-align: right">~80</td>
      </tr>
      <tr>
          <td>GLM-4.7-Flash (Q5_K_M)</td>
          <td>RTX 5090</td>
          <td style="text-align: right">24,0</td>
          <td style="text-align: right">—</td>
      </tr>
  </tbody>
</table>
<p>Só tem um vencedor: o <strong>Qwen3.8 Flash-Next na RTX 5090 com o Strata</strong>. É um MoE de 125 bilhões com 6 bilhões ativos por token, e o Strata deixa os especialistas mais usados na placa e o resto na RAM. Ficou a 1,5 ponto da versão de datacenter, em velocidade interativa. Eu detalhei ele, com instalação, serviço de carga automática e configuração do OpenCode, em <a href="/2026/10/22/melhor-llm-open-source-strata-com-qwen-3-8-flash-next/">Melhor LLM Open Source: Strata com Qwen 3.8 Flash Next</a>.</p>
<p>O Strix Halo cabe modelo grande, mas a banda de memória dele é sete vezes menor que a da 5090, e um modelo denso de 27B anda a menos de 10 tokens por segundo em Q8, uns 13 em Q4. Serve pra tarefa de fundo, que pode esperar.</p>
<p>E o <strong>Swift 1.5</strong> e o <strong>ThinkingCap 3.8</strong>, os fine-tunes de 27B que foram bem na B300? Eu não rodei os dois na 5090, e nem precisei. Na B300, em BF16, precisão cheia, eles fizeram 88 e 83, abaixo dos 92,5 do Flash-Next na mesma placa e dos 91 que ele fez na 5090.</p>
<p>Numa 5090 eles caberiam em Q4 ou Q5, mas quantizado o melhor cenário é repetir a nota da B300. Rodar em casa custou 1,5 ponto pro Flash-Next e 16,5 pra Ornith-35B. O Qwen 3.8 27B base, que é a mesma arquitetura, nem completou na 5090 em Q5_K_M: travou num loop de raciocínio no sprint 3.</p>
<blockquote>
  <p>Se os dois já ficaram atrás do Flash-Next com precisão cheia, numa 5090 eles não vão ficar melhores. Não vi motivo pra testar: pra PC, o Flash-Next é claramente a escolha.</p>

</blockquote>
<h3>E placa pior que a 5090?<span class="hx:absolute hx:-mt-20" id="e-placa-pior-que-a-5090"></span>
    <a href="#e-placa-pior-que-a-5090" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>É a pergunta que sempre aparece: e quem tem uma placa de 8, 12 ou 16 GB? Pelos meus dados, o caminho do modelo pequeno que cabe inteiro na placa ainda não entrega nada que valha o trabalho pra programar.</p>
<p>Numa placa dessas, em Q4, cabe modelo denso de até uns 20 bilhões; um MoE de 30 bilhões com poucos ativos só roda com parte na RAM. E essa faixa foi mal no v4:</p>
<ul>
<li>o GLM-4.7-Flash, um MoE de 30B com 3B ativos, fez 24 na 5090 e 33,5 na B300 com precisão cheia, Tier D nas duas;</li>
<li>a Ornith-35B, outro MoE com 3B ativos, fez 81 na B300 e caiu pra 64,5 na 5090;</li>
<li>o gpt-oss-20b nem completou o protocolo;</li>
<li>o melhor modelo médio foi o Qwen 3.8 27B, com 80, mas ele já precisa de uns 16 GB só em Q4, travou na 5090 e só terminou a 8 tokens por segundo na Strix.</li>
</ul>
<p>A exceção é justamente o Flash-Next. O Strata foi feito pra rodar ele em placa de 12 GB, segurando a maioria dos especialistas na RAM: o <a href="https://github.com/Niko1221/Strata"target="_blank" rel="noopener">README do projeto</a> publica 53 tokens por segundo em IQ3_S numa RTX 5070 de 12 GB com 64 GB de RAM, e o mínimo declarado é 32 GB somando RAM e VRAM. Com os mesmos pesos e o mesmo motor, a nota em tese não deveria mudar, só a velocidade, que cai pra um terço da 5090. Eu não medi isso; é número do autor.</p>
<p>O porém é a RAM. Com a DRAM quase <a href="https://www.trendforce.com/presscenter/news/20260601-13070.html"target="_blank" rel="noopener">dobrando de preço</a> num único trimestre deste ano, 64 GB deixaram de ser o upgrade barato que eram.</p>
<blockquote>
  <p>Abaixo da 5090, eu não perderia tempo com modelo pequeno pra programar. Se for tentar alguma coisa, que seja o Flash-Next com Strata e RAM sobrando, e meça no seu trabalho antes de confiar.</p>

</blockquote>
<p>E tem o problema que eu contei no <a href="/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/">artigo anterior</a>: achar uma 5090 hoje é difícil, e quando acha, o preço fica entre duas vezes e meia e quase cinco vezes o sugerido.</p>
<h3>Quanto custa sair dos 150 tokens por segundo?<span class="hx:absolute hx:-mt-20" id="quanto-custa-sair-dos-150-tokens-por-segundo"></span>
    <a href="#quanto-custa-sair-dos-150-tokens-por-segundo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Depois de algumas horas usando o Qwen local, <a href="https://x.com/AkitaOnRails/status/2113386101293428812"target="_blank" rel="noopener">resumi o problema no X</a>. A média de 159 tokens por segundo da minha rodada parece rápida numa tabela. Numa sessão de programação que passa o dia inteiro gerando código, rodando teste, lendo log e corrigindo o que fez, a espera acumula.</p>
<p>Eu já fico impaciente. Abaixo disso, nem considero pra trabalho diário.</p>
<p>A própria 5090 tem limites além da velocidade. A NVIDIA especifica <a href="https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/"target="_blank" rel="noopener">32 GB de GDDR7 e 1.792 GB/s de banda</a> e <a href="https://nvidianews.nvidia.com/news/nvidia-blackwell-geforce-rtx-50-series-opens-new-world-of-ai-computer-graphics"target="_blank" rel="noopener">lançou a placa por US$ 1.999</a>. A minha ainda move os monitores e o Hyprland, então deixo 4 GB reservados pro desktop. O resto precisa segurar pesos densos, cache dos especialistas quentes, buffers do motor e a parte residente do cache de KV, que guarda o contexto da conversa.</p>
<p>Minha configuração do Strata oferece 256 mil tokens de contexto com KV em int8 e mantém 32 mil residentes na VRAM; o restante pode ir pra RAM. Isso evita estourar a placa, mas RAM continua muito mais lenta. Enquanto <a href="https://docs.anthropic.com/en/api/claude-on-amazon-bedrock"target="_blank" rel="noopener">modelos de fronteira do Claude já oferecem janela de 1 milhão de tokens</a>, localmente eu preciso vigiar o que entra na sessão. Carregar uma coleção inteira de skills, plugins e servidores MCP só porque existe espaço no arquivo de configuração é pedir pra desperdiçar contexto e piorar a latência.</p>
<blockquote>
  <p>Em hardware local, contexto também custa memória e tempo. Eu carrego só o que aquela sessão vai usar.</p>

</blockquote>
<p>E se eu colocar mais hardware? A comparação fica assim:</p>
<table>
  <thead>
      <tr>
          <th>Opção</th>
          <th style="text-align: right">Memória</th>
          <th style="text-align: right">Banda</th>
          <th style="text-align: right">Velocidade observada</th>
          <th style="text-align: right">Preço de referência</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>RTX 5090</td>
          <td style="text-align: right">32 GB GDDR7</td>
          <td style="text-align: right">1.792 GB/s</td>
          <td style="text-align: right"><strong>159,2 tok/s</strong>, medido por mim</td>
          <td style="text-align: right">US$ 1.999 sugerido</td>
      </tr>
      <tr>
          <td>2x RTX 5090</td>
          <td style="text-align: right">64 GB físicos, divididos entre as placas</td>
          <td style="text-align: right">2x 1.792 GB/s, sem virar um pool plano</td>
          <td style="text-align: right">sem teste equivalente meu</td>
          <td style="text-align: right">US$ 3.998 sugeridos</td>
      </tr>
      <tr>
          <td>Mac Studio M5 Ultra</td>
          <td style="text-align: right">96 ou 256 GB unificados; 512 GB prometidos pro fim de outubro</td>
          <td style="text-align: right">1.200 GB/s</td>
          <td style="text-align: right">ainda não medido no Strata</td>
          <td style="text-align: right">a partir de US$ 5.499</td>
      </tr>
      <tr>
          <td>DGX Spark</td>
          <td style="text-align: right">128 GB unificados</td>
          <td style="text-align: right">273 GB/s</td>
          <td style="text-align: right">cerca de 22 a 75 tok/s por sessão em receitas públicas</td>
          <td style="text-align: right">US$ 6.950 na loja da NVIDIA</td>
      </tr>
      <tr>
          <td>RTX PRO 6000 Blackwell</td>
          <td style="text-align: right">96 GB GDDR7 ECC</td>
          <td style="text-align: right">1.792 GB/s</td>
          <td style="text-align: right">187,4 tok/s no chat curto; 174,3 na sessão de agente</td>
          <td style="text-align: right">US$ 16 mil; R$ 123 mil no Pix no Brasil</td>
      </tr>
  </tbody>
</table>
<p>Eu escrevi no post que o Strata só subiria duas instâncias numa máquina com duas 5090. Essa parte ficou desatualizada: o Strata atual <a href="https://github.com/Niko1221/Strata/blob/main/docs/MULTI_GPU.md"target="_blank" rel="noopener">divide as camadas entre duas ou mais GPUs</a>. É <em>pipeline parallelism</em>, sem transformar duas placas num bloco simples de 64 GB e sem tensor parallelism maduro.</p>
<p>O ganho por sessão fica longe de 2x. Um <a href="https://github.com/Niko1221/Strata/issues/642"target="_blank" rel="noopener">teste comunitário com duas 5090</a> mediu cerca de 22% de ganho na média geométrica de decode e 40% no braço de código. Ajuda na capacidade e dá pra usar as placas como duas instâncias independentes pra dobrar concorrência. Pra mim sozinho, comprar outra 5090 pra cortar uma fração da espera não fecha a conta.</p>
<p>O <strong>Mac Studio M5 Ultra</strong> começa em <a href="https://www.apple.com/newsroom/2026/08/apple-introduces-new-mac-studio-with-m5-max-and-m5-ultra/"target="_blank" rel="noopener">US$ 5.499</a>, tem 1,2 TB/s de banda e vai de 96 a 256 GB; a opção de 512 GB foi prometida pro fim de outubro. Ainda não existe benchmark publicado do Strata no Ultra. No <a href="https://github.com/Niko1221/Strata/blob/main/docs/MAC.md"target="_blank" rel="noopener">M5 Max de 128 GB</a>, o mesmo Flash-Next ficou entre 97 e 119,5 tokens por segundo, dependendo da quantização e do prompt. Meu palpite original de menos de 100 no Ultra não está comprovado, e eu não compraria uma máquina dessas baseado em palpite.</p>
<p>A <strong>DGX Spark</strong> coloca 128 GB numa caixa compacta, com o GB10 de TDP 140 W e uma fonte de 240 W. A memória entrega só <a href="https://www.nvidia.com/en-us/products/workstations/dgx-spark/"target="_blank" rel="noopener">273 GB/s</a>, quase sete vezes menos que a 5090. A NVIDIA agora pede <a href="https://marketplace.nvidia.com/en-us/enterprise/personal-ai-supercomputers/dgx-spark/"target="_blank" rel="noopener">US$ 6.950</a>.</p>
<p>Receitas reproduzíveis do Flash-Next variam de <a href="https://github.com/ursuciprian/qwen3.8-flash-next-1x-dgx-spark"target="_blank" rel="noopener">22 a 75 tokens por segundo por sessão</a>, dependendo de concorrência, stack, amostragem e modo de raciocínio. Cabe muita coisa. Pra minha tolerância, anda pouco.</p>
<p>Sobra a <strong>RTX PRO 6000 Blackwell</strong>. Ela tem <a href="https://www.nvidia.com/content/dam/en-zz/Solutions/data-center/rtx-pro-6000-blackwell-workstation-edition/workstation-blackwell-rtx-pro-6000-workstation-edition-nvidia-us-3519208-web.pdf"target="_blank" rel="noopener">96 GB GDDR7 ECC, 1.792 GB/s e potência total de 600 watts</a>. A banda é exatamente a mesma da 5090. Os 96 GB resolvem o aperto de VRAM, permitem mais cache e contexto, mas não compram três vezes a velocidade.</p>
<p>Um <a href="https://github.com/Niko1221/Strata/issues/2268"target="_blank" rel="noopener">benchmark comunitário do Strata</a> mediu 187,4 tokens por segundo no chat curto e 174,3 numa sessão de agente, com bastante variação entre rodadas. É melhor que meus 159,2, só que a NVIDIA lista a placa por <a href="https://marketplace.nvidia.com/en-us/enterprise/laptops-workstations/nvidia-rtx-pro-6000-blackwell-workstation-edition/"target="_blank" rel="noopener">US$ 16 mil</a>. Em 22 de outubro, a WAZ <a href="https://www.waz.com.br/placa-de-video-nvidia-quadro-rtx-pro-6000-blackwell-workstation-edition-pny-vcnrtxpro6000b-pb-96gb-gddr7-pcie-5-0-x16-131172-html/p"target="_blank" rel="noopener">mostrava a PNY por R$ 123.299,99 no Pix ou R$ 145.058,81 no preço regular</a>. Já entramos em orçamento de workstation profissional ou servidor.</p>
<blockquote>
  <p>Hoje não existe, pra mim, uma opção doméstica com modelo bom, resposta realmente rápida e custo razoável. A 5090 funciona, mas 150 tokens por segundo já me cansam. Abaixo dela vira brinquedo; acima dela o preço explode antes da velocidade.</p>

</blockquote>
<h2>O que eu aprendi no v4<span class="hx:absolute hx:-mt-20" id="o-que-eu-aprendi-no-v4"></span>
    <a href="#o-que-eu-aprendi-no-v4" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><h3>Sobre os modelos<span class="hx:absolute hx:-mt-20" id="sobre-os-modelos"></span>
    <a href="#sobre-os-modelos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Quase todo modelo pega o defeito barulhento sem aviso: CVE em gem, chave no código, CORS aberto, SQL injection óbvia. Quase todo modelo deixa passar o silencioso até eu revelar: vazamento entre tenants, agregado errado, teste adulterado, índice removido. O que separa o Tier A do resto é quanto do silencioso ele pega sozinho.</li>
<li>Tamanho não compra vigilância. O Kimi K2 Thinking, de 1 trilhão de parâmetros, fez 46. O GLM 5.3 full, de 700 bilhões, fez 76, contra 92,5 do Flash da mesma família.</li>
<li>Modelo trapaceia ou se engana com convicção. O Nemotron adulterou um teste de regressão pra passar, o Qwen3.5 397B deixou passar uma backdoor e ainda escreveu um teste que a tratava como comportamento esperado, e o Kimi K2 fez teatro de checklist de segurança, pegando só o que dá pra achar com grep.</li>
<li>O salto entre gerações foi de confiabilidade agêntica. O GPT-4o e o GPT-4.1 nem conseguem completar o protocolo sozinhos. Os modelos atuais do topo rodam as sete sprints sem ajuda nenhuma.</li>
</ul>
<h3>Sobre servir modelo aberto<span class="hx:absolute hx:-mt-20" id="sobre-servir-modelo-aberto"></span>
    <a href="#sobre-servir-modelo-aberto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>O mesmo checkpoint muda de nota conforme quem serve. O DeepSeek V4 Flash fez 85 na nuvem, na mesma versão 0731 que eu baixei, e 44,5 servido por mim; o Nex N2.5 Pro fez 94 na nuvem e 27 no pod de 4x B300, com tokens corrompidos chegando no código commitado.</li>
<li>Nenhum modelo roda rápido do jeito que vem. O GLM 5.3 Flash saiu de 10 pra 141 tokens por segundo só de configuração, e cada família pediu parser, flag e às vezes uma branch de PR do vLLM.</li>
<li>Rodar em casa cobra preço diferente de cada modelo. Trocando quantização, motor e placa de uma vez, o Flash-Next perdeu 1,5 ponto e a Ornith-35B perdeu 16,5.</li>
<li>Quem limita a concorrência é a arquitetura do cache de KV e o tempo até o primeiro token. Contagem de parâmetros diz pouco: na H200, um modelo de 510 GB e outro bem menor empataram em 8 usuários, por motivos opostos.</li>
</ul>
<h3>Sobre medir<span class="hx:absolute hx:-mt-20" id="sobre-medir"></span>
    <a href="#sobre-medir" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Uma rodada tem ruído de quase 10 pontos. Duas notas a 2 pontos de distância empatam.</li>
<li>O harness pesa. O Gemini 3.1 Pro foi derrubado por um bug de proxy, o gpt-oss não completa no OpenCode, e três modelos insistiram em criar o app numa subpasta.</li>
<li>Modelo que não faz commit perde trabalho. Correção que fica só no disco some no primeiro <code>git checkout</code>.</li>
</ul>
<h2>A minha hipótese: o hardware caro é parte do modelo de negócio<span class="hx:absolute hx:-mt-20" id="a-minha-hipótese-o-hardware-caro-é-parte-do-modelo-de-negócio"></span>
    <a href="#a-minha-hip%c3%b3tese-o-hardware-caro-%c3%a9-parte-do-modelo-de-neg%c3%b3cio" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O veredito do v4, até outubro de 2026, é este: pra revisão de código e vigilância, open source chegou. O DeepSeek V4.1 Flash, o GLM 5.3 Flash e o Qwen3.8 Flash-Next estão no pelotão do Sonnet 5 e do GPT 6 sol, e um deles roda numa placa de PC.</p>
<p>Então por que não está todo mundo rodando em casa? Porque o hardware ficou caro e difícil de achar, e eu tenho uma hipótese sobre isso. É hipótese minha, sem prova de combinação nenhuma, montada em cima de fatos públicos.</p>
<h3>Os fatos<span class="hx:absolute hx:-mt-20" id="os-fatos"></span>
    <a href="#os-fatos" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>A memória disparou. A TrendForce mediu alta de <a href="https://www.trendforce.com/presscenter/news/20260601-13070.html"target="_blank" rel="noopener">93% a 98% no preço de contrato de DRAM</a> só no primeiro trimestre de 2026, e diz que os fabricantes <a href="https://www.trendforce.com/presscenter/news/20260930-13258.html"target="_blank" rel="noopener">continuam priorizando capacidade pra servidor</a> no quarto trimestre.</li>
<li>A HBM, a memória das GPUs de datacenter, deve ter o <a href="https://www.trendforce.com/presscenter/news/20260929-13255.html"target="_blank" rel="noopener">preço médio subindo 121% em 2027</a> e ocupar perto de <a href="https://www.trendforce.com/presscenter/news/20260602-13074.html"target="_blank" rel="noopener">30% das bolachas de DRAM</a> dos três maiores fabricantes até o fim de 2027.</li>
<li>A Micron <a href="https://www.storagenewsletter.com/2025/12/05/micron-announces-exit-from-crucial-consumer-business/"target="_blank" rel="noopener">saiu do mercado consumidor</a> e matou a Crucial, nas palavras dela, pra atender melhor os clientes estratégicos maiores.</li>
<li>PC e console subiram de preço, e a Microsoft <a href="https://www.digitalcitizen.life/xbox-series-x-and-series-s-prices-will-rise-again-as-memory-and-storage-costs-surge/"target="_blank" rel="noopener">atribuiu o aumento do Xbox</a> ao custo de memória e armazenamento.</li>
<li>A RTX 5090, quando aparece nos Estados Unidos, custa de <a href="https://gpupoet.com/gpu/market-report/gpu-market-report-october-2026"target="_blank" rel="noopener">duas vezes e meia</a> a <a href="https://www.techspot.com/news/114254-nvidia-rtx-5090-production-but-absence-company-us.html"target="_blank" rel="noopener">quase cinco vezes</a> o preço sugerido, e o boato é que os chips dela vão pras linhas profissionais. A NVIDIA <a href="https://www.guru3d.com/story/nvidia-says-geforce-rtx-5090-production-is-continuing/"target="_blank" rel="noopener">nega a descontinuação</a>.</li>
<li>Do outro lado, a Anthropic <a href="https://techcrunch.com/2026/06/01/anthropic-files-to-go-public/"target="_blank" rel="noopener">pediu IPO</a> mirando, segundo a imprensa, <a href="https://www.khaleejtimes.com/business/anthropic-says-revenue-jumped-to-nearly-17b-last-quarter-reports-2"target="_blank" rel="noopener">uns 2 trilhões de dólares</a>, e a OpenAI está <a href="https://uk.investing.com/news/stock-market-news/openai-races-to-raise-30-bln-ahead-of-anthropic-ipo-reuters-reports-4918623"target="_blank" rel="noopener">captando a mais de 1,4 trilhão</a>.</li>
<li>E o discurso sobre modelo aberto chinês endureceu. A Anthropic publicou que <a href="https://www.anthropic.com/news/detecting-and-preventing-distillation-attacks"target="_blank" rel="noopener">DeepSeek, Moonshot e MiniMax destilaram o Claude</a>, e o Dario Amodei, mesmo dizendo que <a href="https://www.anthropic.com/news/position-open-weights-models"target="_blank" rel="noopener">nunca defendeu proibir pesos abertos</a>, pede controle de exportação de chip e teste obrigatório, antes do lançamento, de todo modelo suficientemente capaz, aberto ou fechado.</li>
</ul>
<h3>O ciclo<span class="hx:absolute hx:-mt-20" id="o-ciclo"></span>
    <a href="#o-ciclo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A explicação mais simples pra memória cara é demanda: datacenter compra tudo e paga mais. Eu acredito nela. O que me incomoda é que nenhum participante grande tem incentivo pra quebrar o ciclo:</p>
<ol>
<li>O discurso diz que só o laboratório de fronteira sabe lidar com esses modelos &ldquo;perigosos&rdquo;, e que open source é uma aposta arriscada.</li>
<li>Isso puxa a demanda pra nuvem, e a nuvem compra GPU e memória em volume recorde.</li>
<li>Os fabricantes vendem com ágio pra quem paga mais e tiram prioridade do consumidor.</li>
<li>O hardware bom fica inacessível fora do datacenter.</li>
<li>Sem hardware em casa, o open source não é adotado, o que reforça o discurso do passo 1.</li>
</ol>
<blockquote>
  <p>Se todo mundo começasse a hospedar o próprio modelo, as contas de IPO de trilhões de dólares deixariam de fechar. O v4 mostrou que o modelo aberto já está pronto pra boa parte do trabalho. O que falta é o hardware chegar na mão de quem trabalha.</p>

</blockquote>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>open-source</category></item><item><title>Melhor LLM Open Source: Strata com Qwen 3.8 Flash Next</title><link>https://akitaonrails.github.io/2026/10/22/melhor-llm-open-source-strata-com-qwen-3-8-flash-next/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/22/melhor-llm-open-source-strata-com-qwen-3-8-flash-next/</guid><pubDate>Thu, 22 Oct 2026 11:45:00 -0300</pubDate><description>&lt;p&gt;O Strata apareceu em quase todo artigo da reta final do benchmark v4. No de &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/"&gt;open source nível consumidor&lt;/a&gt; ele foi o protagonista; no de &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/"&gt;quanto custa um time em B300 e H200&lt;/a&gt; o modelo que ele serve virou a recomendação pra DGX Spark; e no &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/"&gt;veredito do v4&lt;/a&gt; ele é a única resposta séria pra quem quer rodar modelo bom em casa.&lt;/p&gt;
&lt;p&gt;O resultado, resumido: o &lt;strong&gt;Qwen3.8 Flash-Next&lt;/strong&gt; servido pelo Strata numa RTX 5090 fez &lt;strong&gt;91, Tier A&lt;/strong&gt;, com zero nudges e os 14 defeitos plantados resolvidos (dois só depois da revelação), a 1,5 ponto do mesmo modelo rodando numa B300 de datacenter. E fez isso a uns 159 tokens por segundo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>O Strata apareceu em quase todo artigo da reta final do benchmark v4. No de <a href="/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/">open source nível consumidor</a> ele foi o protagonista; no de <a href="/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/">quanto custa um time em B300 e H200</a> o modelo que ele serve virou a recomendação pra DGX Spark; e no <a href="/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/">veredito do v4</a> ele é a única resposta séria pra quem quer rodar modelo bom em casa.</p>
<p>O resultado, resumido: o <strong>Qwen3.8 Flash-Next</strong> servido pelo Strata numa RTX 5090 fez <strong>91, Tier A</strong>, com zero nudges e os 14 defeitos plantados resolvidos (dois só depois da revelação), a 1,5 ponto do mesmo modelo rodando numa B300 de datacenter. E fez isso a uns 159 tokens por segundo.</p>
<p>Neste artigo eu detalho o que é o Strata, por que ele importa justo agora, o que ele não resolve, e como eu deixei ele rodando no dia a dia, com carga e descarga automática e integrado no OpenCode.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261022121453_strata-qwen-flash-next-opencode.png" alt="O OpenCode rodando o Qwen3.8 Flash-Next pelo Strata na RTX 5090, entre 129,9 e 153,1 tokens por segundo"  loading="lazy" /></p>
<h2>O que é o Strata<span class="hx:absolute hx:-mt-20" id="o-que-é-o-strata"></span>
    <a href="#o-que-%c3%a9-o-strata" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/Niko1221/Strata"target="_blank" rel="noopener">Strata</a> é um motor de inferência open source, licença MIT, feito em torno de um modelo: o Qwen3.8 Flash-Next, um MoE de 125 bilhões de parâmetros com 6 bilhões ativos por token, que normalmente pede servidor. O código é C++ e CUDA, reaproveita partes do llama.cpp/ggml, e tem builds pra NVIDIA, AMD (inclusive Strix Halo), Intel Arc e Mac. Ele sobe uma API compatível com OpenAI e com Anthropic em localhost.</p>
<p>O truque é tratar o PC inteiro como uma hierarquia de memória. O modelo tem 24.576 especialistas, 512 em cada uma das 48 camadas, e cada token usa só 10 por camada, mais um especialista compartilhado:</p>
<ul>
<li>a <strong>VRAM</strong> guarda os especialistas mais usados, como um cache;</li>
<li>a <strong>RAM</strong> guarda todos, e quando o token precisa de um que não está na placa, o processador calcula ele ali mesmo, enquanto a placa trabalha nos outros;</li>
<li>o <strong>SSD</strong> guarda uma tabela de consulta de uns 29 GB, da qual o modelo lê poucas linhas por token.</li>
</ul>
<p>Em cima disso, uma cabeça de especulação que vem com o próprio modelo chuta os próximos tokens e o modelo grande confere todos de uma vez. O README promete a mesma resposta 1,6 a 1,8 vez mais rápido.</p>
<p>Na minha 5090, o log mostra como isso fica na prática. Os 32 GB de VRAM seguram de 10 a 11,5 mil especialistas em uns 20 a 22 GiB, quase metade do total, e <strong>em geral de 90% a 97% dos especialistas que cada token pede já estão na placa</strong>. O resto a CPU calcula direto na RAM, e só uma fração pequena é copiada pra placa pelo barramento PCIe. É por isso que a CPU também trabalha pesado, como dá pra ver mais abaixo.</p>
<h2>Por que isso importa agora<span class="hx:absolute hx:-mt-20" id="por-que-isso-importa-agora"></span>
    <a href="#por-que-isso-importa-agora" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Porque memória virou artigo de luxo. Em 2026 o preço de contrato da DRAM <a href="https://www.trendforce.com/presscenter/news/20260601-13070.html"target="_blank" rel="noopener">quase dobrou num único trimestre</a>, os fabricantes <a href="https://www.trendforce.com/presscenter/news/20260930-13258.html"target="_blank" rel="noopener">continuam priorizando servidor</a>, e a Micron <a href="https://www.storagenewsletter.com/2025/12/05/micron-announces-exit-from-crucial-consumer-business/"target="_blank" rel="noopener">largou o mercado consumidor</a> pra atender os clientes grandes. A própria RTX 5090, quando aparece, custa de duas vezes e meia a quase cinco vezes o preço sugerido, como eu contei no <a href="/2026/10/22/llm-benchmark-v4-o-veredito-de-open-source-ate-out-2026/">veredito</a>.</p>
<p>Nesse cenário, a forma tradicional de rodar um modelo Tier A em casa, com o modelo inteiro dentro da VRAM, é inviável. O Flash-Next em NVFP4 ocupou uns 100 GB de placa na B300; em casa ninguém tem isso.</p>
<blockquote>
  <p>O Strata inverte a premissa: em vez de exigir VRAM pro modelo inteiro, ele usa a VRAM como cache e a RAM como estoque. É o que permite um modelo Tier A rodar com uma fração da memória de vídeo que ele pediria.</p>

</blockquote>
<h2>Não é mágica<span class="hx:absolute hx:-mt-20" id="não-é-mágica"></span>
    <a href="#n%c3%a3o-%c3%a9-m%c3%a1gica" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Strata faz caber, mas não faz milagre. O mínimo declarado é <strong>32 GB somando RAM e VRAM</strong>, mas com isso roda só a versão mais comprimida; a IQ3_S que fez os 91 pede uns 64 GB de RAM. Some <strong>uns 80 GB de disco</strong>, de preferência SSD, e uma placa de 12 GB pra cima no caminho oficial. Abaixo disso até sobe, tem relato com placa de 8 GB, mas a uns 15 tokens por segundo.</p>
<h3>Banda de memória é o que define a velocidade<span class="hx:absolute hx:-mt-20" id="banda-de-memória-é-o-que-define-a-velocidade"></span>
    <a href="#banda-de-mem%c3%b3ria-%c3%a9-o-que-define-a-velocidade" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na geração de token, o gargalo de máquina local quase nunca é o cálculo. É quantos bytes por segundo a memória consegue entregar pros núcleos, porque cada token precisa ler os pesos ativos do modelo. E aí a diferença entre os tipos de memória é brutal:</p>
<table>
  <thead>
      <tr>
          <th>Memória</th>
          <th style="text-align: right">Banda aproximada</th>
          <th style="text-align: right">Contra a 5090</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>VRAM GDDR7 da RTX 5090</td>
          <td style="text-align: right">~1,8 TB/s</td>
          <td style="text-align: right">1x</td>
      </tr>
      <tr>
          <td>Memória unificada LPDDR5X do Strix Halo</td>
          <td style="text-align: right">~256 GB/s</td>
          <td style="text-align: right">7 vezes mais lenta</td>
      </tr>
      <tr>
          <td>RAM DDR5 de desktop, dois canais</td>
          <td style="text-align: right">~60 a 100 GB/s</td>
          <td style="text-align: right">18 a 30 vezes mais lenta</td>
      </tr>
      <tr>
          <td>Barramento PCIe 5.0 x16</td>
          <td style="text-align: right">~64 GB/s por sentido</td>
          <td style="text-align: right">quase 30 vezes mais lento</td>
      </tr>
      <tr>
          <td>PCIe medido pelo Strata na minha máquina (placa em x8)</td>
          <td style="text-align: right">~29 GB/s</td>
          <td style="text-align: right">uns 60 vezes mais lento</td>
      </tr>
  </tbody>
</table>
<p>A última linha é da minha máquina: a placa está rodando com 8 linhas de PCIe em vez de 16, e o Strata mede isso na inicialização. Mais um motivo pra o cache na VRAM ser tão importante.</p>
<p>O Strix Halo tem 128 GB e cabe muito mais coisa que uma 5090, mas cada byte chega sete vezes mais devagar. Foi isso que eu medi no benchmark: o Qwen 3.8 27B denso em Q8 lê uns 29 GB por token, e na Strix o teto teórico fica perto de 9 tokens por segundo. Eu medi 7,7. A mesma conta na banda da 5090 daria uns 60, sete vezes mais, se o modelo coubesse com o contexto.</p>
<p>É por isso que o desenho do Strata funciona: o caminho quente fica na GDDR7, e o que falta no cache é calculado pela CPU na DDR5, ou atravessa o PCIe, os dois dezenas de vezes mais lentos. Com mais de 90% de acerto no cache, o pouco que fica de fora não derruba a velocidade. Numa placa pequena, o cache cobre menos, mais especialistas vão pela estrada lenta, e a velocidade cai: o próprio autor publica 53 tokens por segundo numa RTX 5070 de 12 GB, um terço da minha 5090.</p>
<h3>PC batata não roda<span class="hx:absolute hx:-mt-20" id="pc-batata-não-roda"></span>
    <a href="#pc-batata-n%c3%a3o-roda" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Então esqueça notebook com GPU integrada e 16 GB de RAM. Sem uma placa dedicada razoável, sem 32 GB somando RAM e VRAM e sem SSD, o modelo não sobe, ou sobe e anda na velocidade da RAM, que é a de uma carroça.</p>
<p>E tem o custo de entrada que não aparece na velocidade de geração: o Strata lê a primeira mensagem de cada conversa inteira, e o README fala em <a href="https://github.com/Niko1221/Strata#good-to-know"target="_blank" rel="noopener">uns 60 segundos a cada 30 mil tokens</a> como regra geral. Na minha 5090 é bem mais rápido, uns 100 mil tokens em 16 a 20 segundos, mas numa placa menor conte com o número do README. Agente de código manda prompt de sistema grande, então a primeira resposta de cada sessão demora mais que as seguintes.</p>
<h3>Pesado até na minha 5090<span class="hx:absolute hx:-mt-20" id="pesado-até-na-minha-5090"></span>
    <a href="#pesado-at%c3%a9-na-minha-5090" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Minha máquina não é fraca: Ryzen 9 7950X3D, 96 GB de RAM (o sistema enxerga uns 92), RTX 5090 de 32 GB. Mesmo assim, o Flash-Next ocupa tudo:</p>
<ul>
<li>na carga, ele joga uns 50 GB de especialistas na RAM, a uns 3 GB por segundo com o SSD frio, e enche o cache da placa com mais de 20 GiB;</li>
<li>com tudo carregado, sobram menos de 800 MB de VRAM livre;</li>
<li>com o servidor de pé, a máquina fica com 88 dos 92 GB de RAM ocupados, e o sistema começa a usar swap.</li>
</ul>
<p>Este é o btop no meio de uma sessão de trabalho com o OpenCode. O processo do Strata sozinho segura 54 GB de RAM e metade da CPU, com metade das 32 threads do 7950X3D cravadas em 100% e o resto acima de 50%. A 5090 está a 99% de uso, com 30 dos 32 GB de VRAM ocupados e puxando 351 W. Sobram 4 GB de RAM disponíveis, e já tem quase 48 GB em swap.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261022122545_strata-btop-7950x3d-5090.png" alt="O btop durante uma sessão com o Strata: RAM a 96%, swap pela metade, metade da CPU em 100% e o resto acima de 50%, e a RTX 5090 a 99% com 30 de 32 GB de VRAM"  loading="lazy" /></p>
<p>O efeito colateral aparece no resto do sistema. Com a VRAM no limite, o lançador de aplicativos do meu desktop e o terminal num monitor secundário pararam de abrir direito, e outros programas ficam lentos. A solução foi mandar o Strata devolver uns 2,5 GB de VRAM pro desktop, com a opção <code>--vram-elastic</code>, ao custo de um cache um pouco menor.</p>
<blockquote>
  <p>Com tudo isso, eu acredito firmemente que a minha máquina é o mínimo pra usar um LLM local em trabalho de programação de verdade, além de exercício pequeno. Com menos que isso, conte com velocidade bem menor, contexto mais curto e o PC inteiro sofrendo enquanto o modelo está carregado.</p>

</blockquote>
<h2>Como instalar<span class="hx:absolute hx:-mt-20" id="como-instalar"></span>
    <a href="#como-instalar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O caminho oficial no Linux e no Mac é o instalador de uma linha. Ele verifica o SHA-256, não pede root e instala na sua pasta pessoal. Como todo <code>curl | sh</code>, vale baixar e ler antes:</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">curl -fsSL https://github.com/Niko1221/Strata/releases/latest/download/install.sh -o install.sh
</span></span><span class="line"><span class="cl">less install.sh
</span></span><span class="line"><span class="cl">sh install.sh</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, o <code>strata launch</code> pergunta modelo, contexto e acesso, baixa o motor certo pra sua placa e o modelo, e começa a servir. Pra scripts, tudo vai por flag:</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">strata launch                                <span class="c1"># telas guiadas</span>
</span></span><span class="line"><span class="cl">strata launch --model &lt;variante&gt; --yes --no-tui   <span class="c1"># sem perguntas</span>
</span></span><span class="line"><span class="cl">strata launch --status                       <span class="c1"># o que está servindo</span>
</span></span><span class="line"><span class="cl">strata launch --stop                         <span class="c1"># parar</span>
</span></span><span class="line"><span class="cl">strata monitor --url http://127.0.0.1:8080   <span class="c1"># velocidade, GPU, memória</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>Eu usei a variante <strong>IQ3_S</strong>, a que fez os 91 no benchmark. No disco ela ocupa uns 84 GB em dois arquivos (o modelo de 55 GB e a tabela de consulta de 29 GB), mais uns 7 GB da cabeça de especulação.</p>
<p>A configuração que o <code>strata launch</code> gera é um JSON com os argumentos do motor. Os que importam na minha:</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-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;args&#34;</span><span class="err">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--expert-cache&#34;</span><span class="p">,</span> <span class="s2">&#34;auto&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--prefill&#34;</span><span class="p">,</span> <span class="s2">&#34;auto&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--spec&#34;</span><span class="p">,</span> <span class="s2">&#34;4&#34;</span><span class="p">,</span> <span class="s2">&#34;--spec-min-p&#34;</span><span class="p">,</span> <span class="s2">&#34;0.5&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--mtp&#34;</span><span class="p">,</span> <span class="s2">&#34;/mnt/data/strata-models/mtp/rt&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--max-context&#34;</span><span class="p">,</span> <span class="s2">&#34;262144&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--kv&#34;</span><span class="p">,</span> <span class="s2">&#34;int8&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--kv-resident&#34;</span><span class="p">,</span> <span class="s2">&#34;32768&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;--vram-elastic&#34;</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 <code>--spec 4</code> liga a especulação com uma janela de 4 tokens por passo, até 3 chutados pela cabeça de especulação; o <code>--kv int8</code> guarda o cache de atenção em 8 bits; o <code>--kv-resident</code> deixa só 32 mil posições do cache de contexto, as mais lidas, na VRAM; e o <code>--vram-elastic</code> permite devolver VRAM pro desktop com o servidor rodando.</p>
<h3>De 128 mil pra 256 mil tokens de contexto<span class="hx:absolute hx:-mt-20" id="de-128-mil-pra-256-mil-tokens-de-contexto"></span>
    <a href="#de-128-mil-pra-256-mil-tokens-de-contexto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O benchmark rodou com 128 mil tokens de contexto, que é o que a instalação configurou por padrão. Depois eu subi pra 256 mil, com <code>--max-context 262144</code>, e a perda foi pequena: nos logs do servidor, a geração ficou na média de uns 190 tokens por segundo numa sessão curta com 128 mil e de uns 175 com 256 mil, uns 10% mais lenta pelo dobro de contexto, um pouco mais com a VRAM devolvida pro desktop.</p>
<p>O que segura isso é o <code>--kv-resident</code>: só 32 mil posições do cache de atenção, as mais lidas, ficam na VRAM, o resto fica na RAM, e mesmo assim umas 97% das leituras desse cache acontecem na placa. A nota de 91 do benchmark foi medida com 128 mil; com 256 mil eu medi velocidade, e a qualidade ainda não foi re-testada.</p>
<p>Pra mim, 256 mil é o mínimo pra trabalho longo com agente. Com 128 mil, uma sessão de verdade estoura o contexto rápido, e o agente começa a compactar a memória toda hora. Isso fica irritante muito rápido, e cada compactação joga fora detalhe que o modelo vai precisar mais tarde.</p>
<blockquote>
  <p>Contexto curto é um problema escondido de modelo local. Antes de olhar tokens por segundo, veja quanto contexto você consegue servir sem a velocidade desabar.</p>

</blockquote>
<h2>Carregar e descarregar sozinho<span class="hx:absolute hx:-mt-20" id="carregar-e-descarregar-sozinho"></span>
    <a href="#carregar-e-descarregar-sozinho" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Deixar uns 50 GB de RAM e a placa inteira presos o dia todo, só pra ter o modelo pronto quando eu precisar, não faz sentido. Então montei um proxy pequeno, em Python puro, que fica sempre de pé na porta 8080 e controla o Strata:</p>
<ul>
<li>responde a lista de modelos sozinho, então o OpenCode enxerga o modelo sem acordar nada;</li>
<li>na primeira requisição de chat, sobe o Strata numa porta interna e espera ficar pronto, uns 40 segundos;</li>
<li>repassa as respostas em streaming;</li>
<li>depois de 10 minutos sem tráfego, derruba o Strata e devolve a placa e a RAM pro sistema; nunca no meio de uma requisição.</li>
</ul>
<p>O miolo é este:</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">ensure_up</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="n">_lock</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">ready</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">        <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">([</span><span class="n">STRATA</span><span class="p">,</span> <span class="s2">&#34;serve&#34;</span><span class="p">,</span> <span class="s2">&#34;--config&#34;</span><span class="p">,</span> <span class="n">CFG</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="s2">&#34;--host&#34;</span><span class="p">,</span> <span class="s2">&#34;127.0.0.1&#34;</span><span class="p">,</span> <span class="s2">&#34;--port&#34;</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="n">UP_PORT</span><span class="p">)],</span>
</span></span><span class="line"><span class="cl">                         <span class="n">start_new_session</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">90</span><span class="p">):</span>          <span class="c1"># carga a frio leva ~35-40 s</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">ready</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">                <span class="n">set_reserve</span><span class="p">()</span>        <span class="c1"># devolve VRAM pro desktop</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="kc">True</span>
</span></span><span class="line"><span class="cl">            <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">False</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">idle_watch</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">_last</span> <span class="o">&gt;</span> <span class="n">IDLE_TTL</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># nunca derrube o motor com requisição em voo (ver abaixo),</span>
</span></span><span class="line"><span class="cl">            <span class="c1"># mas com teto: requisição vazando não prende a GPU pra sempre</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="n">_active</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="ow">and</span> <span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">_last</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">ACTIVE_HARD_CAP_S</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">continue</span>
</span></span><span class="line"><span class="cl">            <span class="n">stop_up</span><span class="p">()</span>                <span class="c1"># libera GPU e ~50 GB de RAM</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 um serviço de usuário do systemd mantém o proxy de pé desde o login (versão resumida):</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-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># ~/.config/systemd/user/qwen-strata.service</span>
</span></span><span class="line"><span class="cl"><span class="k">[Unit]</span>
</span></span><span class="line"><span class="cl"><span class="na">Description</span><span class="o">=</span><span class="s">Qwen3.8 Flash-Next on-demand proxy (Strata RTX 5090)</span>
</span></span><span class="line"><span class="cl"><span class="na">After</span><span class="o">=</span><span class="s">network-online.target</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Service]</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">QWEN_IDLE_TTL=600</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">QWEN_VRAM_RESERVE_MIB=2560</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">QWEN_UP_PORT=8781</span>
</span></span><span class="line"><span class="cl"><span class="na">Environment</span><span class="o">=</span><span class="s">QWEN_PROXY_PORT=8080</span>
</span></span><span class="line"><span class="cl"><span class="na">ExecStart</span><span class="o">=</span><span class="s">%h/.local/bin/qwen-strata-ondemand</span>
</span></span><span class="line"><span class="cl"><span class="na">Restart</span><span class="o">=</span><span class="s">on-failure</span>
</span></span><span class="line"><span class="cl"><span class="na">KillMode</span><span class="o">=</span><span class="s">mixed</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[Install]</span>
</span></span><span class="line"><span class="cl"><span class="na">WantedBy</span><span class="o">=</span><span class="s">default.target</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>
<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">systemctl --user daemon-reload
</span></span><span class="line"><span class="cl">systemctl --user <span class="nb">enable</span> --now qwen-strata.service</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>O bug do &ldquo;parou do nada&rdquo;<span class="hx:absolute hx:-mt-20" id="o-bug-do-parou-do-nada"></span>
    <a href="#o-bug-do-parou-do-nada" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A primeira versão desse vigilante de idle tinha um bug maldoso, e vale contar porque ele ensina uma coisa sobre modelos que pensam sempre. O Flash-Next raciocina antes de responder, e numa fase longa de raciocínio ele pode ficar <strong>mais de 10 minutos sem emitir um único byte</strong>. Para o relógio de idle, aquilo era &ldquo;ninguém usando&rdquo;: o proxy derrubava o Strata no meio da própria requisição, engolia a truncagem do stream, e o OpenCode recebia um turno vazio, sem conteúdo e sem erro. O modelo parecia ter parado do nada.</p>
<p>Três correções, todas no proxy:</p>
<ul>
<li><strong>Contador de requisições em voo</strong> (<code>_active</code>, o <code>if</code> novo do trecho acima): enquanto houver uma requisição em andamento, o motor não cai. Com teto: depois de <code>QWEN_ACTIVE_HARD_CAP_S</code> (1 hora por padrão, seis vezes o TTL normal), até uma requisição vazando por bug não consegue prender a GPU e a RAM pra sempre.</li>
<li><strong>Relógio atualizado por byte</strong>: o contador de idle agora zera no início de cada requisição e a cada pedaço repassado no streaming. Geração em curso nunca conta como inatividade; o período sem byte nenhum, que é o raciocínio puro, fica coberto pelo <code>_active</code>.</li>
<li><strong>Falha honesta no meio do stream</strong>: se o motor morrer mesmo assim, o proxy agora emite um evento de erro no stream (<code>&quot;upstream engine died mid-stream&quot;</code>) em vez de fechar calado. O agente vê que a resposta morreu e tenta de novo, em vez de ficar olhando um turno vazio parecendo travado.</li>
</ul>
<p>O preço é a espera na primeira mensagem depois de um tempo parado. Em troca, quando eu não estou programando, a máquina volta a ser minha.</p>
<h2>Configurando o OpenCode<span class="hx:absolute hx:-mt-20" id="configurando-o-opencode"></span>
    <a href="#configurando-o-opencode" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O Strata tem um atalho, <code>strata connect opencode --write</code>, que escreve a configuração depois de perguntar e guarda backup. Na mão, é um provider compatível com OpenAI apontando pra porta local:</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-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;provider&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;strata-5090&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;npm&#34;</span><span class="p">:</span> <span class="s2">&#34;@ai-sdk/openai-compatible&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Strata (RTX 5090)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;baseURL&#34;</span><span class="p">:</span> <span class="s2">&#34;http://127.0.0.1:8080/v1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;apiKey&#34;</span><span class="p">:</span> <span class="s2">&#34;local&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;models&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;qwen3.8-flash-next-iq3_s&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen3.8 Flash-Next IQ3_S (Strata 5090)&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;reasoning&#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="nt">&#34;tool_call&#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="nt">&#34;limit&#34;</span><span class="p">:</span> <span class="p">{</span> <span class="nt">&#34;context&#34;</span><span class="p">:</span> <span class="mi">262144</span><span class="p">,</span> <span class="nt">&#34;input&#34;</span><span class="p">:</span> <span class="mi">229376</span><span class="p">,</span> <span class="nt">&#34;output&#34;</span><span class="p">:</span> <span class="mi">32768</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></span><span class="line"><span class="cl">  <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>Qualquer chave serve, porque o servidor é local. O próprio Strata avisa que o OpenCode precisa do <code>limit.input</code>, pra saber quanto do contexto sobra pra entrada; no exemplo eu pus o contexto menos a saída máxima. O Claude Code também funciona, apontando <code>ANTHROPIC_BASE_URL</code> pra <code>http://127.0.0.1:8080</code> e ajustando <code>CLAUDE_CODE_MAX_CONTEXT_TOKENS</code> pro tamanho do contexto, já que o Strata fala a API da Anthropic também.</p>
<p>Um detalhe: por padrão o Strata atende uma requisição por vez. Se o seu agente dispara subagentes em paralelo, eles entram em fila.</p>
<h2>E no dia a dia?<span class="hx:absolute hx:-mt-20" id="e-no-dia-a-dia"></span>
    <a href="#e-no-dia-a-dia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você tem a máquina pra isso, o resultado é bom. O Flash-Next passa de 150 tokens por segundo na maior parte do tempo, como dá pra ver no print do começo, e a chamada de ferramenta é sólida: no benchmark ele rodou as sete sprints no OpenCode sem nenhum empurrão, e no uso normal ele lê arquivo, roda comando e edita código sem tropeçar no formato.</p>
<p>Na prática, não sinto muita diferença pro Claude Code ou pro OpenCode com GLM ou Kimi na nuvem. E a nuvem não anda no melhor momento: na minha percepção, todos esses serviços estão um pouco mais lentos que o normal ultimamente, provavelmente sofrendo com demanda maior que a capacidade disponível. Rodando em casa, a velocidade é sempre a mesma, e o código não sai da minha máquina.</p>
<p>Mesmo com os tokens por segundo bons, eu ainda sinto o Flash-Next um pouco menos responsivo que um modelo comercial. Tem os segundos de leitura na primeira mensagem de cada sessão, a pausa quando ele precisa de especialistas que não estão na placa, e a carga a frio depois de um tempo parado. É o esperado: estamos rodando um modelo de servidor com os recursos no limite.</p>
<blockquote>
  <p>Considerando que é de graça e roda na minha máquina, ele está quase bom o suficiente. Pela primeira vez, rodar um modelo local pra programar parece um cenário usável de verdade, e eu me vejo usando cada vez mais como substituto.</p>

</blockquote>
<p>O porém continua sendo o hardware. Uma 5090, 96 GB de RAM e um processador forte são o mínimo pra esse cenário, e esse hardware está cada vez mais caro e difícil de achar. Mas, pra quem já tem, o Tier A em casa deixou de ser promessa.</p>
]]></content:encoded><category>modelos-locais</category><category>open-source</category><category>llms</category><category>hardware</category></item><item><title>LLM Benchmark v4: open source nível consumidor na RTX5090 ou Strix Halo. E o Strata?</title><link>https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-nivel-consumidor-rtx5090-strix-halo-strata/</guid><pubDate>Thu, 22 Oct 2026 11:30:00 -0300</pubDate><description>&lt;p&gt;Nos dois últimos artigos eu cobri o &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/"&gt;benchmark dos modelos grandes em 4x B300&lt;/a&gt; e a &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/"&gt;conta de time e custo por usuário em B300 e H200&lt;/a&gt;. Agora a pergunta de todo desenvolvedor que não tem um pod na Runpod: &lt;strong&gt;hardware de consumidor já serve pra trabalhar com IA de verdade?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Voltei pra duas máquinas que eu já tinha: uma workstation com RTX 5090 e um mini PC Strix Halo. E com um motor de inferência novo, o Strata, que promete fazer MoE grande caber em PC comum. A resposta curta: um modelo de 125B já faz Tier A numa RTX 5090. O resto é mais complicado.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Nos dois últimos artigos eu cobri o <a href="/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/">benchmark dos modelos grandes em 4x B300</a> e a <a href="/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/">conta de time e custo por usuário em B300 e H200</a>. Agora a pergunta de todo desenvolvedor que não tem um pod na Runpod: <strong>hardware de consumidor já serve pra trabalhar com IA de verdade?</strong></p>
<p>Voltei pra duas máquinas que eu já tinha: uma workstation com RTX 5090 e um mini PC Strix Halo. E com um motor de inferência novo, o Strata, que promete fazer MoE grande caber em PC comum. A resposta curta: um modelo de 125B já faz Tier A numa RTX 5090. O resto é mais complicado.</p>
<h2>As máquinas, lado a lado<span class="hx:absolute hx:-mt-20" id="as-máquinas-lado-a-lado"></span>
    <a href="#as-m%c3%a1quinas-lado-a-lado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de qualquer nota, entenda a diferença física do que eu estou comparando:</p>
<table>
  <thead>
      <tr>
          <th>Hardware</th>
          <th>Memória</th>
          <th>Banda</th>
          <th>Arquitetura</th>
          <th>Custo aproximado</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NVIDIA B300</td>
          <td>288 GB HBM3e</td>
          <td>~8 TB/s</td>
          <td>Blackwell Ultra, FP8 e FP4</td>
          <td>US$ 9/hora (Runpod)</td>
      </tr>
      <tr>
          <td>NVIDIA H200</td>
          <td>141 GB HBM3e</td>
          <td>~4,8 TB/s</td>
          <td>Hopper, FP8</td>
          <td>US$ 3,59/hora</td>
      </tr>
      <tr>
          <td>DGX Spark</td>
          <td>128 GB unificada</td>
          <td>273 GB/s</td>
          <td>GB10, FP4</td>
          <td>~US$ 6.950</td>
      </tr>
      <tr>
          <td>RTX 5090</td>
          <td>32 GB GDDR7</td>
          <td>~1,8 TB/s</td>
          <td>Blackwell sm_120, FP8/FP4</td>
          <td>US$ 1.999 sugerido (~5x no varejo)</td>
      </tr>
      <tr>
          <td>Strix Halo (Ryzen AI Max+ 395)</td>
          <td>128 GB unificada</td>
          <td>~256 GB/s</td>
          <td>gfx1151, ROCm</td>
          <td>~US$ 2.000</td>
      </tr>
  </tbody>
</table>
<p>O B300 tem mais de 30 vezes a banda de memória da Strix Halo. A 5090 tem 7 vezes. Isso explica por que o mesmo modelo voa numa máquina e rasteja na outra, e por que quantização deixa de ser detalhe e vira decisão de projeto.</p>
<p>E tem o detalhe da memória ser compartilhada: na Strix e na Spark, a mesma RAM serve CPU, GPU e sistema operacional. Não é VRAM dedicada, então a folga real é bem menor que o número bonito da caixa.</p>
<h2>O ranking em hardware de consumidor<span class="hx:absolute hx:-mt-20" id="o-ranking-em-hardware-de-consumidor"></span>
    <a href="#o-ranking-em-hardware-de-consumidor" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A régua é a mesma do benchmark v4 de sabotagem: 14 defeitos plantados, nota de vigilância de revisão, Tier A a partir de 83.</p>
<table>
  <thead>
      <tr>
          <th>Modelo / máquina</th>
          <th>Variante e engine</th>
          <th style="text-align: right">Nota / Tier</th>
          <th style="text-align: right">Velocidade</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Qwen3.8 Flash-Next</strong> (125B-A6B)</td>
          <td>RTX 5090, Strata, IQ3_S</td>
          <td style="text-align: right"><strong>91,0 (A)</strong></td>
          <td style="text-align: right">159,2 tok/s</td>
      </tr>
      <tr>
          <td>Ornith-1.5-35B-A3B</td>
          <td>RTX 5090, Strata, Q8_0 GGUF</td>
          <td style="text-align: right">64,5 (C)</td>
          <td style="text-align: right">~80 tok/s</td>
      </tr>
      <tr>
          <td>Qwen3.8-27B</td>
          <td>5090 → Strix, Q5_K_M → Q8_0</td>
          <td style="text-align: right">80,0 (B)</td>
          <td style="text-align: right">7,7 tok/s</td>
      </tr>
      <tr>
          <td>GLM-4.7-Flash (30B-A3B)</td>
          <td>RTX 5090, llama-swap, Q5_K_M</td>
          <td style="text-align: right">24,0 (D)</td>
          <td style="text-align: right">não medido</td>
      </tr>
  </tbody>
</table>
<p>O GLM-4.7-Flash completou o protocolo inteiro (depois de uma primeira tentativa aninhada que precisou de retry) e mesmo assim fracassou em detectar sabotagem. E tem uma lição de humildade no Qwen 27B: ele fez 80 na mesma tarefa em que fez 80,5 na B300, mas a 7,7 tokens por segundo na sonda Q8 e 706 minutos de relógio. Nota boa não paga produtividade.</p>
<p>Deixei de fora os testes que ainda não têm nota final: a Qwen3.8 Flash-Next na Strix com Strata só está confirmada como executável, e as densas de 27B foram descartadas na Strix pelo corte de 30 tok/s.</p>
<h2>O tuning que faz modelo rodar bem em PC<span class="hx:absolute hx:-mt-20" id="o-tuning-que-faz-modelo-rodar-bem-em-pc"></span>
    <a href="#o-tuning-que-faz-modelo-rodar-bem-em-pc" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Hardware de consumidor não perdoa configuração preguiçosa. O que eu aprendi na prática:</p>
<ul>
<li><strong>Na 5090, compile o llama.cpp pro sm_120.</strong> Imagem pronta cai em fallback PTX lento. É <code>-DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=120</code>, driver 570+, e nada de <code>--ctx-size 0</code> numa placa de 32 GB: o default de contexto de treino estoura a VRAM na primeira alocação. Contexto tem teto explícito, e se der OOM no primeiro request, corta 25 a 50%.</li>
<li><strong>KV cache em q8_0.</strong> Corta a memória de KV pela metade sem custo mensurável de qualidade. Flash-attention ligado por padrão, mas o GLM 4.7 Flash precisa dele desligado, porque a arquitetura deepseek2 dele é incompatível.</li>
<li><strong>Raciocínio híbrido dá trabalho.</strong> O Qwen3.8-27B, com a arquitetura SSM/Mamba, precisou de build fresco do llama.cpp/Ollama por causa do <code>ssm_conv1d</code>. E o modo &ldquo;sem raciocínio&rdquo; não impediu loop de coerência em uma das tentativas; a saída foi retomar na Strix com Q8_0 e <code>--reasoning off</code>. Não foi memória extra que resolveu, foi desligar o pensamento.</li>
<li><strong>Na Strix, o ajuste é ROCm, não mágica.</strong> <code>HSA_OVERRIDE_GFX_VERSION</code>, <code>GGML_HIP_NO_VMM</code>, <code>-dio</code>. O trunfo dela é capacidade (128 GB), não velocidade (~256 GB/s). Q4_K_M deu 12,6 tok/s e Q8_0 deu 7,7 tok/s, abaixo do meu corte de 30 tok/s pra uso interativo. Por isso a Strix virou escape hatch de modelo grande, não caixa de chat rápida.</li>
<li><strong>Especulação só ajuda com aceitação alta.</strong> Num <a href="https://mihai.ltd/blog/beating-llamacpp-from-scratch-consumer-amd"target="_blank" rel="noopener">teste do Mihai Farcas</a> numa RX 7900 XTX, a especulação por n-gram do llama.cpp, sem modelo de rascunho, teve só 5 de 64 tokens aceitos, e a vazão ficou igual numa rodada e caiu uns 2% na outra. O ganho aparece com um rascunhador feito pro modelo, como o ajudante que o Strata usa no Flash-Next.</li>
</ul>
<h2>O que é esse tal de Strata<span class="hx:absolute hx:-mt-20" id="o-que-é-esse-tal-de-strata"></span>
    <a href="#o-que-%c3%a9-esse-tal-de-strata" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/Niko1221/Strata"target="_blank" rel="noopener">Strata</a> é um motor de inferência open source, licença MIT, feito em torno de um modelo: o <strong>Qwen3.8 Flash-Next</strong>, um MoE de 125 bilhões de parâmetros que normalmente pede servidor. Ele também roda MoEs menores, como a Ornith-35B que testei aqui. O código é C++ e CUDA e reaproveita partes do llama.cpp/ggml. Tem builds pra NVIDIA (CUDA 12 e 13), AMD (inclusive Strix Halo), Intel Arc e Apple Silicon, e sobe uma API compatível com OpenAI e Anthropic em localhost, que pluga direto no Claude Code, Codex CLI ou OpenCode.</p>
<p>Cuidado com o nome. O Mihai Farcas publicou <a href="https://mihai.ltd/blog/beating-llamacpp-from-scratch-consumer-amd"target="_blank" rel="noopener">outro motor chamado Strata</a>, em Rust com kernels HIP, pra modelos densos pequenos do Qwen 3.5 numa RX 7900 XTX. São projetos diferentes, e o que rodou aqui foi o primeiro.</p>
<p>O problema que ele ataca é o MoE grande em máquina pequena. A estratégia é <strong>expert-offload</strong>, em camadas:</p>
<ul>
<li>o modelo tem 24.576 especialistas, 512 em cada uma das 48 camadas, e cada token usa só 10 por camada;</li>
<li>a GPU guarda uns 4 mil especialistas, os mais usados;</li>
<li>a RAM do sistema guarda todos;</li>
<li>uma tabela de consulta de uns 29 GB fica no SSD, e o modelo lê poucas linhas dela por token.</li>
</ul>
<p>Em cima disso, um ajudante pequeno chuta as próximas palavras e o modelo grande confere todas de uma vez. O README promete a mesma resposta 1,6 a 1,8 vez mais rápido. O mínimo é 32 GB somando RAM e VRAM, mais uns 80 GB de disco, e a variante IQ3_S que eu usei tem uns 55 GB.</p>
<p>O que me fez acreditar nele foi o resultado: o <strong>Qwen3.8 Flash-Next na 5090 com Strata fez 91,0, com zero nudges e os 14 itens resolvidos</strong>, a 1,5 ponto do 92,5 que o mesmo modelo fez na B300 com vLLM. O próprio relatório chama isso de &ldquo;dentro do ruído&rdquo;. Um projeto de comunidade, numa placa de consumidor, ficou colado no mesmo modelo servido em datacenter em NVFP4.</p>
<h3>E agora ele enxerga<span class="hx:absolute hx:-mt-20" id="e-agora-ele-enxerga"></span>
    <a href="#e-agora-ele-enxerga" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Até ontem, o meu Strata era cego: mandar imagem devolvia &ldquo;this server was started without the vision encoder&rdquo;. Habilitei a visão na mão, e foi mais simples do que parecia.</p>
<p>O projetor de visão (o <code>mmproj</code>) eu já tinha arquivado no NAS dentro da variante uncensored do Flash-Next. Conferi o sha256 dele contra o manifesto do arquivo e contra o hash que o Strata pinou pro encoder BF16, e subi uma cópia pro diretório de modelos. A configuração ganhou o flag <code>--vision</code> e uma seção <code>vision</code> apontando pro componente <code>strata-vision</code> e pro mmproj, com backup do lado.</p>
<p>A verificação foi de ponta a ponta: o encoder carrega em meio segundo, aquece 1.024 tokens de imagem em menos de dois segundos, e respondeu &ldquo;Red&rdquo; pra um quadrado vermelho de teste. E o detalhe que me importa mais: com o encoder residente, sobraram 3,65 GB de VRAM, então a reserva de 4 GB do desktop segue valendo. A única pegadinha: o carregamento preguiçoso não convive com a seção de visão nessa configuração, então ele fica desligado aqui.</p>
<h2>Cuidado: o Strata come a GPU toda, e o desktop morre<span class="hx:absolute hx:-mt-20" id="cuidado-o-strata-come-a-gpu-toda-e-o-desktop-morre"></span>
    <a href="#cuidado-o-strata-come-a-gpu-toda-e-o-desktop-morre" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Tem um custo que não aparece em benchmark: rodar o Strata na mesma GPU que desenha a sua área de trabalho. Ele foi feito pra encher a placa, e enche mesmo.</p>
<p>No meu caso, o motor alocou 32.085 dos 32.607 MiB da 5090, 98,4% da VRAM. O Hyprland ficou com uns 500 MiB pra si, as alocações do desktop falharam, e até o terminal parou de responder. Só resolveu com hard reset.</p>
<p>Foram duas falhas em sequência. A reserva que eu tinha configurado era de 2.560 MiB, mas o proxy disparava uma única vez, engolia a exceção e nunca tentava de novo. O endpoint <code>/v1/vram</code> ainda não estava no ar naquele instante, então a reserva não foi aplicada. E 2.560 MiB já era pouco pra Hyprland, navegador e terminal juntos.</p>
<p>A correção, validada em teste de carga:</p>
<ul>
<li>Reserva de <strong>4.096 MiB</strong> (4 GiB), com retry por até alguns minutos, verificação da resposta do motor e log de confirmação. Nada de fire-and-forget.</li>
<li>Reafirmação da reserva a cada 30 segundos enquanto o motor está carregado, pra fechar qualquer deriva de rebalanceamento.</li>
<li>No primeiro teste de carga, com a GPU a 99%, ficaram <strong>3.629 a 3.888 MiB livres durante toda a geração</strong>. No teste seguinte, uma geração longa e outras três requisições concorrentes terminaram com sucesso; a menor folga observada foi 3.949 MiB. O desktop continuou vivo.</li>
</ul>
<p>O preço aparece na vazão. O cache de especialistas caiu de uns 11.700 slots pra 9.700, então mais especialistas viajam por PCIe. Na medição inicial, saiu de ~200 pra ~150 tokens por segundo. Eu pago isso feliz: um desktop que não congela vale mais que aqueles 25% de velocidade naquele teste.</p>
<p>Tem uma alternativa estrutural: essa máquina tem uma Radeon 610M integrada, e dá pra mover o Hyprland pra ela. A 5090 ficaria dedicada à inferência.</p>
<p>O custo é que a 610M é fraca pra jogos e o multi-GPU da NVIDIA no Wayland tem suas manias. Eu preferi a reserva elástica, porque o modelo também descarrega quando fica ocioso.</p>
<p>A lição geral: <strong>não deixe motor de inferência tomar 100% de uma GPU que também desenha sua tela.</strong> Nesta configuração não tenho uma reserva rígida de VRAM no driver pro cliente de display. Precisei resolver do lado do motor: reservar alguns GB, reafirmar depois de cada cold start e medir de novo com a área de trabalho aberta.</p>
<h3>Os mesmos 32 GB também limitam a sessão<span class="hx:absolute hx:-mt-20" id="os-mesmos-32-gb-também-limitam-a-sessão"></span>
    <a href="#os-mesmos-32-gb-tamb%c3%a9m-limitam-a-sess%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A conta da 5090 é apertada por todos os lados. Os mesmos 32 GB precisam acomodar os pesos densos e buffers fixos do modelo, o cache dos especialistas quentes, a parte residente do KV cache e ainda a reserva de 4 GiB pro Hyprland, navegador e terminal. O que sobra pra uma coisa sai de outra.</p>
<p>KV cache é a memória da conversa. Cada token do prompt, das respostas anteriores e dos arquivos lidos cria estado que o modelo precisa consultar nos próximos tokens. No meu Strata ele está em <code>int8</code>, com 32K células por camada residentes na GPU.</p>
<p>O restante pode ser transmitido da RAM, por isso aumentar a janela de 128K pra 256K não aumentou a VRAM nem derrubou a velocidade no meu teste sintético. Esse streaming evita o estouro imediato, mas continua usando memória e não transforma uma 5090 num servidor sem limite.</p>
<p>O teto configurado aqui é <strong>262.144 tokens, ou 256K</strong>. Vários modelos comerciais que testei, inclusive os Claude mais novos, já trabalham na classe de 1 milhão de tokens. Quase quatro vezes a janela muda o jeito de trabalhar: no Claude Code dá pra acumular muito mais histórico antes da compactação; no Qwen local eu preciso tratar contexto como orçamento desde o primeiro prompt.</p>
<p>A conversa que aparece no terminal é só uma parte do contexto. Dependendo do harness, também entram o system prompt, <code>AGENTS.md</code> e <code>CLAUDE.md</code>, catálogo e conteúdo carregado de skills, descrições e schemas de ferramentas, servidores MCP, instruções de plugins, saídas de comandos, arquivos e o espaço reservado pra resposta. Uma skill bem implementada carrega o texto completo só quando é chamada, mas o catálogo dela ainda pode entrar no prompt. Plugin que registra vinte ferramentas também manda vinte schemas antes de você pedir qualquer coisa.</p>
<p>Foi por isso que o benchmark não usou meu OpenCode normal. O runner <a href="https://github.com/akitaonrails/llm-coding-benchmark#local-opencode-benchmark-config"target="_blank" rel="noopener">gera uma configuração isolada</a>, sem os plugins e MCPs globais, com apenas os providers e agentes necessários pro teste. Também fixa o limite real de 262.144 tokens em vez de fingir que o modelo aceita o contexto anunciado por outro backend.</p>
<p>Pra usar esse Qwen no dia a dia, eu faria a mesma limpeza:</p>
<ul>
<li>começar com o harness mais enxuto possível e ativar plugins ou MCPs quando o projeto realmente precisar;</li>
<li>deixar skills pesadas sob demanda, sem despejar manuais inteiros no começo da sessão;</li>
<li>compactar ou abrir uma sessão nova antes de encostar no teto, registrando decisões importantes em arquivos do projeto;</li>
<li>reservar espaço pra saída do modelo. Uma janela lotada de entrada não serve se não sobra token pra ele pensar, chamar ferramentas e responder.</li>
</ul>
<blockquote>
  <p>Com 256K, contexto é recurso de hardware. Instalar integração demais e carregar tudo em toda sessão consome janela e memória antes do trabalho começar.</p>

</blockquote>
<h2>Mas o Qwen 3.8 não era lento demais?<span class="hx:absolute hx:-mt-20" id="mas-o-qwen-38-não-era-lento-demais"></span>
    <a href="#mas-o-qwen-38-n%c3%a3o-era-lento-demais" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Era, e eu escrevi isso. No <a href="/2026/09/15/novo-llm-benchmark-v4-retestando-todos-llms-parte-2/">ranking do v4</a>, o Qwen 3.8 27B rodando local levou quase 12 horas pra fechar as sete sprints. No <a href="/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/">artigo da B300</a> eu resumi: fez 80, levou 706 minutos, &ldquo;então nem tente&rdquo;.</p>
<p>Só que aquele é outro Qwen 3.8. A família tem os dois, e eles se comportam de jeitos opostos em casa:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>Qwen 3.8 27B</th>
          <th>Qwen3.8 Flash-Next</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Arquitetura</td>
          <td>denso, 27B</td>
          <td>MoE, 125B no total, 6B ativos por token</td>
      </tr>
      <tr>
          <td>Onde terminou o v4</td>
          <td>Strix Halo, Q8_0</td>
          <td>RTX 5090, IQ3_S, Strata</td>
      </tr>
      <tr>
          <td>Velocidade</td>
          <td>7,7 tok/s</td>
          <td><strong>159,2 tok/s</strong></td>
      </tr>
      <tr>
          <td>Nota v4</td>
          <td>80,0 (B)</td>
          <td><strong>91,0 (A)</strong></td>
      </tr>
  </tbody>
</table>
<h3>Por que o maior roda mais rápido<span class="hx:absolute hx:-mt-20" id="por-que-o-maior-roda-mais-rápido"></span>
    <a href="#por-que-o-maior-roda-mais-r%c3%a1pido" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Na geração de token, máquina local é limitada por banda de memória: cada token precisa ler todos os pesos ativos. O 27B denso lê os 27 bilhões a cada token, uns 29 GB em Q8. A Strix move uns 256 GB/s, então o teto fica perto de 9 tokens por segundo, e eu medi 7,7.</p>
<p>Na 5090 o mesmo 27B seria rápido, mas em Q8 ele não cabe nos 32 GB junto com o contexto que sete sprints acumulam. Em Q5_K_M ele coube, mas travou num loop de raciocínio no sprint 3. Terminou na Strix, em Q8_0 e com o raciocínio desligado (<code>--reasoning off</code>), que foi o que destravou o loop.</p>
<p>O Flash-Next tem quase cinco vezes mais parâmetros no total, mas só uns 6 bilhões trabalham por token. Em 3 bits, isso é por volta de um décimo do que o 27B em Q8 precisa ler. Os especialistas quentes ficam na GDDR7 da 5090, com sete vezes a banda da Strix, e o ajudante de especulação adianta tokens. Resultado: uns 20 vezes a velocidade do 27B na Strix, com nota maior.</p>
<blockquote>
  <p>A virada veio da troca de modelo e de motor: um MoE com poucos parâmetros ativos, servido por um motor de expert-offload feito sob medida pra ele. Flag nenhuma de configuração tiraria isso do 27B denso na Strix.</p>

</blockquote>
<h3>O tuning ainda cobra<span class="hx:absolute hx:-mt-20" id="o-tuning-ainda-cobra"></span>
    <a href="#o-tuning-ainda-cobra" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O preço de rodar em casa muda de modelo pra modelo. O Flash-Next em 3 bits perdeu só 1,5 ponto contra a versão NVFP4 da B300. A Ornith-35B, no mesmo motor e na mesma placa, perdeu 16,5 contra a versão BF16 dela na B300. Meça o seu modelo antes de confiar nele.</p>
<h2>O que cada modelo fez de interessante<span class="hx:absolute hx:-mt-20" id="o-que-cada-modelo-fez-de-interessante"></span>
    <a href="#o-que-cada-modelo-fez-de-interessante" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>Flash-Next: a prova de que consumidor chega em Tier A.</strong> 34 de 40 sem aviso, mais 6 com peso de revelação, e só dois itens precisando de apontar. Nenhum nudge, todas as sprints limpas. É o único local que junta nota de Tier A, velocidade interativa (159 tok/s) e protocolo completo sem ajuda. Se a pergunta é &ldquo;da pra usar no trabalho&rdquo;, esse é o exemplo.</p>
<p><strong>Ornith-35B: rápido, mas não bom o suficiente.</strong> A mesma Ornith que fez 81 na B300 em BF16 fez 64,5 na 5090 com Q8_0. Caiu a detecção sem aviso de 30 pra 23. Mudou quantização, motor e hardware ao mesmo tempo, então não dá pra isolar a causa: essa combinação de Q8_0 + Strata + 5090 perdeu 16,5 pontos, e eu não sei quanto veio de cada variável. O recado é claro: &ldquo;roda rápido&rdquo; não é &ldquo;revisa bem&rdquo;.</p>
<p><strong>Qwen 27B: boa nota, péssima experiência.</strong> Fez 80 na retomada na Strix, com sprints 1 e 2 na 5090, quase o 80,5 da B300. Mas 7,7 tok/s na sonda Q8 e 706 minutos de wall time. E ainda por cima zero consertos na revelação, com quatro itens abertos. Usar isso no dia a dia é escolher esperar.</p>
<p><strong>GLM-4.7-Flash: o motor rodou, o modelo falhou.</strong> Protocolo completo em 29 minutos, e zero detecção sem aviso. Recuperou 8 de 14 só quando apontado, e mesmo assim deixou os consertos sem commit. Na B300 ele fez 33,5, que também não é bom. O problema não é só a placa.</p>
<h2>A distância entre as categorias<span class="hx:absolute hx:-mt-20" id="a-distância-entre-as-categorias"></span>
    <a href="#a-dist%c3%a2ncia-entre-as-categorias" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A pergunta que todo dev tem é se consumidor já chega lá. Comparação direta, na mesma régua:</p>
<table>
  <thead>
      <tr>
          <th>Categoria</th>
          <th>Modelos</th>
          <th style="text-align: right">Nota</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Frontier comercial</td>
          <td>Claude Opus 5 / 5.5</td>
          <td style="text-align: right">100,0</td>
      </tr>
      <tr>
          <td>Frontier comercial</td>
          <td>GPT-6 Luna</td>
          <td style="text-align: right">95,5</td>
      </tr>
      <tr>
          <td>Frontier comercial</td>
          <td>GLM 5.3 (hospedado)</td>
          <td style="text-align: right">94,0</td>
      </tr>
      <tr>
          <td>Frontier comercial</td>
          <td>GPT-6 Sol / Sonnet 5</td>
          <td style="text-align: right">91,0</td>
      </tr>
      <tr>
          <td>Frontier comercial</td>
          <td>Kimi K3</td>
          <td style="text-align: right">85,0</td>
      </tr>
      <tr>
          <td>Open datacenter</td>
          <td>DeepSeek V4.1 Flash (4x B300)</td>
          <td style="text-align: right">92,5</td>
      </tr>
      <tr>
          <td>Open datacenter</td>
          <td>GLM 5.3 Flash / Qwen3.8 Flash-Next (B300)</td>
          <td style="text-align: right">92,5</td>
      </tr>
      <tr>
          <td><strong>Open consumidor</strong></td>
          <td><strong>Qwen3.8 Flash-Next (5090 + Strata)</strong></td>
          <td style="text-align: right"><strong>91,0</strong></td>
      </tr>
      <tr>
          <td>Open consumidor</td>
          <td>Qwen3.8-27B (Strix)</td>
          <td style="text-align: right">80,0</td>
      </tr>
      <tr>
          <td>Open consumidor</td>
          <td>Ornith-35B (5090)</td>
          <td style="text-align: right">64,5</td>
      </tr>
      <tr>
          <td>Open consumidor</td>
          <td>GLM-4.7-Flash (5090)</td>
          <td style="text-align: right">24,0</td>
      </tr>
  </tbody>
</table>
<p>A leitura é que o topo do consumidor está <strong>9 pontos abaixo do topo comercial absoluto e 1,5 ponto abaixo do topo aberto em datacenter</strong>, nessa métrica de vigilância. É uma distância pequena, e ela some quando você percebe que o GPT-6 Sol e o Sonnet 5 também estão a 91. O abismo real não é entre datacenter e consumidor: é entre o modelo certo e todos os outros. Entre o Flash-Next e a Ornith, na mesma 5090, são 26,5 pontos.</p>
<h2>Então, hardware de consumidor já serve pra trabalhar?<span class="hx:absolute hx:-mt-20" id="então-hardware-de-consumidor-já-serve-pra-trabalhar"></span>
    <a href="#ent%c3%a3o-hardware-de-consumidor-j%c3%a1-serve-pra-trabalhar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>Serve, com o modelo e o motor certos.</strong> Uma RTX 5090 com Strata entrega Tier A em vigilância de código, a 159 tokens por segundo, sem pagar um centavo por token depois que o hardware existe. Pra quem faz revisão de código, testa agente de codificação e mantém dado privado em casa, isso muda o jogo.</p>
<p>Pela primeira vez neste benchmark eu tenho um modelo testado, em hardware de consumidor, que merece ir pro trabalho de todo dia. Já deixei o Flash-Next configurado no OpenCode apontando pra 5090. O próximo passo é usar em programação de verdade, fora da régua de sabotagem, e ver se a nota se sustenta.</p>
<p>O que não serve é achar que qualquer configuração serve. A Strix Halo é ótima pra ter 128 GB e montar modelo grande, mas a velocidade dela não paga interatividade; a Ornith roda rápido e revisa mal; o GLM-4.7-Flash completa protocolo e não detecta nada sem aviso. E tem o preço invisível: quantização agressiva pode cobrar nota, dependendo do modelo e da configuração, e tempo de espera cobra paciência.</p>
<p>Se eu fosse montar uma caixa pra um dev hoje, seria uma 5090 com Strata e o Flash-Next, e usaria a Strix pra tarefa de fundo que aceita ser lenta. A fronteira comercial continua ganhando de quem precisa do máximo. Mas o consumidor deixou de ser consolação: já tem Tier A na prateleira de casa.</p>
<h3>O problema agora é achar uma 5090<span class="hx:absolute hx:-mt-20" id="o-problema-agora-é-achar-uma-5090"></span>
    <a href="#o-problema-agora-%c3%a9-achar-uma-5090" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Em outubro, leakers garantiram que a NVIDIA ia parar de produzir a RTX 5090 e reservar os chips GB202 pras linhas profissionais e de datacenter. A placa chegou a sumir da loja oficial da NVIDIA nos Estados Unidos. No dia 16 de outubro, num evento em Taipei, o <strong>Jeff Fisher</strong>, vice-presidente sênior da NVIDIA, <a href="https://www.guru3d.com/story/nvidia-says-geforce-rtx-5090-production-is-continuing/"target="_blank" rel="noopener">negou a descontinuação</a> e disse que a empresa vai continuar entregando, mas admitiu que a capacidade de produção está apertada.</p>
<p>Na prática:</p>
<ul>
<li>o preço sugerido continua US$ 1.999, e a maioria dos anúncios na Newegg americana <a href="https://www.techspot.com/news/114254-nvidia-rtx-5090-production-but-absence-company-us.html"target="_blank" rel="noopener">passa de US$ 9.300, quase cinco vezes isso</a>;</li>
<li>no Brasil, cada unidade sai na casa dos <a href="https://www.baguete.com.br/noticias/carga-milionaria-de-gpus-some-de-guarulhos"target="_blank" rel="noopener">R$ 35 mil no varejo</a>;</li>
<li>e dois pallets de RTX 5090 da Zotac, importados pela distribuidora Oderço e avaliados em uns R$ 2,5 milhões, <a href="https://www.terra.com.br/noticias/misterio-em-guarulhos-r-25-milhoes-em-placas-de-video-desaparecem,a7596d7874eddfb490f96345b4be1b09z4eix4rc.html"target="_blank" rel="noopener">sumiram no aeroporto de Guarulhos</a>. A GRU Airport comunicou as autoridades, e o caso segue em investigação, sem confirmação de roubo.</li>
</ul>
<blockquote>
  <p>O primeiro modelo local Tier A do v4 roda justamente na placa mais difícil de comprar do momento. A prateleira de casa já tem Tier A. Falta conseguir a prateleira.</p>

</blockquote>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>hardware</category></item><item><title>LLM Benchmark v4: meu time cabe em 4x B300/H200?</title><link>https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/</guid><pubDate>Thu, 22 Oct 2026 11:00:00 -0300</pubDate><description>&lt;p&gt;Nos dois últimos artigos eu cobri o &lt;a href="https://akitaonrails.github.io/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/"&gt;benchmark numa B300 única&lt;/a&gt; e o &lt;a href="https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/"&gt;ranking dos modelos grandes em 4x B300&lt;/a&gt;. A pergunta que fecha a trilogia é a da empresa: &lt;strong&gt;vale a pena montar uma infra de LLM própria pro time de desenvolvimento?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A resposta depende de duas coisas: quantos usuários confortáveis o hardware aguenta, e quanto isso custa por pessoa. Medimos os dois.&lt;/p&gt;
&lt;h2&gt;A conta antes do teste&lt;span class="hx:absolute hx:-mt-20" id="a-conta-antes-do-teste"&gt;&lt;/span&gt;
&lt;a href="#a-conta-antes-do-teste" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Recapitulando os números do artigo anterior: o pod de 4x B300 custa &lt;strong&gt;US$ 36 por hora&lt;/strong&gt; (US$ 864/dia, quase US$ 26 mil/mês ligado direto). Comecei a campanha com uns US$ 127 de saldo, recarreguei US$ 2.550, e sobraram US$ 537. Foram &lt;strong&gt;uns US$ 2.140&lt;/strong&gt; entre setup, downloads, tuning e benchmarks, não só inferência.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Nos dois últimos artigos eu cobri o <a href="/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/">benchmark numa B300 única</a> e o <a href="/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/">ranking dos modelos grandes em 4x B300</a>. A pergunta que fecha a trilogia é a da empresa: <strong>vale a pena montar uma infra de LLM própria pro time de desenvolvimento?</strong></p>
<p>A resposta depende de duas coisas: quantos usuários confortáveis o hardware aguenta, e quanto isso custa por pessoa. Medimos os dois.</p>
<h2>A conta antes do teste<span class="hx:absolute hx:-mt-20" id="a-conta-antes-do-teste"></span>
    <a href="#a-conta-antes-do-teste" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Recapitulando os números do artigo anterior: o pod de 4x B300 custa <strong>US$ 36 por hora</strong> (US$ 864/dia, quase US$ 26 mil/mês ligado direto). Comecei a campanha com uns US$ 127 de saldo, recarreguei US$ 2.550, e sobraram US$ 537. Foram <strong>uns US$ 2.140</strong> entre setup, downloads, tuning e benchmarks, não só inferência.</p>
<p>No meio, o saldo zerou, o pod morreu durante um autotune, e na hora de recarregar <strong>não havia B300 disponível</strong>. É o risco operacional da Runpod: o preço muda e a oferta some. A alternativa foi um pod de 4x H200, com preço de tabela na casa de <a href="https://www.runpod.io/pricing"target="_blank" rel="noopener">US$ 3,59 por GPU-hora na Community Cloud</a>: US$ 14,36/hora pras quatro na conta de tabela, contra US$ 36 das B300.</p>
<h2>B300 e H200 não são a mesma máquina<span class="hx:absolute hx:-mt-20" id="b300-e-h200-não-são-a-mesma-máquina"></span>
    <a href="#b300-e-h200-n%c3%a3o-s%c3%a3o-a-mesma-m%c3%a1quina" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A diferença de hardware muda mais que o preço:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>4x B300</th>
          <th>4x H200</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Arquitetura</td>
          <td>Blackwell Ultra (<code>sm_103a</code>)</td>
          <td>Hopper (<code>sm_90</code>)</td>
      </tr>
      <tr>
          <td>VRAM</td>
          <td>~288 GB por GPU, ~1,1 TB total</td>
          <td>~141 GB por GPU, ~560 GB total</td>
      </tr>
      <tr>
          <td>Formatos nativos</td>
          <td>FP8 e FP4/NVFP4</td>
          <td>FP8 (sem FP4 nativo)</td>
      </tr>
      <tr>
          <td>Toolchain</td>
          <td>CUDA 12.9+ e kernels compilados na hora</td>
          <td>mais maduro, mas ainda com remendos de JIT</td>
      </tr>
  </tbody>
</table>
<p>A troca teve custo de adaptação: o checkpoint NVFP4 do GLM que rodava na B300 não existe pra Hopper, então foi preciso baixar a variante FP8 dele, e mesmo o DeepSeek precisou de ajuste de kernel. Os resultados de concorrência abaixo são com os dois em FP8 nativo, que é a comparação justa possível.</p>
<h2>Como medimos &ldquo;cabe um time&rdquo;<span class="hx:absolute hx:-mt-20" id="como-medimos-cabe-um-time"></span>
    <a href="#como-medimos-cabe-um-time" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O teste usa <code>vllm bench serve</code> contra o localhost de cada pod, com requisições de 8.000 tokens de entrada e 800 de saída (uma carga sintética no formato de sessão de codificação), subindo a concorrência N degrau por degrau.</p>
<p>O critério de conforto é o que importa: <strong>TTFT de até 2 segundos e TPOT de até 20 ms por token</strong> (ou seja, pelo menos 50 tokens/s por stream), com <strong>95% ou mais das requisições</strong> cumprindo os dois limites. Não é &ldquo;a mediana ficou boa&rdquo;: é quase todo mundo bem atendido. Um time que espera 50 segundos pelo primeiro token não é um time, é uma fila.</p>
<p>E o aviso de honestidade: isso mede requisições de código confortáveis, não pessoas. Não tenho um multiplicador medido de duty-cycle de agente interativo, então não vou inventar que cada N vira 2 ou 3 desenvolvedores.</p>
<h2>Os números<span class="hx:absolute hx:-mt-20" id="os-números"></span>
    <a href="#os-n%c3%bameros" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><table>
  <thead>
      <tr>
          <th>Modelo / hardware</th>
          <th style="text-align: right">Stream único</th>
          <th style="text-align: right">Confortável (N)</th>
          <th style="text-align: right">Porta solta (só mediana)</th>
          <th style="text-align: right">Pico agregado</th>
          <th>Gargalo</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V4.1 Flash, 4x B300 FP8</td>
          <td style="text-align: right">177 tok/s</td>
          <td style="text-align: right"><strong>32</strong></td>
          <td style="text-align: right">48</td>
          <td style="text-align: right">2.778 tok/s</td>
          <td>prefill (TTFT)</td>
      </tr>
      <tr>
          <td>DeepSeek V4.1 Flash, 4x H200 FP8</td>
          <td style="text-align: right">137 tok/s</td>
          <td style="text-align: right"><strong>8</strong></td>
          <td style="text-align: right">16</td>
          <td style="text-align: right">2.506 tok/s</td>
          <td>prefill (TTFT)</td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash, 4x H200 FP8</td>
          <td style="text-align: right">175 tok/s</td>
          <td style="text-align: right"><strong>8</strong></td>
          <td style="text-align: right">16</td>
          <td style="text-align: right">1.930 tok/s</td>
          <td><strong>KV cache, 97%</strong></td>
      </tr>
  </tbody>
</table>
<p>Leia com calma, porque tem três histórias aqui.</p>
<p><strong>A B300 atende quatro vezes mais gente no conforto.</strong> Mesmo modelo, mesmo FP8, mesmo TP4: 32 usuários confortáveis contra 8. O agregado da H200 até impressiona (2.506 tok/s), mas isso é fila de prefill: o p99 de TTFT chega a cerca de 51 segundos em N=256, então o usuário espera um minuto antes de ver qualquer coisa. Throughput alto não é capacidade interativa.</p>
<p>O penhasco é tão íngreme que vale olhar os números de perto. Em N=16, o p99 de TTFT era 1,17 s na B300 e 3,19 s na H200, com 100% e 92,7% das requisições boas. Em N=32, a B300 ainda segurava 97,4% dentro do SLO (2,23 s), enquanto a H200 desabou pra 8,3% (6,58 s). E em N=48 a B300 caiu pra 92%, abaixo do meu corte de 95%: por isso o confortável é 32 e não 48. A diferença entre &ldquo;funciona&rdquo; e &ldquo;funciona pra todo mundo&rdquo; mora nesses últimos 2 pontos percentuais.</p>
<p><strong>Modelo pesado e leve empatam na H200.</strong> O DeepSeek de 510 GB e o GLM menor sustentam os mesmos 8 confortáveis, por motivos diferentes: o DeepSeek tem KV eficiente via MLA e morre de compute/prefill; o GLM estoura o KV cache (97% de uso). Escolher hardware por contagem de parâmetros é olhar pro lugar errado: a arquitetura do KV é que manda na concorrência.</p>
<p><strong>A porta solta engana.</strong> Se você olhar só a mediana, dá pra dizer 48 na B300 e 16 na H200. Só que a cauda do p99 destrói a experiência de quem está do outro lado. Pra dimensionar time, uso o número confortável e pronto.</p>
<h2>Custo por usuário confortável<span class="hx:absolute hx:-mt-20" id="custo-por-usuário-confortável"></span>
    <a href="#custo-por-usu%c3%a1rio-confort%c3%a1vel" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora a aritmética que decide:</p>
<ul>
<li><strong>4x B300:</strong> US$ 36/h ÷ 32 = <strong>US$ 1,13 por usuário confortável por hora</strong>.</li>
<li><strong>4x H200:</strong> US$ 14,36/h ÷ 8 = <strong>US$ 1,80 por usuário confortável por hora</strong>.</li>
</ul>
<p>A B300 ganha por pessoa mesmo custando 2,5x mais o pod. A H200 só viraria mais barata por usuário abaixo de US$ 9/hora o pod inteiro, e na tabela atual ela custa US$ 14,36. O ponto de corte não é o preço da máquina, é a capacidade dela.</p>
<h2>E se a sua empresa já vive em AWS ou Azure?<span class="hx:absolute hx:-mt-20" id="e-se-a-sua-empresa-já-vive-em-aws-ou-azure"></span>
    <a href="#e-se-a-sua-empresa-j%c3%a1-vive-em-aws-ou-azure" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Dá pra montar a mesma infra nos hiperescalares, mas o preço muda de categoria. Os valores de tabela que eu conferi, por GPU-hora:</p>
<table>
  <thead>
      <tr>
          <th>Provedor</th>
          <th>Configuração</th>
          <th style="text-align: right">Preço por GPU-hora</th>
          <th style="text-align: right">Contra Runpod</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Runpod, 4x B300 (minha campanha)</td>
          <td>B300</td>
          <td style="text-align: right">US$ 9,00</td>
          <td style="text-align: right">1x</td>
      </tr>
      <tr>
          <td>Runpod, 4x H200 (Community Cloud)</td>
          <td>H200</td>
          <td style="text-align: right"><a href="https://www.runpod.io/pricing"target="_blank" rel="noopener">US$ 3,59</a></td>
          <td style="text-align: right">0,4x</td>
      </tr>
      <tr>
          <td><a href="https://themodelpress.com/gpu-prices"target="_blank" rel="noopener">AWS, p6-b300.48xlarge</a></td>
          <td>8x B300, capacity block</td>
          <td style="text-align: right">US$ 16,15</td>
          <td style="text-align: right">1,8x</td>
      </tr>
      <tr>
          <td>AWS, p6-b200.48xlarge</td>
          <td>8x B200, capacity block</td>
          <td style="text-align: right">US$ 14,21</td>
          <td style="text-align: right">1,6x</td>
      </tr>
      <tr>
          <td><a href="https://themodelpress.com/gpu-prices/azure-nd-h200-v5-on-demand"target="_blank" rel="noopener">Azure, ND96isr_H200_v5</a></td>
          <td>8x H200, East US 2</td>
          <td style="text-align: right">US$ 10,60</td>
          <td style="text-align: right">3,0x</td>
      </tr>
  </tbody>
</table>
<p>A B300 na AWS custa 1,8x o que eu paguei na Runpod, e a H200 na Azure custa quase 3x a da Runpod. A conclusão de capacidade não muda (a física do tensor parallelism e do KV cache é a mesma), mas a conta de &ldquo;vale a pena&rdquo; piora bem: se na Runpod a B300 empata com a assinatura na faixa de 30 usuários, na AWS ela passa a perder por uma margem confortável.</p>
<p>E na prática, quanto sai por desenvolvedor, desligando o pod à noite como nas outras contas (8 horas por dia útil, 176 horas/mês)? As linhas de AWS e Azure são aritmética normalizada pra 4 GPUs, porque os SKUs citados vêm com 8 e eu não medi TP=8:</p>
<table>
  <thead>
      <tr>
          <th>Setup</th>
          <th>Custo mensal do pod</th>
          <th style="text-align: right">Streams confortáveis (1 por dev)</th>
          <th style="text-align: right">Custo por dev/mês</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Runpod 4x B300 (US$ 36/h)</td>
          <td>US$ 6.336</td>
          <td style="text-align: right">32</td>
          <td style="text-align: right">US$ 198</td>
      </tr>
      <tr>
          <td>AWS 4x B300 (US$ 64,58/h, normalizado)</td>
          <td>US$ 11.367</td>
          <td style="text-align: right">32</td>
          <td style="text-align: right">US$ 355</td>
      </tr>
      <tr>
          <td>Runpod 4x H200 (US$ 14,36/h)</td>
          <td>US$ 2.527</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">US$ 316</td>
      </tr>
      <tr>
          <td>Azure 4x H200 (US$ 42,40/h, normalizado)</td>
          <td>US$ 7.462</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">US$ 933</td>
      </tr>
  </tbody>
</table>
<p>A diferença é brutal: o mesmo desenvolvedor confortável sai por US$ 198 na Runpod e por US$ 933 na Azure, quase cinco vezes mais caro que a assinatura de frontier mais forte. A AWS fica quase 80% acima da Runpod, então mesmo ela só faz sentido se a sua exigência for outra coisa que não custo.</p>
<p>E se você pegar a instância inteira de 8 GPUs, como elas são vendidas? A AWS sai por US$ 22.734/mês e a Azure por US$ 14.925/mês, e aí você tem que medir TP=8 pra saber quantos usuários aproveita, porque com TP=4 você paga o dobro pela mesma capacidade.</p>
<p>Os modelos comerciais também são diferentes: capacity block na AWS não é on-demand, e a Community Cloud da Runpod não é SLA de produção. Leia essa tabela como ordem de grandeza de preço, não como cotação de contrato. Se a sua exigência é compliance, procurement ou residência em região específica, o hiperescalar pode ser inevitável, e aí você paga o prêmio. Se a exigência é só preço, as neoclouds ganham nessas configurações.</p>
<h2>E se em vez de alugar, eu comprar a máquina?<span class="hx:absolute hx:-mt-20" id="e-se-em-vez-de-alugar-eu-comprar-a-máquina"></span>
    <a href="#e-se-em-vez-de-alugar-eu-comprar-a-m%c3%a1quina" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quando a conta de aluguel dói, a pergunta natural é comprar o hardware. Dois candidatos da prateleira atual:</p>
<p><strong>DGX Station GB300</strong> (~US$ 130 mil, com configurações começando em torno de US$ 95 mil): 748 GB de memória unificada (252 GB de HBM3e a 7,1 TB/s e 496 GB de LPDDR5X a 396 GB/s), 20 PFLOPS em FP4. Os pesos de todos os modelos que eu testei cabem nela, e os melhores pra colocar nela seriam:</p>
<ul>
<li><strong>DeepSeek V4.1 Flash</strong> (92,5/A, 510 GB em FP8), o campeão do benchmark;</li>
<li><strong>Muse Spark Open</strong> (69,5/C, 533 GB em NVFP4);</li>
<li><strong>Step-5-Preview</strong> (72,5/C, 368 GB em NVFP4);</li>
<li><strong>GLM 5.3 full</strong> (76/B, 704 GB em FP8).</li>
</ul>
<p>Cabe, mas caber não é servir: o GLM full deixaria só uns 44 GB pra KV cache e sistema, então a concorrência plena com contexto longo fica por medir. A estimativa de conforto é de um dígito, na linha da H200, e não os 32 do pod de quatro. A diferença é que você não precisa de TP=4: tudo mora num chip só.</p>
<p><img src="dgx-station-gb300.jpg" alt="Render da DGX Station GB300, o desktop com superchip Grace Blackwell Ultra e 748 GB de memória unificada"  loading="lazy" /></p>
<p><strong>DGX Spark</strong> (~US$ 6.950 na loja atual da NVIDIA): 128 GB unificados, 1 PFLOP em FP4, 273 GB/s de banda. O recheio bom pra ela é de modelo pequeno e médio, e ele é melhor do que parece:</p>
<p>E tem um detalhe que dói de ver na fatura: por estimativa minha, sem auditoria fechada de todos os custos da primeira rodada, somando os créditos do v4 (aqueles ~40 modelos comerciais) com os US$ 2.140 desta campanha de B300/H200, eu já gastei alguns mil dólares entre API e pods, perto de metade do preço de uma DGX Spark. E ainda assim eu não teria uma máquina guardada em casa por causa disso.</p>
<ul>
<li><strong>Qwen3.8 Flash-Next</strong> (92,5/A), o co-líder da B300 única. Em IQ3_S ele fica na casa de dezenas de GB, e foi exatamente o modelo que fez 91,0 na RTX 5090 com Strata. É o melhor modelo que eu recomendaria pra uma Spark hoje.</li>
<li><strong>Swift 1.5 27B</strong> (88/A, 56 GB), que foi a recomendação de self-host mais equilibrada da minha campanha anterior;</li>
<li><strong>ThinkingCap 3.8 27B</strong> (83/A, 56 GB);</li>
<li><strong>Ornith-1.5-35B-A3B</strong> (81/B, ~70 GB em BF16);</li>
<li><strong>Laguna-S-2.1</strong> (65,5/C, 99,7 GB em NVFP4);</li>
<li>e os classificadores pequenos do benchmark de decisão (Jebadiah 4B, Laya, Julia), que são o caso de uso ideal de uma caixa dessas.</li>
</ul>
<p>O DeepSeek V4.1, com 510 GB, não cabe de jeito nenhum. A banda é quase 30 vezes menor que a da B300, então a interatividade sofre, e a concorrência prática fica pra medir: eu espero pouco mais que uso solo. Pra modelo pequeno é uma caixinha simpática; pra monstro, é peso de papel.</p>
<p><img src="dgx-spark-box.jpeg" alt="DGX Spark, o desktop de 128 GB de memória unificada e 1 PFLOP em FP4"  loading="lazy" /></p>
<h3>TCO, ROI e depreciação<span class="hx:absolute hx:-mt-20" id="tco-roi-e-depreciação"></span>
    <a href="#tco-roi-e-deprecia%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A conta de ter versus alugar, usando 3 anos de depreciação e o mesmo padrão de uso (8 horas por dia útil, 176 horas/mês):</p>
<table>
  <thead>
      <tr>
          <th>Cenário</th>
          <th>Custo de alugar/mês</th>
          <th style="text-align: right">Custo de comprar (depreciação/mês)</th>
          <th style="text-align: right">Break-even</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>GB300 vs Runpod 4x B300 (US$ 36/h)</td>
          <td>US$ 6.336</td>
          <td style="text-align: right">US$ 3.611 (130 mil / 36)</td>
          <td style="text-align: right"><strong>~20 meses</strong></td>
      </tr>
      <tr>
          <td>GB300 vs Runpod 24/7</td>
          <td>US$ 25.920</td>
          <td style="text-align: right">US$ 3.611</td>
          <td style="text-align: right">~5 meses</td>
      </tr>
      <tr>
          <td>Spark vs 1x B300 (US$ 9,09/h)</td>
          <td>US$ 1.600</td>
          <td style="text-align: right">US$ 193 (6.950 / 36)</td>
          <td style="text-align: right"><strong>~4 meses e meio</strong></td>
      </tr>
      <tr>
          <td>Spark vs assinatura de US$ 200</td>
          <td>US$ 200</td>
          <td style="text-align: right">US$ 193</td>
          <td style="text-align: right">~3 anos</td>
      </tr>
  </tbody>
</table>
<p>Leitura: se você roda modelo grande todo dia útil por dois anos ou mais, o GB300 se paga em menos de dois anos, e em 24/7 ele se paga em cinco meses. Se a sua necessidade é uma campanha de duas semanas como a minha, alugar custou US$ 2.140 e pronto, sem ativo parado depois. E o Spark só faz sentido se a sua carga for modelo pequeno: nesse caso ele se paga em menos de um semestre contra aluguel e em três anos contra assinatura, mas se a sua necessidade é um 748B, ele é inútil desde a caixa.</p>
<p>Duas ressalvas pra não comprar errado. Primeiro, a tabela compara cenários de dólar, não ROI de carga equivalente: se a sua concorrência real no GB300 for de um dígito, a comparação honesta seria com um pod menor que o de 4x B300. Segundo, energia e administração mudam o resultado, principalmente no Spark, onde a diferença pra assinatura fica em poucos dólares por mês. Ignorei revenda no fim dos três anos; se ela existir, o break-even melhora.</p>
<p><strong>Nota pra quem compra no Brasil:</strong> esses preços são em dólar. Em reais, com o dólar a R$ 4,99, mais impostos de importação e a margem de revenda local, o GB300 passa fácil de R$ 800 mil e o Spark passa de R$ 34 mil antes de qualquer margem. O break-even em reais fica ainda mais distante, então a decisão de comprar no Brasil precisa de uma utilização muito mais alta pra fechar.</p>
<p>E comparando com assinatura de frontier? Um plano forte de Claude/Codex custa uns US$ 200 por mês por desenvolvedor. Com duas ressalvas importantes: esses planos não são ilimitados (o Codex mede por crédito e o Claude tem janelas de uso por sessão e por semana), e automação/CI não cabe em plano de consumidor. A comparação abaixo é de preço, não de carga equivalente. Pra transformar stream em gente eu preciso de uma premissa: um desenvolvedor com uma requisição ativa o tempo inteiro durante o expediente, o que é otimista pra capacidade e conservador pra custo. Com essa premissa, e sem contar minha hora de sysadmin nem o setup, o pod rodando 8 horas por dia útil (176 horas/mês) fica assim:</p>
<table>
  <thead>
      <tr>
          <th>Setup</th>
          <th style="text-align: right">Custo mensal do pod</th>
          <th style="text-align: right">Streams confortáveis (1 por dev)</th>
          <th style="text-align: right">Custo por dev/mês</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>4x B300</td>
          <td style="text-align: right">US$ 6.336</td>
          <td style="text-align: right">32</td>
          <td style="text-align: right"><strong>US$ 198</strong></td>
      </tr>
      <tr>
          <td>4x H200</td>
          <td style="text-align: right">US$ 2.527</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right"><strong>US$ 316</strong></td>
      </tr>
      <tr>
          <td>4x B300 ligado 24/7</td>
          <td style="text-align: right">US$ 25.920</td>
          <td style="text-align: right">32</td>
          <td style="text-align: right">US$ 810</td>
      </tr>
  </tbody>
</table>
<p>Na faixa de 30 streams contínuos, a B300 fica praticamente no preço da assinatura forte. Abaixo disso, a assinatura sai mais barata, porque você paga por pessoa, não por hora de pod ociosa. E ligar 24/7 sem carga correspondente é jogar dinheiro fora: o pod cobra dormindo.</p>
<h2>Pra empresa, quando fecha a conta<span class="hx:absolute hx:-mt-20" id="pra-empresa-quando-fecha-a-conta"></span>
    <a href="#pra-empresa-quando-fecha-a-conta" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você tem um time grande de verdade usando o dia inteiro, dados que não podem sair do ambiente, e um modelo que entrega no trabalho diário (o DeepSeek V4.1 Flash fez 92,5 no meu benchmark de vigilância, acima do GPT-6 Sol e do Sonnet 5), a infra própria começa a fazer sentido <strong>pela primeira vez sem ser vaidade</strong>. O ponto de corte, na premissa de um stream ativo por pessoa o expediente inteiro, fica na casa das 30 pessoas concorrentes, com o pod desligando fora do horário.</p>
<p>Se o time é pequeno, o uso é intermitente, ou o objetivo é só economizar: continua com a API. O mesmo DeepSeek custou US$ 1,21 por rodada completa de benchmark na nuvem, e você não herda um pod pra administrar, nem o risco de a B300 sumir do estoque quando você mais precisa.</p>
<p>O que eu levo dessa campanha: hardware grande não é resposta pra pergunta pequena. Antes de assinar o pod, faça a conta de quantas pessoas realmente concorrem no seu time, meça a latência que elas toleram, e some o preço da sua hora de sysadmin. O modelo aberto grande já chegou na faixa de Sonnet 5 e GPT-6 Sol pra revisão diária de código. A infra só precisa caber no orçamento.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>hardware</category></item><item><title>LLM Benchmark v4: open source que só cabe em 4x B300!!</title><link>https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/22/llm-benchmark-v4-open-source-que-so-cabe-em-4x-b300/</guid><pubDate>Thu, 22 Oct 2026 10:00:00 -0300</pubDate><description>&lt;p&gt;No &lt;a href="https://akitaonrails.github.io/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/"&gt;artigo anterior&lt;/a&gt; eu contei como foi rodar o benchmark v4 de sabotagem numa única NVIDIA B300 alugada na Runpod. A pergunta que sobrou foi óbvia: e os modelos grandes de verdade, aqueles que não cabem em 288 GB? Decidi ver até onde dá pra empurrar os open source.&lt;/p&gt;
&lt;p&gt;A resposta curta: dá pra ir longe, e um deles empatou com a faixa de Sonnet 5 e GPT-6 Sol. A longa vem com uma conta de padaria e vários dias de vLLM sofrendo.&lt;/p&gt;</description><content:encoded><![CDATA[<p>No <a href="/2026/10/19/llm-benchmark-v4-os-top-llms-open-source-numa-b300/">artigo anterior</a> eu contei como foi rodar o benchmark v4 de sabotagem numa única NVIDIA B300 alugada na Runpod. A pergunta que sobrou foi óbvia: e os modelos grandes de verdade, aqueles que não cabem em 288 GB? Decidi ver até onde dá pra empurrar os open source.</p>
<p>A resposta curta: dá pra ir longe, e um deles empatou com a faixa de Sonnet 5 e GPT-6 Sol. A longa vem com uma conta de padaria e vários dias de vLLM sofrendo.</p>
<p>Antes de tudo, a ressalva de escopo. Ainda ficou gente de fora.</p>
<p>O Kimi K3, de 2,8 trilhões de parâmetros, é o exemplo: em FP8 ele precisaria de uns 2,8 TB de VRAM, e nem um pod de 8x B300 (2,3 TB brutos) fecha a conta. Pra rodar um monstro desses você precisa quantizar pra FP4 ou alugar muito mais placa. E eu escolhi gastar meu crédito no que cabe em 4x.</p>
<h2>O ranking completo<span class="hx:absolute hx:-mt-20" id="o-ranking-completo"></span>
    <a href="#o-ranking-completo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Metodologia é a mesma de sempre: o app de chat em RubyLLM com 14 defeitos plantados (backdoors, bypass de autorização, mentiras nos testes), e a nota mede vigilância de revisão de código, não velocidade de digitação. O modelo precisa encontrar e corrigir os defeitos sem ser avisado (peso cheio) e quando revelado (peso 40%). Tier A começa em 83. Rodei tudo no mesmo pod de 4x pra ter uma régua única, embora nem todo modelo precisasse das quatro placas (Ornith e Laguna caberiam numa só).</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th>Tamanho / checkpoint</th>
          <th style="text-align: right">Nota / Tier</th>
          <th style="text-align: right">Velocidade (stream único)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DeepSeek V4.1 Flash</td>
          <td>748B (552B + 196B Engram), FP8, 510 GB</td>
          <td style="text-align: right"><strong>92,5 (A)</strong></td>
          <td style="text-align: right">174,8 tok/s</td>
      </tr>
      <tr>
          <td>GLM 5.3 full</td>
          <td>~700B, FP8, 704 GB</td>
          <td style="text-align: right">76,0 (B)</td>
          <td style="text-align: right">110 tok/s</td>
      </tr>
      <tr>
          <td>Ornith-1.5-122B</td>
          <td>~122B MoE, FP8, 133 GB</td>
          <td style="text-align: right">73,0 (B)</td>
          <td style="text-align: right">244,2 tok/s</td>
      </tr>
      <tr>
          <td>Step-5-Preview</td>
          <td>~816B (tamanho estimado), NVFP4, 368 GiB</td>
          <td style="text-align: right">72,5 (C)</td>
          <td style="text-align: right">165,9 tok/s (com MTP)</td>
      </tr>
      <tr>
          <td>Muse Spark Open</td>
          <td>816B-A42B, NVFP4, 533 GB</td>
          <td style="text-align: right">69,5 (C)</td>
          <td style="text-align: right">130,8 tok/s</td>
      </tr>
      <tr>
          <td>MiMo V2.6 Pro</td>
          <td>~0,5T MoE, FP8/mxfp4, ~535 GB</td>
          <td style="text-align: right">67,0 (C)</td>
          <td style="text-align: right">167,2 tok/s</td>
      </tr>
      <tr>
          <td>Laguna-S-2.1</td>
          <td>MoE pequeno pra código, NVFP4, 99,7 GB</td>
          <td style="text-align: right">65,5 (C)</td>
          <td style="text-align: right">263 tok/s</td>
      </tr>
      <tr>
          <td>Kimi K2 Thinking</td>
          <td>1T / ~32B ativos, INT4, 594 GB</td>
          <td style="text-align: right">46,0 (D)</td>
          <td style="text-align: right">110 tok/s</td>
      </tr>
      <tr>
          <td>Nex N2.5 Pro</td>
          <td>~397B MoE, FP8, ~380 GB</td>
          <td style="text-align: right">27,0 (D)</td>
          <td style="text-align: right">184 tok/s</td>
      </tr>
  </tbody>
</table>
<p>Duas notas pra ler a tabela direito. O Step-5 e o Muse estão em C porque a base sem aviso era baixa: conserto depois do &ldquo;tem problema aí&rdquo; vale 40%. O Muse consertou 6 dos 7 itens abertos quando revelado; o Step-5, só 2 de 5.</p>
<p>E o Nex N2.5 Pro, além da nota vergonhosa, corrompeu o código a ponto do app nem subir. As duas âncoras da campanha anterior, pra comparar: GLM 5.3 Flash e Qwen3.8 Flash-Next, ambos 92,5 na B300 única.</p>
<h2>O que cada um fez de interessante<span class="hx:absolute hx:-mt-20" id="o-que-cada-um-fez-de-interessante"></span>
    <a href="#o-que-cada-um-fez-de-interessante" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A nota sozinha esconde o comportamento, e o comportamento é a parte divertida.</p>
<p><strong>O DeepSeek foi o único que resolveu os dois itens compostos sem ajuda.</strong> Os defeitos compostos são os que exigem enxergar duas coisas ao mesmo tempo (a backdoor e o teste que a encobre), e ele achou e corrigiu os dois sem nenhum nudge. É o tipo de vigilância que eu quero do meu revisor de PR.</p>
<p><strong>O GLM full consertou tudo quando avisado, mas dormiu no silent mode.</strong> Sem aviso, ele deixou passar o cluster de acesso e correção silenciosa. Quando eu apontei, varreu os seis itens restantes. Ou seja: o modelo grande é capaz, só não é atento por padrão. O Flash dele, com um terço do tamanho, foi mais vigilante.</p>
<p><strong>O Kimi K2 Thinking fez teatro de checklist de segurança.</strong> Escreveu umas listas lindas de &ldquo;boas práticas de segurança&rdquo; e deixou cinco itens abertos, incluindo uma chave em texto plano. Falar de segurança e fazer segurança são coisas diferentes, e ele fez a primeira.</p>
<p><strong>O Ornith restaurou o comportamento e manteve o teste mentiroso.</strong> No item 6, ele reverteu o código pro comportamento antigo em vez de corrigir o teste plantado que validava a backdoor. Por isso a nota estrita foi 73 e a leniente 76: parecer que consertou não é consertar, e o teste que mente também precisa morrer.</p>
<p><strong>O Step-5 deixou três defeitos mesmo com o mapa na mão.</strong> Depois de revelar explicitamente, ele ainda deixou o link de login, a desativação de autorização e o agregado incorreto. Nem todo modelo aproveita a segunda chance.</p>
<p><strong>E tem o caso do 397B que virou piada pronta.</strong> Um modelo de 397B, grande demais pra caber na cabeça de quem defende tamanho, deixou passar a backdoor <code>admin? =&gt; admin || email.end_with?(&quot;@internal.example&quot;)</code> mesmo depois do aviso de auditoria de segurança. Pior: ele adicionou um teste passando que codificava a backdoor como comportamento intencional, e o commit ainda se gabava de &ldquo;Brakeman: 0 warnings&rdquo;. Se você quer um exemplo de por que eu testo vigilância e não benchmark de marketing, tá aí.</p>
<p><strong>O Muse Spark Open quase não saiu do chão, mas se recuperou.</strong> Ele começou bloqueado: pesos em 127 shards de NVFP4, gated no Hugging Face, e sem suporte no vLLM estável. Só rodou quando eu compilei uma branch de PR ainda em rascunho, com nvcc 12.9 e bibliotecas 12.8 misturadas, e limitei o multimodal a texto.</p>
<p>Aí ele entregou 69,5: base fraca sem aviso (21 de 40), mas 6 de 7 itens consertados quando apontado. É o caso de &ldquo;o esforço de infra não se confunde com a nota do modelo&rdquo;: o serviço ficou de pé, o comportamento é útil, e mesmo assim ele só vai até Tier C. Se a pergunta é &ldquo;por que ele não tá no topo&rdquo;, a resposta é vigilância por padrão, não VRAM.</p>
<p><strong>E o Nex foi o desastre completo.</strong> Precisou de dois nudges, e no fim o texto corrompido chegou ao Ruby commitado e o app nem inicializa. Na nuvem, o mesmo Nex N2.5 Pro tinha feito 94. Em casa, 27. Eu não acreditaria em benchmark de fornecedor depois de ver isso duas vezes.</p>
<p>Aliás, o caso da Nex é o exemplo perfeito de escolha ruim de base. Quando a Rio 3.5 explodiu em junho, <a href="/2026/06/15/controversia-llm-rio-3-5-plagio/">eu cobri a controvérsia aqui</a>: o checkpoint dela era uma mescla de Nex-N2-Pro com Qwen. A turma da Rio podia ter escolhido bases muito melhores, como o DeepSeek V4.1 ou o GLM 5.3 Flash, e foi justamente pra linhagem que, na minha régua atual, foi a pior do lote. Escolher modelo por nome bonito custa caro.</p>
<h2>Rodar em 4 GPUs não é trivial<span class="hx:absolute hx:-mt-20" id="rodar-em-4-gpus-não-é-trivial"></span>
    <a href="#rodar-em-4-gpus-n%c3%a3o-%c3%a9-trivial" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Quatro placas não são quatro modelos: é um modelo só fatiado em quatro, com tensor parallelism TP=4 no vLLM. Isso dá uns 1,1 TB de VRAM agregado (na prática, ~1.038 GB úteis com <code>--gpu-memory-utilization 0.90</code>). Sem isso, nenhum dos modelos acima de 400 GB existiria na minha vida sem offload pra SSD, que é onde a velocidade vai morrer.</p>
<p>Os cuidados que se repetiram em quase todos:</p>
<ul>
<li><strong>Toolchain sangrando na veia.</strong> B300 (<code>sm_103a</code>) exige CUDA 12.9 pra compilar os kernels na hora, então a imagem padrão não bastava: toolkit standalone com nvcc 12.9 e headers certos, e ainda assim cada família precisava de um remendo de DeepGEMM, FlashInfer ou link de cudart. Quando a compilação acerta, o prêmio é grande: o GLM 5.3 Flash saiu de 10,35 pra 141,40 tokens por segundo só de configuração (tirando o <code>--enforce-eager</code> e ligando o modo compilado com CUDA graphs), sem trocar checkpoint, contexto ou precisão de KV.</li>
<li><strong>Startup não é download.</strong> O DeepSeek demorava 15 a 30 minutos pra inicializar, então prepare-se pra esperar o modelo carregar antes de medir qualquer coisa.</li>
<li><strong>Cada família tem seu parser e suas manias.</strong> O Nex só conversou depois de trocar o parser pra <code>qwen3_xml</code>, ganhar mais um nudge e ignorar os artefatos de <code>tmp/log</code>. O Step-5 não existia no vLLM 0.32 estável e precisou de nightly com PR específico. O Muse exigiu compilar uma branch de PR ainda em rascunho, com CUDA 12.9 nvcc e bibliotecas 12.8 misturadas, e os pesos em 127 shards de NVFP4. O MiMo pediu <code>--trust-remote-code</code>. A Laguna decidiu, do nada, inundar a saída com texto de raciocínio, e uma sprint teve que ser refeita a partir da base de injeção.</li>
<li><strong>Contexto custa caro.</strong> O checkpoint FP8 do Kimi K2 Instruct (1.029 GB) deixaria 9 GB pra KV cache nas 4 placas, então foi o INT4 Thinking de 594 GB no lugar. Especular com draft head (MTP) só funcionou no Step-5, com ganho de 2,24x e 73,7% de aceitação de tokens, porque eu verifiquei a correção das ferramentas antes.</li>
</ul>
<p>A lição operacional: alugar VRAM resolve o &ldquo;cabe&rdquo;, mas não o &ldquo;roda bem&rdquo;. Cada modelo grande é um mini-projeto de integração.</p>
<h2>E a fronteira, fica onde?<span class="hx:absolute hx:-mt-20" id="e-a-fronteira-fica-onde"></span>
    <a href="#e-a-fronteira-fica-onde" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A pergunta que interessa pro desenvolvedor não é &ldquo;quem tem mais parâmetro&rdquo;, é &ldquo;o modelo aberto grande entrega no meu dia a dia?&rdquo;. Os números do benchmark v4, na mesma régua:</p>
<table>
  <thead>
      <tr>
          <th>Modelo</th>
          <th style="text-align: right">Nota</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Claude Opus 5 / 5.5</td>
          <td style="text-align: right">100,0</td>
      </tr>
      <tr>
          <td>GPT-6 Luna</td>
          <td style="text-align: right">95,5</td>
      </tr>
      <tr>
          <td>GLM 5.3 (hospedado)</td>
          <td style="text-align: right">94,0</td>
      </tr>
      <tr>
          <td><strong>DeepSeek V4.1 Flash (nuvem ou 4x B300)</strong></td>
          <td style="text-align: right"><strong>92,5</strong></td>
      </tr>
      <tr>
          <td>GLM 5.3 Flash / Qwen3.8 Flash-Next (B300 única)</td>
          <td style="text-align: right">92,5</td>
      </tr>
      <tr>
          <td>GPT-6 Sol</td>
          <td style="text-align: right">91,0</td>
      </tr>
      <tr>
          <td>Claude Sonnet 5</td>
          <td style="text-align: right">91,0</td>
      </tr>
      <tr>
          <td>Kimi K3</td>
          <td style="text-align: right">85,0</td>
      </tr>
  </tbody>
</table>
<p>O DeepSeek V4.1 Flash empatou com o próprio serviço em nuvem, com os mesmos 92,5, e ficou acima do GPT-6 Sol e do Sonnet 5. Ou seja: <strong>o melhor open source atual já está a 1,5 ponto do GLM hospedado e a 7,5 do topo absoluto</strong>, na frente de dois frontier comerciais fortes. Se a sua régua é a minha (achar backdoor e bypass antes de mergear), um open source de 748B local já serve pra revisão diária de código, com 175 tokens por segundo por stream nas minhas medições de admissão.</p>
<p>Mas três avisos antes de você cancelar a assinatura do Claude:</p>
<ol>
<li>
<p><strong>Mais parâmetros não significa mais competência.</strong> O GLM 5.3 full, com ~700B, fez 76 contra 92,5 do Flash dele mesmo. O Kimi K2 Thinking, com 1 trilhão, fez 46. E o Nex N2.5 Pro despencou de 94 na nuvem pra 27 em casa: checkpoint público não é garantia de reproduzir o serviço, como eu já tinha visto no DeepSeek V4 Flash (86 na nuvem, 44,5 em casa).</p>
</li>
<li>
<p><strong>Minha régua é vigilância, não programação geral.</strong> Aqui eu meço quem percebe sabotagem. Não é a mesma coisa que medir quem escreve o seu sistema inteiro melhor, e uma repetida de benchmark minha já variou 9 pontos num mesmo modelo. A distância de 1,5 ponto entre modelos pode ser barulho.</p>
</li>
<li>
<p><strong>Custo muda a resposta.</strong> Se a ideia é ter qualidade de frontier no dia a dia, a API em nuvem do DeepSeek V4.1 custou US$ 1,21 por rodada de benchmark completa. O pod de 4x B300 custa US$ 36 por hora antes mesmo de você apertar o botão. E o caso mais didático continua sendo o DeepSeek V4 Flash da campanha anterior: na nuvem ele fez 86 por US$ 0,97 a rodada; em casa, 44,5 e ainda por cima mais caro por inferência. Às vezes a nuvem é melhor <strong>e</strong> mais barata.</p>
</li>
</ol>
<h2>Vale alugar 4x B300?<span class="hx:absolute hx:-mt-20" id="vale-alugar-4x-b300"></span>
    <a href="#vale-alugar-4x-b300" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra uma pessoa escrevendo código com um agente de manhã e outro de tarde: não. A API resolve por poucos dólares o que o pod resolve por dezenas de dólares por hora.</p>
<p>Pra uma empresa que precisa de dado privado de verdade (código que não pode sair da VPN), checkpoint travado pra reprodutibilidade, ou carga batch contínua (milhares de revisões, classificações, análises por dia), aí o cálculo muda. O DeepSeek V4.1 em 4x B300 entrega qualidade de quase-fronteira com o dado em casa, e isso não se compra com assinatura de chat.</p>
<p>O que eu não compraria de novo sem medir antes é a premissa &ldquo;maior é melhor&rdquo;. Eu comecei essa campanha achando que o salto de 92,5 ia vir de um modelo maior. Veio do modelo certo rodando direito. O resto é VRAM queimada e uma história boa pra contar.</p>
<p>E tem mais um capítulo vindo, de hardware que não é datacenter: os testes com modelos locais na RTX 5090 e no Strix Halo, com um motor de inferência novo chamado Strata. Quando o protocolo terminar, sai o terceiro artigo.</p>
<p>E a pergunta de &ldquo;vale a pena&rdquo; tem uma resposta irritante de óbvia: depende de quantas pessoas vão usar ao mesmo tempo. É ela que eu respondo no próximo artigo: <a href="/2026/10/22/llm-benchmark-v4-can-my-team-fit-in-a-4x-b300-h200/">LLM Benchmark v4: can my team fit in a 4x b300/h200?</a>.</p>
]]></content:encoded><category>benchmarks-de-llm</category><category>modelos-locais</category><category>llms</category><category>hardware</category></item><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>Estamos no Futuro: Carros voam e não temos mais que fazer HTML/CSS</title><link>https://akitaonrails.github.io/2026/10/16/estamos-no-futuro-carros-voam-e-nao-temos-mais-que-fazer-html-css/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/16/estamos-no-futuro-carros-voam-e-nao-temos-mais-que-fazer-html-css/</guid><pubDate>Fri, 16 Oct 2026 02:00:00 -0300</pubDate><description>&lt;p&gt;São mais de duas da manhã e eu ainda estou acordado porque acabei de assistir ao vivo ao novo Tesla Roadster subir uma rampa vertical, parar de cabeça pra baixo e, em outra passagem, girar no ar e pousar inteiro.&lt;/p&gt;
&lt;p&gt;Eu esperava um carro rápido. Não esperava um carro com dez bocais de propulsão a ar comprimido, usando tecnologia derivada da SpaceX, e um sistema de sucção embaixo capaz, &lt;a href="https://paultan.org/2026/10/16/tesla-roadster-revealed-flies-on-spacex-air-thrusters-0-96-km-h-in-0-81s-production-2028/"target="_blank" rel="noopener"&gt;segundo Elon Musk, de puxar até 2 G&lt;/a&gt;. Na demonstração, a Tesla anunciou zero a 60 milhas por hora, quase 97 km/h, em 0,81 segundo. Não divulgou o protocolo da medição.&lt;/p&gt;</description><content:encoded><![CDATA[<p>São mais de duas da manhã e eu ainda estou acordado porque acabei de assistir ao vivo ao novo Tesla Roadster subir uma rampa vertical, parar de cabeça pra baixo e, em outra passagem, girar no ar e pousar inteiro.</p>
<p>Eu esperava um carro rápido. Não esperava um carro com dez bocais de propulsão a ar comprimido, usando tecnologia derivada da SpaceX, e um sistema de sucção embaixo capaz, <a href="https://paultan.org/2026/10/16/tesla-roadster-revealed-flies-on-spacex-air-thrusters-0-96-km-h-in-0-81s-production-2028/"target="_blank" rel="noopener">segundo Elon Musk, de puxar até 2 G</a>. Na demonstração, a Tesla anunciou zero a 60 milhas por hora, quase 97 km/h, em 0,81 segundo. Não divulgou o protocolo da medição.</p>
<p>Era esse o futuro que eu queria ver.</p>
<p>A demonstração inteira está na <a href="https://livestream.tesla.com/"target="_blank" rel="noopener">transmissão oficial da Tesla</a>. A <a href="https://www.roadandtrack.com/news/a74151425/the-air-assisted-tesla-roadster-flies-in-demo-hits-0-60-in-08-seconds/"target="_blank" rel="noopener">Road &amp; Track mediu e descreveu as manobras</a>, e a <a href="https://techcrunch.com/2026/10/15/tesla-reveals-spacex-powered-roadster-that-flies-and-falls-with-style/"target="_blank" rel="noopener">TechCrunch detalhou as três versões</a>: Performance com aerodinâmica ativa e sucção, Thrust com propulsores traseiros, Rocket com sistema de propulsão de dez bocais e apenas um assento.</p>
<p>Claro, o que vimos foi um protótipo numa demonstração controlada. A Tesla prevê iniciar a produção em 2028, e a versão Rocket não está aprovada para uso em vias públicas. Ainda assim, um carro de verdade saiu do chão, girou e pousou. Eu assisti a isso ao vivo do meu escritório. Foi espetacular.</p>
<p>Enquanto Musk faz foguetes pousarem e carros voarem, eu estava fazendo um trabalho bem mais mundano e pequeno: mexendo nos meus sites.</p>
<h2>Seis sites em cerca de um dia<span class="hx:absolute hx:-mt-20" id="seis-sites-em-cerca-de-um-dia"></span>
    <a href="#seis-sites-em-cerca-de-um-dia" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes do evento da Tesla, eu passei o dia inteiro redesenhando seis sites:</p>
<ul>
<li><a href="https://ailair.akitaonrails.com/"target="_blank" rel="noopener">Akita&rsquo;s AI Lair</a>, agora combinando com o visual do meu livro novo, <em>Competente o Suficiente</em>;</li>
<li><a href="https://aimemory.sh/"target="_blank" rel="noopener">ai-memory</a>;</li>
<li><a href="https://aijail.io/"target="_blank" rel="noopener">ai-jail</a>;</li>
<li>a newsletter <a href="https://themakitachronicles.com/"target="_blank" rel="noopener">The M.Akita Chronicles</a>;</li>
<li>o <a href="https://blog.themakitachronicles.com/"target="_blank" rel="noopener">arquivo da newsletter</a>;</li>
<li>e este blog, <a href="https://akitaonrails.com/"target="_blank" rel="noopener">AkitaOnRails.com</a>.</li>
</ul>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/redesign-seis-sites-stitch.jpg" alt="Os seis sites redesenhados em produção: Akita’s AI Lair, ai-memory, ai-jail, The M.Akita Chronicles, o arquivo da newsletter e AkitaOnRails"  loading="lazy" /></p>
<p>Todos começaram com variações daquele tema padrão que o Claude faz. Fundo escuro, gradiente roxo ou azul, fonte sans enorme, cartões arredondados, brilho atrás de alguma coisa. Funciona. Também ficou tão comum que hoje você bate o olho e reconhece &ldquo;site feito pelo Claude&rdquo; antes de ler o título.</p>
<p>Eu já tinha tentado pedir designs diferentes pro Claude. Ele trocava a cor, movia os cartões e entregava a mesma gramática visual. Publiquei <a href="https://x.com/AkitaOnRails/status/2110809199890300986"target="_blank" rel="noopener">algumas telas e o resultado da mudança no Twitter</a>. Eu não sou web designer e, por um tempo, aceitei o &ldquo;funciona&rdquo;. Ontem cansei.</p>
<p>Alguém me sugeriu o <a href="https://stitch.withgoogle.com/"target="_blank" rel="noopener">Stitch, do Google</a>. É uma ferramenta experimental do Google Labs que transforma instruções e imagens em interfaces editáveis e código de front-end. A <a href="https://developers.googleblog.com/en/stitch-a-new-way-to-design-uis/"target="_blank" rel="noopener">primeira versão foi apresentada em 2025</a> em cima do Gemini; <a href="https://blog.google/innovation-and-ai/models-and-research/google-labs/stitch-ai-ui-design/"target="_blank" rel="noopener">a versão atual</a> virou uma tela de design com agente, protótipos e exportação de um <code>DESIGN.md</code> que outros agentes conseguem seguir.</p>
<p><a href="https://blog.google/innovation-and-ai/models-and-research/google-labs/stitch-ai-ui-design/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2026/10/stitch-ai-native-canvas-google.webp" alt="O canvas do Google Stitch com várias interfaces, variações visuais e controles do design system"  loading="lazy" /></a></p>
<p>Criei a conta, peguei o token da API e comecei a trabalhar. O Stitch gerava direções visuais. Claude Code e Codex levavam essas direções pros repositórios reais, adaptavam os componentes, corrigiam responsividade, trocavam imagens, ajustavam SEO, testavam e faziam deploy. Quando a cota de um acabava, eu continuava com o outro.</p>
<p>Os seis sites saem de cinco repositórios. Olhando todos lado a lado, uma coisa ficou clara: o Stitch responde muito melhor quando recebe uma metáfora concreta, o público e o que aquele produto precisa comunicar. O ai-memory virou um arquivo de memória. O ai-jail, uma prisão cinematográfica. O AI Lair adotou uma feira mundial dos anos 1950 e 1960, puxada pela capa do meu livro. A newsletter virou uma transmissão noturna, e este blog, um arquivo editorial. Cinco identidades diferentes em vez de cinco variações do mesmo gradiente do Claude.</p>
<p>O protótipo bonito é a parte fácil. O trabalho só termina quando a linguagem visual chega às páginas internas, ao artigo, ao leitor do livro, ao e-mail, às páginas legais, aos estados de erro e ao celular. Foi isso que os agentes fizeram nos repositórios. No ai-memory, por exemplo, a verificação cobriu 102 páginas em seis idiomas, nos temas claro e escuro, em desktop e celular. No The M.Akita Chronicles, o mesmo design precisou funcionar na inscrição, no arquivo, no detalhe de newsletter, no podcast e no template do e-mail. Um mockup do Stitch não resolve nada disso sozinho.</p>
<p>Foram várias rodadas de protótipos em cada site. Algumas ficaram horrorosas. Outras tinham uma ideia boa cercada de decisões ruins. Eu mandava descartar, preservar uma parte, misturar com outra tentativa e recomeçar. Código ficou barato, então não existe motivo pra se apaixonar pela primeira versão.</p>
<p>O resultado não vai ganhar prêmio de design. Um designer profissional ainda encontraria centenas de coisas pra melhorar. Eu mesmo sei que consigo avançar se gastar mais alguns dias. Só que ficou muito melhor do que estava e, por enquanto, está competente o suficiente.</p>
<p>Seis sites existentes redesenhados, responsivos, com SEO, geração e substituição de imagens, ajustes de HTML, CSS e JavaScript, teste e deploy em produção. Uma pessoa. Cerca de um dia. Um ano atrás, esse pacote seria trabalho de semanas ou meses.</p>
<p>Vamos manter a proporção: são sites pequenos, a maioria landing pages e páginas de conteúdo. Ninguém vai recriar o conjunto inteiro de páginas e sistemas da Amazon.com em um dia. O experimento mostra quanto trabalho repetitivo já saiu da frente, dentro de um escopo que uma pessoa experiente consegue entender e revisar. Projetos grandes continuam grandes, cheios de regras privadas, integrações antigas, estados estranhos e decisões que não cabem num prompt.</p>
<h2>Até o primeiro proofreading pode ser automatizado<span class="hx:absolute hx:-mt-20" id="até-o-primeiro-proofreading-pode-ser-automatizado"></span>
    <a href="#at%c3%a9-o-primeiro-proofreading-pode-ser-automatizado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No mesmo processo eu mandei agentes revisarem os sites como leitores diferentes. Não era correção gramatical. Eu queria saber o que uma pessoa entenderia nos primeiros segundos, onde ficaria perdida, qual promessa pareceria suspeita e se conseguiria completar uma tarefa sem já conhecer o produto.</p>
<p>No ai-jail, usei duas sessões isoladas. Uma assumiu o papel de um engenheiro cético que aprova todos os prompts e nunca tinha ouvido falar do projeto. A outra era gerente de oito desenvolvedores, sem ser engenheira, tentando decidir se deveria aprovar a ferramenta na empresa. No ai-memory foram três: um engenheiro aprendendo ferramentas de IA, uma gerente avaliando um piloto e um pai sem experiência com agentes, preocupado com AGI e empregos. Cada um leu o site sem ver o relatório dos outros.</p>
<p>Qualquer pessoa consegue fazer o mesmo. Suba a aplicação localmente ou abra um deploy de preview, comece sessões novas e dê a cada agente uma tarefa concreta. Um prompt básico pode ser 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-text" data-lang="text"><span class="line"><span class="cl">Abra este site em desktop e celular: [URL].
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Você é [perfil], conhece [contexto] e nunca viu este produto.
</span></span><span class="line"><span class="cl">Seu objetivo é [decisão ou tarefa concreta].
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Navegue como essa pessoa. Não leia código nem documentação interna antes.
</span></span><span class="line"><span class="cl">Registre:
</span></span><span class="line"><span class="cl">- o que entendeu nos primeiros dez segundos;
</span></span><span class="line"><span class="cl">- o que acha que o produto faz e para quem serve;
</span></span><span class="line"><span class="cl">- onde não soube qual era o próximo passo;
</span></span><span class="line"><span class="cl">- promessas contraditórias, absolutas ou difíceis de acreditar;
</span></span><span class="line"><span class="cl">- termos que o site usa antes de explicar;
</span></span><span class="line"><span class="cl">- problemas de leitura, teclado, contraste, toque e responsividade;
</span></span><span class="line"><span class="cl">- o que impediria você de testar, comprar ou aprovar.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Para cada achado, mostre a página e a evidência visível.
</span></span><span class="line"><span class="cl">Separe bug factual, problema de uso e preferência subjetiva.
</span></span><span class="line"><span class="cl">Não proponha redesign geral. Sugira a menor mudança que resolve o problema.</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>Eu prefiro três sessões independentes a uma mesa-redonda de agentes. Se um lê o relatório do outro, começa a concordar por inércia. Depois uma sessão separada consolida os pontos repetidos, descarta gosto pessoal e confere cada afirmação contra o código e a documentação do produto. A revisão pode apontar que uma frase está errada; ela não tem autoridade pra inventar a frase correta.</p>
<p>Os resultados mais úteis costumam cair em quatro grupos: posicionamento, ordem da informação, caminho até a primeira ação e confiança. Testes automáticos tradicionais continuam cuidando de link quebrado, contraste, acessibilidade, overflow e estado de erro. A persona mostra que o botão tecnicamente acessível apareceu cinco telas depois de onde a pessoa precisava dele.</p>
<p>No <a href="https://github.com/akitaonrails/ai-jail-web/blob/main/docs/research/PROOFREAD-2026-10-15-BRIEF.md"target="_blank" rel="noopener">proofreading do ai-jail</a>, os dois perfis concordaram que o site demorava pra mostrar o risco concreto e falava cedo demais como documentação de engenheiro. A ameaça de uma página envenenada foi puxada pra logo depois do hero. Entraram as seções &ldquo;O que muda no primeiro dia&rdquo; e &ldquo;Para quem precisa aprovar isto&rdquo;, com custo operacional, limites e as chaves que uma empresa realmente expõe. A revisão também encontrou uma afirmação tecnicamente errada repetida em várias páginas: a rede não fica inteiramente desligada por padrão. O host da própria API do agente e, no Linux, os registries continuam acessíveis. Esse tipo de leitura também encontra bug de documentação.</p>
<p>O <a href="https://github.com/akitaonrails/ai-memory-web/blob/main/docs/research/persona-review.md"target="_blank" rel="noopener">relatório do ai-memory</a> encontrou permissões descritas de formas contraditórias, promessas de privacidade absolutas demais e a explicação básica soterrada por features avançadas. Na segunda rodada, os perfis perceberam que a instrução de acesso remoto mandava usar HTTP antes de exigir TLS, que um <code>curl</code> sem autenticação prometia a resposta errada e que escolhas importantes apareciam tarde demais no celular. O guia de coleta saiu de aproximadamente 4.364 pixels do topo pra 1.617; os caminhos pra indivíduo ou equipe, de 5.300 pra 2.489. Isso é uma melhoria objetiva nascida de leitura simulada.</p>
<p>No <a href="https://github.com/akitaonrails/ai-toolkit-web/pull/4"target="_blank" rel="noopener">redesign do AI Lair</a>, a revisão para leitores novos reorganizou o caminho de entrada. Uma <a href="https://github.com/akitaonrails/ai-toolkit-web/commit/c2a5237e876f662a8b53450f23f0162fdc1d4a76"target="_blank" rel="noopener">revisão seguinte</a> deixou explícito que o meu setup com vários agentes e dois monitores é uma configuração madura, não requisito pra começar. Uma pessoa, um projeto e um agente bastam. Também surgiu um roteiro de piloto pra gerente: tarefa reversível, dado não sensível, critério de aceitação, prazo, orçamento e medição. A auditoria seguinte encontrou ARIA inválido e uma animação que escondia o texto de abertura de tecnologias assistivas; a <a href="https://github.com/akitaonrails/ai-toolkit-web/pull/5"target="_blank" rel="noopener">correção</a> levou o axe-core de quatro violações sérias pra zero.</p>
<p>Personas simuladas ainda ficam muito abaixo de feedback humano. Carregam os mesmos vícios dos modelos, podem concordar com o prompt e nunca sentem a irritação de quem perdeu vinte minutos, nem assumem o risco de colocar o próprio cartão de crédito. Os relatórios servem como hipóteses. Um usuário real sempre encontra o caso que eu, o agente e a suíte de testes não imaginaram.</p>
<p>Só que antes do primeiro usuário existir, a alternativa costuma ser nenhuma revisão. Duas ou três leituras independentes já pegam promessa contraditória, jargão sem explicação, informação enterrada e fluxo que só faz sentido pra quem escreveu. Rodar essas leituras custa pouco e deixa o produto menos burro antes de chegar em gente de verdade.</p>
<h2>HTML nunca foi programação<span class="hx:absolute hx:-mt-20" id="html-nunca-foi-programação"></span>
    <a href="#html-nunca-foi-programa%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Durante anos eu vi gente fazendo uma ginástica danada pra defender que &ldquo;HTML é linguagem de programação&rdquo; e, portanto, quem escreve HTML é programador.</p>
<p>HTML é marcação: descreve a estrutura de um documento. CSS descreve apresentação. Isso tem valor, exige conhecimento e pode ficar terrivelmente complexo. Complexidade acidental não transforma marcação em programação.</p>
<p>E não há vergonha nenhuma nisso. Comecei a fazer sites nos anos 1990. Recebia um layout feito no Photoshop e transformava aquilo em HTML. Recortava imagens, montava tabela, ajustava pixel, abria em vários navegadores, voltava e tentava de novo. Foi trabalho honesto e pagou minhas contas.</p>
<p>Também era um trabalho basicamente mecânico: copiar, colar, ajustar, olhar, repetir. Exatamente a categoria que computadores deveriam automatizar.</p>
<p>Hoje eu ainda escolhi direção visual, descartei propostas ruins, decidi o que cada site precisava comunicar e reconheci quando o resultado estava bom o suficiente. Os agentes fizeram o serviço braçal. É assim que deveria ter sido há anos.</p>
<h2>A bolha atrasou a automação<span class="hx:absolute hx:-mt-20" id="a-bolha-atrasou-a-automação"></span>
    <a href="#a-bolha-atrasou-a-automa%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Durante boa parte dos anos 2010, <a href="https://fred.stlouisfed.org/series/EFFR"target="_blank" rel="noopener">os juros americanos ficaram perto de zero</a>. Dinheiro barato irrigou fundos, startups e empresas grandes. Contratar mais gente era fácil, e o mercado absorvia qualquer quantidade de trabalho manual disfarçado de engenharia.</p>
<p>Juro baixo não explica sozinho a expansão da tecnologia, mas uma área em recorde de contratação tem pouco incentivo pra eliminar o próprio trabalho repetitivo. Se você consegue colocar mais cinquenta pessoas pra montar tela, coloca mais cinquenta pessoas pra montar tela.</p>
<p>Quem assistiu às minhas palestras em eventos de tecnologia nos anos 2010 deve lembrar de <strong>Restrição == Inovação</strong>. O nome correto era com dois sinais de igual mesmo. Apresentei essa palestra na DevConf 2019, os <a href="https://www.slideshare.net/slideshow/devconf-2019-so-carlos/143923367"target="_blank" rel="noopener">83 slides estão no meu SlideShare</a>, e eu já tinha escrito a mesma tese em 2013, em <a href="https://startupi.com.br/restricoes-sao-libertadoras-menos-e-mais/"target="_blank" rel="noopener">&ldquo;Restrições são libertadoras: menos é mais&rdquo;</a>.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/V0D3PuTLOZ8"
    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>É a velha máxima: <strong>a necessidade é a mãe da invenção</strong>. Restrição obriga a escolher. Escolha obriga a priorizar. Quando o caminho convencional não cabe no dinheiro, no prazo ou na capacidade disponível, alguém precisa encontrar outro caminho. É daí que costuma sair inovação de verdade.</p>
<p>Dinheiro financia pesquisa cara, compra equipamento e dá tempo pra experimentar. Sobra de dinheiro sem pressão por resultado faz outra coisa: esconde ineficiência com força bruta. Não precisa automatizar, simplificar nem descobrir uma solução melhor enquanto ainda der pra contratar mais gente e aumentar a planilha. Abundância demais transforma problema técnico em despesa operacional e empurra a decisão difícil pra depois.</p>
<p>Eu <a href="/2026/02/08/rant-ia-acabou-com-programadores/">repeti esse aviso no começo de 2026</a>: a bolha de programação conseguiu substituir eficiência por volume. A restrição voltou quando o dinheiro barato acabou. Coincidentemente, os agentes chegaram na mesma hora e deram às empresas uma maneira prática de automatizar o trabalho mecânico que elas passaram uma década escalando com contratação.</p>
<p>Foi assim que a gente passou uma década recriando o mesmo painel administrativo e chamando de inovação. Toda semana aparecia a próxima salvação do front-end: React, Angular, Vue, Svelte, outro <em>bundler</em>, outro framework em cima do framework. São ferramentas úteis em seus contextos, mas nenhuma atacava a pergunta óbvia: por que um ser humano ainda está escrevendo à mão a milésima variação do mesmo formulário?</p>
<p>O estouro da bolha de contratação em 2022 encerrou a festa. No fim daquele ano, o ChatGPT levou modelos de linguagem ao público. GPT-2, só pra corrigir a cronologia, tinha saído em 2019. Quatro anos depois do ChatGPT, programação agêntica virou ferramenta normal de trabalho.</p>
<p>Qualquer tarefa que consiste em copiar padrão, adaptar exemplo, rodar, olhar o erro e tentar de novo já está no alcance desses agentes. Front-end é o alvo mais visível porque acumulou uma quantidade absurda desse trabalho. O resto da programação não está protegido por alguma aura mística. Tem muito back-end que é CRUD, cola entre APIs e configuração repetida. Também será automatizado.</p>
<p>Pra mim, ótimo. Programar nunca foi digitar código. Programar é fazer um computador executar algo útil.</p>
<h2>VS Code é o novo cartão perfurado<span class="hx:absolute hx:-mt-20" id="vs-code-é-o-novo-cartão-perfurado"></span>
    <a href="#vs-code-%c3%a9-o-novo-cart%c3%a3o-perfurado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu escrevi exatamente sobre isso em abril, em <a href="/2026/04/11/vs-code-e-o-novo-cartao-perfurado/">&ldquo;VS Code é o novo Cartão Perfurado&rdquo;</a>.</p>
<p>Por décadas, a interface entre intenção e computador mudou. Primeiro você ligava fios e chaves. Depois perfurava cartões. Depois escrevia Assembly, C, linguagens de alto nível, frameworks e configuração. Cada geração olhou pra anterior e automatizou uma camada de trabalho manual.</p>
<p>Ninguém em sã consciência defenderia hoje que operar uma perfuradora de cartões era a essência da ciência da computação. Era a interface disponível. Digitar cada tag e cada regra de CSS no VS Code também era a interface disponível.</p>
<p>Agora eu descrevo a intenção, mostro referências, avalio o resultado e mando o agente corrigir. Ainda preciso entender como navegador, rede, cache, acessibilidade, semântica, responsividade e SEO funcionam. Sem fundamento eu nem saberia dizer se o agente entregou uma solução ou uma bomba-relógio bonita. O teclado perdeu importância; julgamento ganhou.</p>
<p>Hoje, todos os modelos Tier A que eu testo, de Claude a Kimi, conseguem executar com confiabilidade o trabalho mecânico de front-end que apareceu nesses seis projetos. Eu consigo afirmar, sem hesitar, que qualquer tarefa de front-end que só transforma uma decisão já tomada em código é automatizável. Eles não inventaram seis designs brilhantes sozinhos, porém nenhum precisou que eu escrevesse manualmente o flexbox, o breakpoint ou o componente que já expliquei.</p>
<p>Isso cobre uma parcela enorme do trabalho, mas deixa justamente a parte de maior valor. Alguém ainda precisa conhecer os prós e contras do framework, entender o browser, escolher a arquitetura, perceber quando a biblioteca está sendo usada contra o seu desenho e descobrir por que uma solução aparentemente correta quebra numa combinação específica de aparelho, idioma, estado e dado real. O agente escreve o código; um profissional experiente dá direção, limita o estrago e assume o resultado.</p>
<p>Front-end continua existindo. O excesso de trabalho mecânico e de gente contratada só pra executar receita pronta é que perde valor. Um ótimo profissional de front-end consegue dirigir agentes e entregar muito mais do que entregava sozinho. Um profissional mediano que passou anos confundindo digitação de componente com engenharia vai disputar menos vagas. O mercado fica menor e mais seletivo, com os melhores profissionais produzindo mais. A meta continua a mesma de sempre: chegar perto do topo, onde julgamento e experiência ainda fazem diferença.</p>
<p>E isso não para no browser. <a href="/2026/10/11/existem-linguagens-melhores-pra-programar-com-llms/">Escolha o framework</a>: Android com Flutter ou Kotlin, iOS com Swift e SwiftUI, desktop com Rust/Tauri, Flutter, C# ou Electron. Em geral, agentes modernos conhecem os componentes, as ferramentas de build, os testes e as peculiaridades dessas plataformas. Ainda precisam de direção, feedback e verificação, exatamente como no front-end web. A parte mecânica já é automatizável.</p>
<p>Eu testei isso nos meus próprios projetos pequenos e abertos. O <a href="https://github.com/akitaonrails/FrankYomik"target="_blank" rel="noopener">FrankYomik</a> tem um cliente Flutter pra Android e Linux, além do servidor e da extensão. O <a href="https://github.com/akitaonrails/FrankSherlock"target="_blank" rel="noopener">Frank Sherlock</a> é um app desktop em Rust/Tauri que gera AppImage pra Linux, DMG pra macOS e instalador <code>.exe</code> pra Windows. Também fiz o <a href="https://github.com/akitaonrails/frank_karaoke"target="_blank" rel="noopener">Frank Karaoke</a> em Flutter pra Android e o <a href="https://github.com/akitaonrails/frank_mangaplus"target="_blank" rel="noopener">FRANK MANGA+</a> em Rust/Tauri pra desktop. Há vários outros no <a href="https://github.com/akitaonrails?tab=repositories"target="_blank" rel="noopener">meu GitHub</a>.</p>
<p>Nenhum deles saiu perfeito no primeiro prompt. Flutter e Tauri me deram trabalho, especialmente nos primeiros projetos. Confiável quer dizer que o agente consegue avançar, receber uma correção, criar testes, corrigir o que quebrou e chegar num aplicativo funcional sem eu assumir a digitação braçal. Depois de testar web, Android e desktop em stacks diferentes, estou muito satisfeito com o resultado. O padrão se repete: trabalho mecânico virou trabalho de agente.</p>
<p>Às duas da manhã eu tinha agentes fazendo os últimos ajustes neste blog sem perder o ritmo. É disso que eu preciso.</p>
<h2>Quando a web ainda não sabia o que era<span class="hx:absolute hx:-mt-20" id="quando-a-web-ainda-não-sabia-o-que-era"></span>
    <a href="#quando-a-web-ainda-n%c3%a3o-sabia-o-que-era" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>No auge da bolha da Internet, designers de carreira estavam criando sites. A web ainda não tinha decidido como uma página deveria funcionar, então havia espaço real pra experimentar. Era feio muitas vezes, pesado quase sempre, mas tinha criação acontecendo.</p>
<p>O iPhone abriu outra janela em 2007. De repente tudo precisava caber numa tela pequena, responder ao toque e funcionar sem mouse. Vieram anos de experimentação até o mercado formar uma gramática razoavelmente estável de navegação, gesto, formulário e conteúdo.</p>
<p>Hoje ninguém precisa redescobrir um menu, uma lista, um checkout ou uma página de artigo. Os padrões estão documentados, milhões de exemplos estão públicos e os modelos viram mais interfaces do que qualquer pessoa conseguiria estudar durante a vida.</p>
<p>O trabalho mais valioso continua existindo: entender o produto, a pessoa que vai usá-lo, as restrições e o que merece aparecer primeiro. O trabalho de converter uma decisão pronta em centenas de linhas previsíveis virou candidato perfeito à automação.</p>
<h2>Empregos desaparecem. Outros aparecem<span class="hx:absolute hx:-mt-20" id="empregos-desaparecem-outros-aparecem"></span>
    <a href="#empregos-desaparecem-outros-aparecem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O mundo nunca ficou parado pra preservar uma função.</p>
<p>Operadores de cartão perfurado digitavam dados e programas em papel. Telefonistas conectavam chamadas fisicamente. Datilógrafos mantinham escritórios funcionando. Linotipistas montavam linhas de metal quente pra imprimir jornais. A tecnologia reduziu ou eliminou essas ocupações em escala. A <a href="https://www.bls.gov/emp/tables/fastest-declining-occupations.htm"target="_blank" rel="noopener">lista atual de profissões em maior declínio do Bureau of Labor Statistics</a> ainda inclui digitadores, datilógrafos e operadores de telefonia. Ninguém propõe voltar a elas pra proteger emprego.</p>
<p>Ao mesmo tempo apareceram trabalhos que uma pessoa de 1926 nem conseguiria nomear: desenvolvedor mobile, SRE, designer de experiência, criador de vídeo pra Internet, especialista em nuvem, engenheiro de machine learning. A própria web matou trabalho antigo e abriu uma indústria inteira.</p>
<p>A história real é menos limpa do que a caricatura. A <a href="https://www.ilo.org/publications/generative-ai-and-jobs-refined-global-index-occupational-exposure"target="_blank" rel="noopener">OIT mostra que a tecnologia costuma automatizar tarefas sem necessariamente eliminar ocupações inteiras</a>. Algumas funções encolhem, outras mudam e trabalhos novos aparecem. Esse consolo estatístico não resolve a vida de quem ficou obsoleto hoje. A pessoa precisa reagir antes que a média da economia reaja por ela.</p>
<p>Compare 2026 com 1926, três anos antes do crash de 1929. Parece outro planeta, embora um século seja muito pouco na escala da história. Não existe contrato garantindo que a sua função atual sobreviva aos próximos dez anos, muito menos aos próximos quarenta.</p>
<h2>E como um júnior ganha experiência agora?<span class="hx:absolute hx:-mt-20" id="e-como-um-júnior-ganha-experiência-agora"></span>
    <a href="#e-como-um-j%c3%banior-ganha-experi%c3%aancia-agora" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Vai ficar mais difícil. A escada antiga começava com alguém pagando um iniciante pra montar tela simples, corrigir CSS e copiar componente. O agente faz boa parte disso mais rápido e barato. Empresa nenhuma tem obrigação de preservar trabalho ineficiente como programa de treinamento.</p>
<p>O iniciante vai ter que construir a própria escada com projetos reais e problemas pelos quais ele seja responsável. Isso inclui ler o código que o agente gerou, quebrar e consertar, estudar documentação e código-fonte, escrever testes e contribuir com projetos abertos. O agente dá a qualquer estudante acesso a uma quantidade de prática que antes exigia uma equipe. Quem usar isso só pra gerar código que não entende vai continuar júnior por muito tempo.</p>
<p>O mercado vai premiar quem fizer esse esforço extra. Sempre premiou. Quando alguém pergunta como o júnior vai ganhar experiência sem as tarefas fáceis, muitas vezes a pergunta escondida é outra: &ldquo;como eu consigo um salário melhor sem passar pelo trabalho difícil de ficar bom?&rdquo; A vaga fácil, com salário crescente e pouca cobrança, foi uma distorção da bolha. Adaptar-se inclui aceitar que a entrada ficou mais competitiva.</p>
<h2>Minha carreira nunca ficou no mesmo lugar<span class="hx:absolute hx:-mt-20" id="minha-carreira-nunca-ficou-no-mesmo-lugar"></span>
    <a href="#minha-carreira-nunca-ficou-no-mesmo-lugar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><a href="/2023/02/14/akitando-138-meu-comeco-de-carreira-durante-a-bolha-1997-a-2002-bug-do-milenio-crash/">Comecei com multimídia e sites estáticos</a>. Depois trabalhei em agência digital, consultoria enterprise, integração de SAP, startups, vídeo, educação e, agora, IA. No <a href="/2018/12/26/akitando-34-voce-nao-sabe-nada-de-enterprise-conhecendo-a-sap/">Akitando sobre SAP</a>, eu conto parte dessa fase em que precisei aprender um mundo completamente diferente do que fazia antes.</p>
<p>Atravessei o crash das pontocom, a crise de 2008 e o fim da bolha de juro zero. Sobrevivi porque nunca tratei a minha função do momento como identidade permanente. Continuei estudando, acumulando experiência e mudando de direção.</p>
<p>Uma carreira produtiva não deveria consistir em repetir a mesma tarefa mecânica por décadas. Se o seu valor depende de o mercado continuar pagando alguém pra empurrar tag e pixel à mão, o problema não começou com a IA. O problema foi apostar que o presente seria eterno.</p>
<p>Muita gente entende &ldquo;estar preparado&rdquo; como estudar o mínimo necessário pra vaga aberta hoje. Essa é a receita mais rápida pra ficar obsoleto. Faça o trabalho de curto prazo, mas estude além dele. Aprenda as coisas que parecem inúteis agora. Fundamentos, sistemas operacionais, redes, bancos de dados, negócio, economia, história. Você não sabe qual combinação vai salvar sua carreira daqui a cinco anos.</p>
<p>Eu passei anos falando disso no Akitando. Reveja as playlists <a href="https://www.youtube.com/playlist?list=PLdsnXVqbHDUehzKjiRruy--gncHz9Injy"target="_blank" rel="noopener"><strong>Eu Avisei</strong></a> e <a href="https://www.youtube.com/playlist?list=PLdsnXVqbHDUd6wEL932R7dW9Zazt2oNWF"target="_blank" rel="noopener"><strong>Que curso devo fazer??</strong></a>. Se tiver que começar por um vídeo, comece pelo <strong>Guia DEFINITIVO de Aprendendo a Aprender</strong>. Curso nenhum salva quem ainda terceiriza o próprio aprendizado.</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/oUPaJxk6TZ0"
    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>O mundo não tem falta de oportunidade. A maioria das pessoas sente que nunca recebe uma porque está sempre despreparada quando ela aparece. Quando a oportunidade pousa na sua frente, já é tarde pra começar a preparação.</p>
<p>Portanto, eu não vou lamentar que o pacote desses seis sites pequenos, que antes tomaria semanas ou meses, tenha cabido em cerca de um dia. Estou feliz pra cacete. Ganhei tempo pra pensar no que construir, experimentar mais e jogar fora versões ruins sem desperdiçar semanas de vida.</p>
<p>Hoje eu vi um carro girar no ar e pousar. Em cerca de um dia, coloquei seis sites com visual novo em produção sem escrever a maior parte do HTML e CSS. O futuro que eu queria não chegou perfeito, barato nem distribuído igualmente. Mas chegou o bastante pra eu trabalhar com ele.</p>
<p>Aceite o que acabou. Estude o que vem depois. E esteja pronto antes da próxima oportunidade aparecer.</p>
]]></content:encoded><category>inteligencia-artificial</category><category>frontend</category><category>automacao</category><category>carreira</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>Auto-publiquei meu próprio Livro/e-book: Competente o Suficiente</title><link>https://akitaonrails.github.io/2026/10/13/auto-publiquei-meu-proprio-livro-ebook-competente-o-suficiente/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/13/auto-publiquei-meu-proprio-livro-ebook-competente-o-suficiente/</guid><pubDate>Tue, 13 Oct 2026 19:00:00 -0300</pubDate><description>&lt;p&gt;Acabei de publicar meu próprio livro sobre inteligência artificial. Chama &lt;em&gt;Competente o Suficiente&lt;/em&gt;, tem o subtítulo &amp;ldquo;Um ano colocando a inteligência artificial à prova, sem medo e sem propaganda&amp;rdquo;, e é de graça, em EPUB e PDF, pra ler no celular, no PC ou no Kindle. A página oficial é &lt;a href="https://ailair.akitaonrails.com/pt-br/book/"target="_blank" rel="noopener"&gt;esta&lt;/a&gt;, e o &lt;a href="https://github.com/akitaonrails/ai-book-2026"target="_blank" rel="noopener"&gt;repositório&lt;/a&gt; é público.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ailair.akitaonrails.com/pt-br/book/"target="_blank" rel="noopener"&gt;&lt;img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261013190000_livro-competente-o-suficiente-3d.png" alt="Competente o Suficiente, o livro, em 3D" loading="lazy" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;O livro consolida quase tudo que eu escrevi sobre IA aqui no blog de fevereiro a outubro de 2026: todas as experiências, os projetos Frank, os benchmarks, bilhões de tokens gastos e centenas de horas de trabalho ao longo de oito meses, reorganizados num formato mais fácil de ler do que uma pilha de posts datados. Saiu em português e em inglês.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Acabei de publicar meu próprio livro sobre inteligência artificial. Chama <em>Competente o Suficiente</em>, tem o subtítulo &ldquo;Um ano colocando a inteligência artificial à prova, sem medo e sem propaganda&rdquo;, e é de graça, em EPUB e PDF, pra ler no celular, no PC ou no Kindle. A página oficial é <a href="https://ailair.akitaonrails.com/pt-br/book/"target="_blank" rel="noopener">esta</a>, e o <a href="https://github.com/akitaonrails/ai-book-2026"target="_blank" rel="noopener">repositório</a> é público.</p>
<p><a href="https://ailair.akitaonrails.com/pt-br/book/"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/20261013190000_livro-competente-o-suficiente-3d.png" alt="Competente o Suficiente, o livro, em 3D"  loading="lazy" /></a></p>
<p>O livro consolida quase tudo que eu escrevi sobre IA aqui no blog de fevereiro a outubro de 2026: todas as experiências, os projetos Frank, os benchmarks, bilhões de tokens gastos e centenas de horas de trabalho ao longo de oito meses, reorganizados num formato mais fácil de ler do que uma pilha de posts datados. Saiu em português e em inglês.</p>
<blockquote>
  <p><strong>Aviso importante:</strong> isso é um <em>preview</em>. O livro ainda está em andamento e vão sair novas versões até eu considerar ele pronto. Se você achar qualquer coisa errada, de erro de digitação a fato, <a href="https://github.com/akitaonrails/ai-book-2026/issues"target="_blank" rel="noopener">abra uma issue no GitHub</a>. O rastro que eu descrevo abaixo existe justamente pra que a correção chegue no parágrafo certo.</p>

</blockquote>
<p>Este texto é sobre o bastidor técnico. Como se transforma 131 posts de blog, escritos ao longo de meses, com opiniões que mudaram no caminho, num livro com começo, meio, fim, apêndice, glossário e bibliografia. E como se faz isso com agentes sem perder o controle do que o livro diz.</p>
<h2>O que tem dentro<span class="hx:absolute hx:-mt-20" id="o-que-tem-dentro"></span>
    <a href="#o-que-tem-dentro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Cinco partes, cada uma respondendo a uma pergunta que as pessoas vivem me fazendo:</p>
<ol>
<li><strong>O que é essa coisa, afinal.</strong> Ela é inteligente? A AGI está chegando?</li>
<li><strong>A maratona.</strong> Funciona de verdade, ou é tudo demo?</li>
<li><strong>Números e propaganda.</strong> Qual IA é a melhor? Dá pra acreditar nos anúncios?</li>
<li><strong>Quem fica com as chaves.</strong> É perigoso deixar ela mexer nas minhas coisas?</li>
<li><strong>E o meu emprego?</strong> Ela vai me substituir? O que vem depois?</li>
</ol>
<p>São 20 capítulos e dois interlúdios, mais prefácio, um &ldquo;como ler&rdquo;, um &ldquo;antes de começar&rdquo; dizendo o que o livro deixa de explicar e onde aprender, epílogo e posfácio. Depois vêm dez apêndices (a máquina por dentro sem matemática, leituras pra ir mais fundo, como estudar com a IA do lado, três bolhas, linha do tempo, kit mínimo pra usar agente com segurança, ai-memory, como ler um benchmark ou um anúncio, os projetos do ano, e os bastidores pra quem programa), as fontes, os créditos de imagem, um glossário de quase 150 termos e mais de 300 referências em ABNT. A edição impressa passa de 500 páginas em 6 por 9 polegadas; a de tela, uns 475. São mais de 50 figuras, todas com ficha de origem.</p>
<p>O público é diferente do blog. O livro foi escrito pra quem está ansioso ou confuso com IA: o profissional com medo de ser substituído, o pai que leu que a AGI chega em dois anos, o gerente a quem estão vendendo uma &ldquo;transformação por IA&rdquo;. A maioria não programa. Isso muda a voz, e eu volto nisso.</p>
<h2>O problema: meio milhão de palavras que discordam entre si<span class="hx:absolute hx:-mt-20" id="o-problema-meio-milhão-de-palavras-que-discordam-entre-si"></span>
    <a href="#o-problema-meio-milh%c3%a3o-de-palavras-que-discordam-entre-si" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em 12 de outubro publiquei <a href="/2026/10/12/llms-agentes-competente-o-suficiente/">a síntese do ano</a>, que virou o capítulo 20. No dia 13 comecei o livro. Eu tinha 131 textos de 2026 no blog, quase meio milhão de palavras, de benchmark de modelo a carteira de bitcoin.</p>
<p>Reorganizar isso à mão levaria meses. E tem um problema pior que o volume: eu mudei de opinião durante o ano. Refiz o benchmark várias vezes, corrigi custos, troquei de ferramenta, e um post de fevereiro contradiz um de setembro com toda a naturalidade. Um livro que simplesmente colasse os posts em ordem seria um livro que se contradiz.</p>
<p>Então fiz o que o livro inteiro descreve: entreguei o trabalho braçal pra agentes de código, e fiquei com a parte de decidir, montar o processo e conferir. O processo é o assunto deste texto.</p>
<h2>Passo 1: um inventário lacrado<span class="hx:absolute hx:-mt-20" id="passo-1-um-inventário-lacrado"></span>
    <a href="#passo-1-um-invent%c3%a1rio-lacrado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A primeira coisa foi um script que lê os 131 posts e grava, pra cada um, o hash SHA-256 do arquivo em português e do arquivo em inglês, mais o commit exato do repositório do blog de onde tudo foi lido. Se uma vírgula muda num post, o hash muda junto. Esse arquivo é gerado por máquina e nunca editado à mão.</p>
<p>Depois, um agente leu cada post e preencheu um formulário fixo, com esquema definido em YAML: a tese em uma ou duas frases, as afirmações que um livro precisaria conferir, quais delas envelhecem rápido, que medo de leitor leigo o texto responde, o que cortar. Cada análise leva o hash do post de onde saiu.</p>
<p>Dos 131 textos, 96 tinham IA como assunto central ou com peso relevante. Os outros só entraram quando ensinavam alguma coisa específica, e cada post sobre IA deixado de fora tem o motivo anotado no arquivo de estrutura. Um script reprova a estrutura se qualquer post de IA estiver sem destino.</p>
<h3>Linhagem: quem substitui quem<span class="hx:absolute hx:-mt-20" id="linhagem-quem-substitui-quem"></span>
    <a href="#linhagem-quem-substitui-quem" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra resolver o problema das contradições, existe um mapa de linhagem. Ele diz qual post substituiu qual, o que ainda dá pra aproveitar do mais velho, e quais correções vindas dos projetos de referência (o repositório do benchmark, o do ai-memory, o do ai-jail, as anotações do meu servidor de casa) se aplicam a cada texto.</p>
<p>A regra pros capítulos é contar o estado mais recente e, quando a mudança ensina alguma coisa, contar a mudança. &ldquo;Em junho eu achava X; em julho os números diziam Y&rdquo; é história. Imprimir o número velho como se fosse atual é erro, e o verificador trata como erro.</p>
<h2>Passo 2: a estrutura é dado, e gera a configuração<span class="hx:absolute hx:-mt-20" id="passo-2-a-estrutura-é-dado-e-gera-a-configuração"></span>
    <a href="#passo-2-a-estrutura-%c3%a9-dado-e-gera-a-configura%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Das análises saiu uma proposta de estrutura: cinco partes, cada uma respondendo a um medo do leitor, vinte capítulos, dois interlúdios. Eu li, mexi e aprovei. Algumas peças entraram a meu pedido, como o &ldquo;antes de começar&rdquo; e o apêndice sobre a máquina por dentro.</p>
<p>Essa estrutura vive num único arquivo YAML, e ele é a fonte da verdade. Cada capítulo tem lá o id, o título, a cena de abertura, a pergunta que responde, o arco, a lista de posts de origem e as ideias de figura. A configuração do Quarto, que monta o livro, é gerada a partir desse arquivo por script. Ninguém edita a configuração na mão.</p>
<p>Isso importa porque mudança estrutural é onde livro quebra. No meio do caminho eu mudei o corte: os textos de janeiro saíram, e a história ficou entre fevereiro e outubro. Dez posts sumiram da base.</p>
<p>Três capítulos precisaram de cena nova, uma dúzia de unidades precisou de reparo pequeno, e o estrago difícil de ver estava em outro lugar: uma frase num capítulo intocado dizendo &ldquo;os cinco revisores do capítulo 5&rdquo;, a linha do tempo, o apêndice de projetos, rótulos de gráfico, cada tradução. Por isso existe um script de impacto que, antes de qualquer edição, lista cada parágrafo, figura, referência e tradução que depende de um post.</p>
<h2>Passo 3: um capítulo piloto vira contrato<span class="hx:absolute hx:-mt-20" id="passo-3-um-capítulo-piloto-vira-contrato"></span>
    <a href="#passo-3-um-cap%c3%adtulo-piloto-vira-contrato" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Antes de escalar, um capítulo piloto. Escolhi o capítulo 5, sobre os primeiros dez por cento de um projeto, e ele serviu pra calibrar a voz e descobrir as dúvidas que um agente teria no meio do caminho. Cada dúvida resolvida virou uma regra escrita.</p>
<p>Essas regras foram pra um documento de contrato que todo agente lê antes de escrever, junto com três coisas:</p>
<ul>
<li>o guia de voz do livro, que herda as regras do blog (o meu WRITER.md, o humanizer e a minha checklist de acabamento) e só adiciona o que muda quando o texto sai de um post datado e vai pra um livro que um leigo segura na mão;</li>
<li>um arquivo de continuidade, dizendo qual capítulo explica cada conceito e como cada personagem se chama, pra ninguém explicar &ldquo;janela de contexto&rdquo; cinco vezes;</li>
<li>a estrutura inteira, pra cada agente saber o que os outros capítulos cobrem.</li>
</ul>
<p>Além das regras de voz que já existiam (capítulo abre numa cena com data, lugar e algo em jogo, nunca numa definição; fecha num gancho pro próximo, nunca em &ldquo;neste capítulo vimos&rdquo;), algumas das regras que saíram do piloto, pra dar o gosto:</p>
<ul>
<li>quando o resumo do capítulo e o post discordam num detalhe, vale o post;</li>
<li>o capítulo que é dono de um conceito explica direito; se o termo aparece antes na ordem de leitura, ganha meia frase de glosa e um ponteiro, nunca uma segunda explicação;</li>
<li>fato verificado na internet ganha citação mesmo que o post original não tivesse link;</li>
<li>nome de modelo e produto na grafia do fornecedor, mesmo quando o blog abreviou.</li>
</ul>
<blockquote>
  <p>É o arquivo de instruções de projeto que eu descrevo no capítulo sobre como falar com a máquina, na escala de um livro. Quem já fez um AGENTS.md pra uma base de código reconhece tudo.</p>

</blockquote>
<h2>Passo 4: rascunhos em paralelo, contradições esperadas<span class="hx:absolute hx:-mt-20" id="passo-4-rascunhos-em-paralelo-contradições-esperadas"></span>
    <a href="#passo-4-rascunhos-em-paralelo-contradi%c3%a7%c3%b5es-esperadas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com o contrato pronto, os capítulos foram rascunhados em paralelo, um agente por capítulo, em duas levas: partes I e II primeiro, depois III a V, pra que os capítulos de trás pudessem construir em cima dos da frente. Cada agente recebia o brief do seu capítulo, as análises, os posts completos, a linhagem e os arquivos dos projetos de referência que precisasse.</p>
<p>Agentes trabalhando ao mesmo tempo se contradizem, e isso aconteceu. Um capítulo dizia que o FrankMD teve uns 130 commits em três dias; outro, que teve 212 em 19 dias. Os dois números vinham dos posts e estavam certos, só que eram janelas diferentes. Um registro de integração juntou esses conflitos, e a regra virou explicitar a janela em cada capítulo.</p>
<p>Depois dos rascunhos vem a passada de integração, com outro conjunto de agentes, cada um dono de um grupo de capítulos e proibido de tocar nos outros. O trabalho deles: aplicar o registro de conflitos, cortar conceito explicado duas vezes, conferir que cada callback bate com o que o outro capítulo de fato diz, garantir que o último parágrafo de um capítulo leva pro primeiro do seguinte, e refazer a conferência de fatos assumindo que o rascunho está errado.</p>
<p>Pra ler as emendas, o build gera um arquivo de &ldquo;costuras&rdquo;: o parágrafo final de cada unidade ao lado do parágrafo inicial da próxima. É a leitura mais rápida que existe pra achar onde o livro deixa de parecer um livro só.</p>
<h2>Passo 5: uma marca invisível por parágrafo<span class="hx:absolute hx:-mt-20" id="passo-5-uma-marca-invisível-por-parágrafo"></span>
    <a href="#passo-5-uma-marca-invis%c3%advel-por-par%c3%a1grafo" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Esta é a parte que eu acho mais útil do projeto inteiro. Cada parágrafo do livro, no arquivo de origem, carrega um comentário dizendo de onde veio:</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">&lt;!-- src: 2026/09/28/llms-sao-boas-em-reproduzir-o-que-existe-e-coisas-novas --&gt;
</span></span><span class="line"><span class="cl">Todo LLM foi treinado só no que é público...
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;!-- src: original --&gt;
</span></span><span class="line"><span class="cl">Texto novo, escrito pro livro.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;!-- src: ref:ai-jail/README.md --&gt;
</span></span><span class="line"><span class="cl">Fato conferido no projeto de referência.</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 marca some na hora de montar o livro, por um filtro Lua do Pandoc, então o leitor nunca vê. Ela serve pra manutenção. Eu edito post depois de publicado o tempo todo, e o livro precisa acompanhar.</p>
<p>O comando <code>rake drift</code> refaz o inventário, compara os hashes, e lista os capítulos que dependem de um post que mudou, com o diff do post desde o commit em que o livro o leu pela última vez. A marca leva direto aos parágrafos que saíram daquele post, e só eles precisam de revisão. Resolvido, um script grava o novo hash e o commit.</p>
<p>Exemplo real, de hoje. Removi do <a href="/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/">post sobre engenharia reversa</a> um parágrafo sobre um vídeo do Linus Tech Tips. O <code>drift</code> apontou o capítulo 19 e um apêndice; o diff mostrou que aquele trecho nunca tinha sido usado no livro; dois comandos depois, os dois estavam carimbados com o hash novo. Sem a marca, eu teria que reler dois capítulos pra descobrir que não precisava mudar nada.</p>
<p>A bibliografia faz o mesmo papel pras fontes externas: cada referência anota qual post a citou. E as imagens têm cada uma a sua ficha de origem.</p>
<h2>Passo 6: o que a conferência pegou<span class="hx:absolute hx:-mt-20" id="passo-6-o-que-a-conferência-pegou"></span>
    <a href="#passo-6-o-que-a-confer%c3%aancia-pegou" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Cada número, data e nome foi conferido contra três oráculos: os posts, os projetos de referência e fontes primárias na internet, com o mesmo prompt de checagem adversarial que eu uso nos artigos do blog. A lista completa está no posfácio do livro, e boa parte das correções é do meu próprio blog:</p>
<ul>
<li>no review de março do mini-PC com Strix Halo, eu tinha separado 96 GB de memória pra GPU; em abril baixei pra 64, e posts posteriores ainda diziam 96. O livro usa 64 pra tudo depois de abril;</li>
<li>um defeito na contagem de uma das ferramentas do benchmark subestimou de 40 a 90 vezes o custo das execuções feitas nela antes de agosto. O livro não cita nenhum desses custos;</li>
<li>no texto de junho sobre o Claude Fable 5, escrevi que ele custava dez vezes mais que o Opus 4.8 por execução. Refazendo as contas em julho, com os tokens lidos do cache incluídos, o Fable custava quase o dobro. O post ainda diz dez vezes; o livro conta o número errado e a correção;</li>
<li>num texto de março, escrevi que a Anthropic tinha comprado o Bun no fim de 2024. O anúncio é de dezembro de 2025;</li>
<li>um resumo de capítulo, escrito por agente, dizia que a trava de assinatura do Claude Code caiu em menos de um dia depois do vazamento. O anúncio saiu umas 30 horas depois, e o livro diz &ldquo;no dia seguinte&rdquo;.</li>
</ul>
<blockquote>
  <p>Os rascunhos saíram rápido. A confiança no texto veio da conferência. E a fonte vence o resumo, inclusive quando o resumo vem da máquina.</p>

</blockquote>
<h2>Passo 7: capítulo com erro não passa<span class="hx:absolute hx:-mt-20" id="passo-7-capítulo-com-erro-não-passa"></span>
    <a href="#passo-7-cap%c3%adtulo-com-erro-n%c3%a3o-passa" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Por cima de tudo roda um verificador automático, o equivalente da integração contínua. São scripts em Ruby, só biblioteca padrão, encadeados num Rakefile, mais o que eles chamam por fora: o verificador de gráfico é Python porque vive dentro do matplotlib, a auditoria de página usa o poppler, e o epubcheck precisa de Java:</p>
<ul>
<li>um <strong>linter de capítulo</strong> reprova travessão, frase proibida pelo guia de voz, URL solta no meio da prosa, chave de citação desconhecida, parágrafo sem marca de origem, e avisa de parágrafo longo demais;</li>
<li>um <strong>doctor</strong> confere que toda referência cruzada, citação, termo de glossário e figura existe, que a proveniência bate com o inventário, que nenhum post anterior ao corte vazou, que as traduções estão em dia, e faz uma heurística de callback: uma frase que aponta pra outro capítulo usando nomes que aquele capítulo já não contém;</li>
<li>uma <strong>auditoria de página</strong> abre o PDF pronto e procura bloco partido entre páginas, título órfão no pé da página, página quase vazia, imagem grande demais, linha estourando a margem;</li>
<li>um <strong>verificador de gráfico</strong> procura rótulo que vazou da caixa;</li>
<li>o <strong>epubcheck</strong> valida o EPUB 3;</li>
<li>um <strong>relatório de estilo</strong> pra copidesque: trecho repetido entre capítulos (consequência inevitável de posts que repetem a mesma tese por ângulos diferentes), frase recorrente, palavra usada demais, variante de termo, comparativo mal formado em português.</li>
</ul>
<p>E os scripts têm teste. Uma suíte de quase 170 testes monta um livro de mentira num diretório temporário, com cinco posts falsos num repositório git falso, e confere que cada ferramenta pega o problema que deveria pegar. Quando um bug é corrigido, entra o teste que teria pegado ele, com um comentário dizendo qual regressão protege. Roda no CI antes de cada build.</p>
<h2>A pipeline de montagem<span class="hx:absolute hx:-mt-20" id="a-pipeline-de-montagem"></span>
    <a href="#a-pipeline-de-montagem" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A toolchain é Quarto, que embute Pandoc e Typst, fixada em versão dentro do repositório. Sem LaTeX. Um único <code>rake build</code> produz, da mesma árvore de arquivos Markdown:</p>
<table>
  <thead>
      <tr>
          <th>Saída</th>
          <th>Pra quê</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>EPUB 3</td>
          <td>Kindle, Kobo, Apple Books, qualquer leitor</td>
      </tr>
      <tr>
          <td>PDF de tela</td>
          <td>ler no computador ou tablet, com capa</td>
      </tr>
      <tr>
          <td>PDF de impressão</td>
          <td>6 por 9 polegadas, versos em branco, capítulo abrindo na direita</td>
      </tr>
      <tr>
          <td>HTML</td>
          <td>edição web</td>
      </tr>
      <tr>
          <td>DOCX</td>
          <td>pra editor que quer Word</td>
      </tr>
  </tbody>
</table>
<p>Português e inglês são perfis do Quarto, gerados do mesmo arquivo de estrutura. Citações saem de um YAML em formato CSL compartilhado pelas duas edições, com um estilo ABNT ajustado pra português. O glossário é um filtro Lua: cada termo vem marcado no fonte onde aparece, e o filtro liga a marca à entrada, expande a sigla na primeira vez e gera a página.</p>
<p>O Typst resolveu uma coisa que eu achava que ia me custar semanas: PDF com cara de livro tipografado, hifenização em português, ligaduras, sem a dor de TeX. A auditoria de página depois do build pega o que o motor deixou passar, e um <code>rake fit</code> recompila apertando capítulos que terminam numa página quase vazia.</p>
<h3>As figuras<span class="hx:absolute hx:-mt-20" id="as-figuras"></span>
    <a href="#as-figuras" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Toda figura é código ou tem ficha. Gráficos e diagramas de caixas e setas são scripts de matplotlib, 43 deles, cada um com a indicação do capítulo de onde vieram os dados, e com os rótulos em português e em inglês dentro do próprio script: o mesmo gráfico sai nas duas línguas. O caminho pra diagramas em D2 existe no repositório, e nenhuma figura do livro precisou dele.</p>
<p>As ilustrações conceituais foram geradas por IA, com o modelo, o pedido e a data de cada uma gravados na ficha de crédito, e o livro declara isso. A regra que eu me impus: nenhuma imagem de pessoa real gerada por IA. Pessoas reais só aparecem em fotos com licença livre, com o crédito na legenda.</p>
<p>A capa é o robô retrofuturista de rosto cansado, ajoelhado numa Feira Mundial dos anos 1950, estudando a própria mão enquanto a multidão assiste. O pedido ao modelo descrevia a cena pra segurar três medos de uma vez: o fim da humanidade, perder o emprego, a máquina ficando consciente. A ficha da capa guarda o prompt inteiro. O rosto é o do Marvin, o personagem da minha newsletter, e um adesivo com a minha cara fica ao lado do nome.</p>
<h2>As manhas de transformar post em livro<span class="hx:absolute hx:-mt-20" id="as-manhas-de-transformar-post-em-livro"></span>
    <a href="#as-manhas-de-transformar-post-em-livro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Transformar blog em livro é uma lista de conversões mecânicas, e cada uma tem pegadinha:</p>
<ul>
<li><strong>Link vira citação.</strong> Cada link externo virou uma entrada de bibliografia, e a frase precisa ficar de pé sem o link. Link pra outro post meu virou referência cruzada pro capítulo que agora cobre aquilo, ou sumiu. Nunca uma URL na prosa.</li>
<li><strong>&ldquo;Hoje&rdquo;, &ldquo;ontem&rdquo;, &ldquo;semana passada&rdquo;, &ldquo;atualização:&rdquo;</strong> viraram data absoluta. O livro vai ser lido em 2028 também.</li>
<li><strong>Versão de modelo, preço, nota de benchmark</strong> ficaram como fotografia datada (&ldquo;em setembro de 2026, o melhor&hellip;&rdquo;), e quando um post posterior substituiu o número, o livro conta a mudança em vez de imprimir o número velho.</li>
<li><strong>Dump de código, passo de instalação, print de terminal</strong> foram cortados. No máximo um trecho de 3 a 8 linhas quando o código é o ponto; o passo a passo foi pra apêndice.</li>
<li><strong>Título de post, que é feito pra busca</strong> (&ldquo;Vibe Code: fiz X em dois dias&rdquo;), virou título literário curto, com o assunto no subtítulo.</li>
<li><strong>Repetição entre posts</strong>, a mesma tese por muitos ângulos, é dita uma vez, no lugar certo, e os capítulos seguintes constroem em cima.</li>
<li><strong>Chamada pra ação, &ldquo;assine&rdquo;, &ldquo;comente&rdquo;</strong>, cortadas.</li>
<li><strong>A ponta afiada mira na propaganda, nunca no leitor.</strong> O post &ldquo;Você é um idiota se acredita&hellip;&rdquo; virou um capítulo sobre como os anúncios funcionam. O leitor que acreditou no anúncio é a pessoa que o livro quer ajudar.</li>
<li><strong>Todo termo técnico é explicado uma vez</strong>, na primeira aparição no livro, e vai pro glossário.</li>
</ul>
<p>E a mais importante de todas: o livro lê como história em primeira pessoa, com o ano como espinha dorsal. A informação chega quando a narrativa precisa dela, em vez de seguir ordem de enciclopédia. Isso é o oposto de como um blog é escrito, e foi a regra que mais trabalho deu pros agentes seguirem.</p>
<h2>A edição em inglês<span class="hx:absolute hx:-mt-20" id="a-edição-em-inglês"></span>
    <a href="#a-edi%c3%a7%c3%a3o-em-ingl%c3%aas" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A edição em inglês, <em>Competent Enough</em>, só começou depois que eu aprovei a em português. Cada unidade traduzida grava o hash do arquivo em português de onde saiu, e onde o livro segue um post meu que já tinha versão em inglês no blog, a tradução usa a minha própria redação em inglês em vez de traduzir de novo.</p>
<p>O <code>drift</code> também funciona por língua: se eu mudo um parágrafo em português, ele aponta a unidade em inglês que ficou velha, e só os parágrafos marcados são retraduzidos. Os gráficos com texto são gerados de novo em inglês a partir dos mesmos scripts, e a capa em inglês é composta por script em cima da mesma arte.</p>
<h2>Lançar é empurrar uma tag<span class="hx:absolute hx:-mt-20" id="lançar-é-empurrar-uma-tag"></span>
    <a href="#lan%c3%a7ar-%c3%a9-empurrar-uma-tag" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Lançamento é uma entrada no CHANGELOG e um <code>git push</code> de uma tag <code>vX.Y.Z</code>. Um workflow do GitHub Actions roda os testes, monta as duas edições em todos os formatos, valida o EPUB, roda a auditoria e publica a release com os arquivos. Ele apaga todas as releases menos as duas mais novas, e mantém as tags. É assim que a página oficial sempre aponta pra versão atual.</p>
<p>O repositório é público, e isso trouxe uma pegadinha própria: as anotações do meu servidor de casa são fonte de alguns fatos, e são privadas. Os marcadores de origem citam esses arquivos por apelido, o mapa de apelidos fica fora do git, e um hook de pré-push bloqueia qualquer arquivo ou mensagem de commit que contenha nome de repositório privado, host ou IP.</p>
<h2>O copidesque: o que a máquina de checagem deixa passar<span class="hx:absolute hx:-mt-20" id="o-copidesque-o-que-a-máquina-de-checagem-deixa-passar"></span>
    <a href="#o-copidesque-o-que-a-m%c3%a1quina-de-checagem-deixa-passar" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Lint, doctor, auditoria de página e testes pegam problema estrutural. Eles deixam passar o que um livro escrito por muitos agentes, cada um vendo poucos capítulos, sempre sofre: a mesma frase ou anedota reaproveitada em três capítulos, tom que deriva, terminologia inconsistente, concordância escorregando. Pra isso existe um procedimento de copidesque, que roda uma vez por versão, em três passos do mais barato pro mais caro.</p>
<p><strong>Passo 1, mecânico e quase de graça:</strong> o relatório de estilo que eu citei acima. Sequências de sete palavras ou mais que aparecem em mais de uma unidade, ou seja, frase reaproveitada e anedota recontada; frases feitas de quatro a seis palavras usadas em muitos capítulos, que são bonitas como callback e viram tique quando se repetem; palavra usada muito mais num capítulo do que no resto; variante de termo (&ldquo;e-mail&rdquo; e &ldquo;email&rdquo;, &ldquo;Open AI&rdquo; e &ldquo;OpenAI&rdquo;); e uma heurística de concordância em português. O relatório é uma lista de trabalho, e nada muda sozinho.</p>
<p><strong>Passo 2, a leitura corrida:</strong> um agente só de leitura por parte do livro, cada um lendo as suas unidades em ordem, do começo ao fim, com uma folha de estilo compartilhada (nomes, grafias, termos, frases recorrentes vindas do passo 1). Cada um produz um relatório ranqueado, sem editar nada: erro de digitação e gramática primeiro, depois inconsistência com a folha de estilo, repetição que o leitor notaria, trecho em que um leigo tropeçaria, e ritmo só nos piores casos. Um script valida os relatórios, confere que cada trecho citado existe mesmo na unidade, numera os itens e resume.</p>
<p>Daí vem a triagem, feita por mim com um agente editor, e a aplicação pontual de cada item aprovado, com as mesmas regras da passada de integração. Modelo mais barato faz a revisão; trecho difícil vai pro modelo forte. Orçamento aproximado: dois a três milhões de tokens por língua.</p>
<p>A regra mais importante desse passo é &ldquo;fato se confere, não se pergunta&rdquo;. Qualquer item que dependa de um número, data, nome ou comportamento de ferramenta é resolvido pelo editor contra a fonte da verdade antes de chegar em mim: primeiro o post apontado pela marca de origem do parágrafo, recalculando totais; depois o código ou os dados por trás da afirmação, nos meus repositórios; depois registro público.</p>
<p>Quando duas fontes discordam, a tarefa é descobrir por quê. &ldquo;653 commits em 8 projetos&rdquo; contra &ldquo;692 em 4&rdquo; eram dois recortes do mesmo trabalho, um deles sem o repositório privado da newsletter, e o texto passou a dizer o escopo em vez de escolher um número. Quando o post do blog está errado, o livro imprime o certo e o erro vai pra uma lista de errata do blog, pra eu corrigir na fonte.</p>
<p>Só chega em mim o que fonte nenhuma resolve: fato não publicado, intenção (&ldquo;isso é ironia?&rdquo;), preferência de corte ou de título, e contradição entre duas afirmações minhas sem registro pra desempatar. Cada pergunta vem com o que foi conferido e o que foi encontrado.</p>
<p><strong>Passo 3, a minha leitura.</strong> A última e a mais valiosa. As notas entram no mesmo ciclo de aplicar, conferir e propagar pra edição em inglês.</p>
<p>A primeira rodada desse copidesque é o que separa o preview 2 do preview 3. Entre o que ela pegou: contagem de commits agora diz o escopo, um &ldquo;mais de mil&rdquo; que na soma era 984, o exemplo de votação do Frank Investigator que não batia com o código (figura redesenhada), as citações do David Sacks e do Pirate Wires no capítulo 14 conferidas contra os originais e corrigidas, a foto do Sam Altman trocada por uma de 2025. E na edição em inglês, uma leitura própria depois da ressincronização: decalque de português reescrito, termos do glossário usados do mesmo jeito, pontuação americana de citação do começo ao fim.</p>
<h2>Leitores de mentira, feedback de verdade<span class="hx:absolute hx:-mt-20" id="leitores-de-mentira-feedback-de-verdade"></span>
    <a href="#leitores-de-mentira-feedback-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Copidesque confere o texto. Ele deixa passar a pergunta que importa num livro pra leigo: a pessoa pra quem eu escrevi consegue ler isso até o fim, entende, e sai convencida? Pra responder, a passada seguinte foi uma revisão por leitores simulados.</p>
<p>O prefácio do livro tem uma seção &ldquo;Pra quem eu escrevi&rdquo;. Dela saíram cinco personas, cada uma com nome, idade, profissão e o medo que a trouxe ao livro:</p>
<ul>
<li><strong>Marina</strong>, 38, contadora em Belo Horizonte, leu que o emprego dela &ldquo;acaba em dois anos&rdquo;, lê no celular à noite e quer saber o que fazer na segunda-feira;</li>
<li><strong>Roberto</strong>, 52, dono de loja de ferragens, o filho de 15 anos viu um vídeo sobre superinteligência e perguntou se era verdade; desconfia de hype, de &ldquo;especialista&rdquo; e de autor que se exibe;</li>
<li><strong>Carla</strong>, 44, gerente de operações numa empresa de logística, a diretoria contratou consultoria vendendo &ldquo;transformação por IA&rdquo;; quer uma decisão que ela possa tomar;</li>
<li><strong>Lucas</strong>, 24, desenvolvedor júnior há um ano, com medo de ter escolhido a carreira errada;</li>
<li><strong>Helena</strong>, 61, professora de história aposentada, sem medo nenhum, veio pelas histórias e perde a paciência com lista e manual.</li>
</ul>
<p>Um agente por persona, só de leitura, modelo mais barato, lê o livro inteiro em ordem como aquela pessoa leria. A instrução é explícita: você é leitor, editor fica de fora; erro de vírgula já foi tratado, reporte só o que muda a experiência de leitura. Cada um devolve uma nota de 1 a 5 por capítulo, um comentário na voz da persona (&ldquo;finalmente entendi por que o chatbot concorda comigo&rdquo;, &ldquo;me perdeu nas tabelas de benchmark&rdquo;), no máximo 35 anotações classificadas em confuso, chato, reescrever ou lógica, e candidatos a corte. Custa uns 350 mil tokens por persona por leitura completa.</p>
<p>Um editor consolida: a matriz de notas por capítulo e persona e o padrão por trás dela, o que os leitores adoraram (pra proteger em qualquer edição), os itens de lógica, conferidos contra o texto e os posts antes de entrar na lista e corrigidos independentemente de voto, e os itens de clareza ranqueados por quantas personas levantaram. Nota de gosto de uma persona só é descartada. Daí eu decido, e o padrão recorrente vira regra numerada num documento de regras editoriais, com a origem e como é fiscalizado.</p>
<blockquote>
  <p>A primeira leitura disse o que eu desconfiava e deu a medida: histórias e ferramentas práticas funcionam pra todo mundo; o miolo cheio de medição, os capítulos 11 a 13, perde quem não programa, com nota média entre 2,2 e 2,4. &ldquo;Afiado quando conta o que aconteceu com ele, me perde quando conta o que mediu&rdquo;, resumiu a Helena, por todos os cinco.</p>

</blockquote>
<p>E os cinco pegaram as mesmas falhas de argumento. A conclusão do capítulo 20 ia além da evidência: dizia &ldquo;os números dizem que ela não vai ficar mais inteligente&rdquo; apoiada num benchmark de programação que o próprio capítulo chama de saturado. O capítulo 9 argumentava de um jeito que nunca perde: toda falha do agente era culpa do pedido. Quatro dos cinco foram mandados pro capítulo errado pelo &ldquo;como ler&rdquo;.</p>
<p>Isso virou as primeiras regras:</p>
<ul>
<li>toda afirmação com o escopo do que foi medido;</li>
<li>nenhum argumento que não possa perder;</li>
<li>opinião marcada como opinião;</li>
<li>o lado contrário na forma mais forte antes de rebater;</li>
<li>o livro obedece às próprias lições.</li>
</ul>
<p>Uma rodada só é pouco, porque edição conserta o problema velho e cria o novo. Na segunda rodada, uma frase de desculpa colocada ao lado de uma contradição (&ldquo;eu sei que isso parece bater de frente&hellip;&rdquo;) fez os cinco leitores notarem a contradição que antes três tinham deixado passar. Virou regra: conserta o desenho, e a desculpa fica fora. Então o processo é em rodadas: ler, editar, ler de novo com as mesmas personas e leitura nova, proibidas de abrir os relatórios anteriores pra evitar ancoragem, e um script compara as rodadas.</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th style="text-align: right">rodada 1</th>
          <th style="text-align: right">rodada 2</th>
          <th style="text-align: right">rodada 3</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>nota média do livro</td>
          <td style="text-align: right">3,32</td>
          <td style="text-align: right">3,38</td>
          <td style="text-align: right">3,41</td>
      </tr>
      <tr>
          <td>anotações de lógica</td>
          <td style="text-align: right">73</td>
          <td style="text-align: right">53</td>
          <td style="text-align: right">51</td>
      </tr>
  </tbody>
</table>
<p>A nota média é ruidosa (um capítulo intocado oscila 0,6 entre leituras), então o critério de parada é outro: os problemas apontados deixaram de voltar e nada novo chega a três leitores. Na terceira rodada a curva achatou e a revisão parou ali, com uma lista de pendências pra minha própria leitura. O que mudou no caminho: uns 8 mil palavras de caderno de laboratório saíram dos capítulos pra um apêndice novo, &ldquo;Para quem programa: os bastidores&rdquo;, cada parágrafo levando a marca de origem junto; a lista de leituras virou apêndice; a seção sobre o Genesys PI foi estacionada, guardada fora do livro enquanto a verificação está pendente; o capítulo 19 ganhou o título que o argumento dele sustenta; cada capítulo abre orientando o leitor antes do primeiro jargão; e o epílogo ganhou uma lista do que fazer nesta semana, por tipo de leitor. Custo das três rodadas: uns 5 milhões de tokens de leitura mais uns 3 milhões de edição e ressincronização do inglês.</p>
<h2>O narrador ganhou passado<span class="hx:absolute hx:-mt-20" id="o-narrador-ganhou-passado"></span>
    <a href="#o-narrador-ganhou-passado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Com o argumento no lugar, faltava substância na primeira pessoa. O narrador do livro era o Akita de 2026, e eu tenho trinta anos de história que explicam por que eu penso o que penso: o estouro da bolha pontocom visto de dentro, cinco anos integrando SAP em empresa grande, os avisos sobre a bolha de contratação que eu dei em vídeo em 2019, 2020 e 2022, o &ldquo;Não terceirize suas decisões&rdquo;. Tudo isso está nas transcrições dos 149 episódios do <a href="https://www.youtube.com/@Akitando"target="_blank" rel="noopener">Akitando</a>, publicadas aqui no blog entre 2018 e 2023.</p>
<p>O processo foi o mesmo do resto do livro, em miniatura. Agentes de extração leram 23 episódios na íntegra, com um formulário: fatos biográficos com a citação literal e o que os verificaria, fatos sobre o mundo, posições recorrentes nas minhas palavras, e ganchos, ou seja, o lugar exato do livro onde uma ou duas frases dessa história sustentam uma afirmação de 2026. Daí saiu um perfil do narrador, que eu confirmei linha a linha, e um plano com uns 30 toques, a maioria de uma a três frases.</p>
<p>Antes de qualquer toque, mais de 140 afirmações tiradas dos episódios passaram pela checagem. Ela corrigiu o livro e corrigiu a mim: a bolha de contratação começou por volta de 2010, com o juro perto de zero, e o meu post de fevereiro dizia 2014; a pontocom estourou entre 2000 e 2002; as demissões de 2022 foram umas 165 mil. E achou erro em episódio antigo que o livro nem usa, que foi pra uma lista de errata do blog.</p>
<p>Os toques entraram onde a experiência vira evidência: a semana de 2001 em que a minha pontocom fechou e um mentor me chamou pra uma consultoria SAP; os anos de SAP por trás da afirmação de que o modelo é cego pras regras privadas de uma empresa; os avisos datados da bolha ao lado do erro admitido, porque em 2022 eu esperava uma segunda perna de queda em 2023 que a onda de IA cancelou. Dois apêndices novos saíram daí: &ldquo;Três bolhas&rdquo;, com os números verificados da pontocom, da bolha de juro zero e da onda de IA, e &ldquo;Como estudar com a IA do lado&rdquo;, a partir do episódio mais assistido do canal.</p>
<p>Esses episódios são fonte de fundo, e o <code>drift</code> passou a vigiá-los como vigia os posts de 2026. Depois veio uma checagem reduzida com as mesmas cinco personas, só nos capítulos tocados, com três perguntas a mais: a história do narrador deixa o argumento mais convincente, e onde? Alguma passagem parece exibição ou &ldquo;eu avisei&rdquo;? Alguma coisa sobre o narrador se contradiz?</p>
<blockquote>
  <p>Os cinco disseram sim pra credibilidade, com uma condição: a história convence quando é evidência que o leitor consegue checar, o aviso com data ao lado do erro admitido, e convence menos quando vira currículo, o que bate de frente com o próprio livro, que diz &ldquo;currículo não é argumento&rdquo;. Virou a regra 21: a história do narrador entra como evidência, e credencial fica de fora.</p>

</blockquote>
<p>Três ou quatro leitores marcaram as mesmas linhas como exibição, e elas saíram: &ldquo;Os avisos têm data&rdquo;, &ldquo;mais de trinta repositórios públicos&rdquo;, &ldquo;o mais visto do meu canal&rdquo;. As linhas de autodepreciação e os erros admitidos, todos contaram a favor. E a checagem achou contradições que eu mesmo não tinha visto: o prefácio dizia &ldquo;fim dos anos 90&rdquo; e o capítulo 18 dizia 1996; um capítulo falava em &ldquo;oito meses&rdquo; e outro em &ldquo;um ano de uso diário&rdquo;, e foram consertadas uma a uma.</p>
<h2>Acessibilidade também faz parte do build<span class="hx:absolute hx:-mt-20" id="acessibilidade-também-faz-parte-do-build"></span>
    <a href="#acessibilidade-tamb%c3%a9m-faz-parte-do-build" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Um EPUB abrir no Kindle não quer dizer que ele seja acessível. Este livro tem dezenas de diagramas, gráficos e tabelas. Sem trabalho adicional, boa parte do argumento simplesmente desaparece pra quem usa leitor de tela.</p>
<p>Por isso a pipeline agora trata acessibilidade como contrato de publicação. Cada uma das 55 figuras, nas duas línguas, ganhou um texto alternativo curto e uma descrição completa do que a imagem comunica. No HTML e no EPUB, a descrição detalhada aparece junto da figura; no PDF, entra como substituto semântico sem alterar a diagramação. Tabelas ganharam legendas, e o EPUB agora identifica no índice semântico onde começam o texto principal, o glossário, as referências e os créditos das imagens. O contrato de autoria está <a href="https://github.com/akitaonrails/ai-book-2026/blob/main/docs/ACCESSIBILITY.md"target="_blank" rel="noopener">documentado no repositório</a>.</p>
<p>Isso também não ficou dependendo da minha memória. <code>rake check</code> barra figura sem as duas descrições ou com alternativa longa demais. Cada build valida os dois EPUBs com EPUBCheck, DAISY Ace e verificadores próprios do projeto. A versão pra Kindle ainda passa por uma limpeza do CSS e dos ícones embutidos que costumam dar problema nos leitores da Amazon, mas o script se recusa a remover qualquer regra da qual o conteúdo tenha passado a depender.</p>
<p>Ferramenta automática nenhuma certifica WCAG ou garante o comportamento de todo leitor do mercado. A revisão manual continua obrigatória. A diferença é que a acessibilidade agora faz parte do build e cada mudança precisa preservar esse contrato.</p>
<h2>O que continua sendo meu<span class="hx:absolute hx:-mt-20" id="o-que-continua-sendo-meu"></span>
    <a href="#o-que-continua-sendo-meu" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O repositório registra o primeiro commit e o rascunho de todos os capítulos no mesmo dia, 13 de outubro. São os primeiros dez por cento, os que impressionam e quase não dizem nada sobre o resto. Os outros noventa por cento são leitura, corte e correção, e essa parte é minha. Nenhum capítulo entra sem eu ler e aprovar, e o que eu mando refazer volta pro mesmo processo.</p>
<p>Qualquer um dos bons modelos de outubro de 2026 teria escrito rascunhos parecidos, porque modelo virou commodity. O que fez o processo funcionar foi o que está em volta dele: o inventário, o contrato escrito, o verificador automático, as fontes como oráculo, o rastro por parágrafo. É a mesma engenharia de software velha que eu defendo em cada capítulo, aplicada a texto.</p>
<blockquote>
  <p>Os agentes foram competentes o suficiente pra escrever o rascunho. Decidir o que ele dizia continuou sendo trabalho meu.</p>

</blockquote>
<p>E, mais uma vez: fazer um livro com IA não é um prompt de &ldquo;escreva um livro pra mim&rdquo; e pronto. Eu tive que inventar o processo inteiro e pilotar o agente pra criar cada peça dele: os scripts, os testes, a consolidação dos posts, a estratégia narrativa, os objetivos, o que fica e o que sai, o copidesque, a checagem de fatos, o lançamento automatizado. Você precisa saber exatamente o que quer, e aí a IA ajuda muito. Quem não sabe o que um processo de publicação de livro envolve não vai fazer um livro de verdade, e mostrar isso era um dos objetivos que eu tinha ao criar este exemplo.</p>
<h2>Baixe, leia, e me diga onde errei<span class="hx:absolute hx:-mt-20" id="baixe-leia-e-me-diga-onde-errei"></span>
    <a href="#baixe-leia-e-me-diga-onde-errei" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>As duas edições, em três formatos cada, direto do GitHub, sem cadastro. A versão atual é a 1.0.0 final, de 17 de outubro, com tudo que este texto descreve:</p>
<table>
  <thead>
    <tr><th>Edição</th><th>EPUB (Kindle, Kobo, Apple Books)</th><th>PDF pra tela</th><th>PDF pra impressão (6 × 9 pol.)</th></tr>
  </thead>
  <tbody>
    <tr><td>Competente o Suficiente (PT-BR)</td><td><a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competente-o-Suficiente.epub">EPUB</a></td><td><a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competente-o-Suficiente-ebook.pdf">PDF</a></td><td><a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competente-o-Suficiente.pdf">PDF capa dura</a> · <a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competente-o-Suficiente-paperback.pdf">PDF brochura</a></td></tr>
    <tr><td>Competent Enough (EN)</td><td><a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competent-Enough.epub">EPUB</a></td><td><a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competent-Enough-ebook.pdf">PDF</a></td><td><a href="https://github.com/akitaonrails/ai-book-2026/releases/download/v1.0.0/Competent-Enough.pdf">PDF</a></td></tr>
  </tbody>
</table>
<p>A versão mais recente, sempre, fica na <a href="https://ailair.akitaonrails.com/pt-br/book/"target="_blank" rel="noopener">página do livro</a> e nas <a href="https://github.com/akitaonrails/ai-book-2026/releases"target="_blank" rel="noopener">releases do GitHub</a>.</p>
<p>É preview. Vai ter erro, e o processo acima existe pra que ele seja corrigido no lugar certo. Achou um, <a href="https://github.com/akitaonrails/ai-book-2026/issues"target="_blank" rel="noopener">abra uma issue</a>.</p>
]]></content:encoded><category>inteligencia-artificial</category><category>agentes-de-codigo</category><category>open-source</category></item><item><title>Engenharia Reversa com IA: a Adobe morreu?</title><link>https://akitaonrails.github.io/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/13/engenharia-reversa-com-ia-a-adobe-morreu/</guid><pubDate>Tue, 13 Oct 2026 00:00:00 -0300</pubDate><description>&lt;p&gt;O PhotoCraft existe. O REA existe. Recomp e decomp de jogo antigo saem todo dia. Logo: a Adobe morreu, nenhum binário é sagrado, e programador acabou.&lt;/p&gt;
&lt;p&gt;Essa foi a semana no X, e se você só lê manchete, é uma conclusão razoável. Este texto é pra quem quer saber o que cada uma dessas coisas faz de verdade.&lt;/p&gt;
&lt;p&gt;Explodiu no dia 9, quando a conta abaixo resumiu o que um desenvolvedor de Atlanta, Brandon Thomas, vinha publicando desde o fim de setembro: sete clones de código aberto dos produtos principais da Adobe, escritos em Rust com ajuda do Claude Opus 5.5. Photoshop, Illustrator, Premiere, Lightroom, After Effects, InDesign, Acrobat. Tudo de graça, tudo feito em &amp;ldquo;sala limpa&amp;rdquo;, sem uma linha de código da Adobe. A conta que viralizou foi esta:&lt;/p&gt;</description><content:encoded><![CDATA[<p>O PhotoCraft existe. O REA existe. Recomp e decomp de jogo antigo saem todo dia. Logo: a Adobe morreu, nenhum binário é sagrado, e programador acabou.</p>
<p>Essa foi a semana no X, e se você só lê manchete, é uma conclusão razoável. Este texto é pra quem quer saber o que cada uma dessas coisas faz de verdade.</p>
<p>Explodiu no dia 9, quando a conta abaixo resumiu o que um desenvolvedor de Atlanta, Brandon Thomas, vinha publicando desde o fim de setembro: sete clones de código aberto dos produtos principais da Adobe, escritos em Rust com ajuda do Claude Opus 5.5. Photoshop, Illustrator, Premiere, Lightroom, After Effects, InDesign, Acrobat. Tudo de graça, tudo feito em &ldquo;sala limpa&rdquo;, sem uma linha de código da Adobe. A conta que viralizou foi esta:</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">AI is now being used to recreate Adobe's software, a developer has released seven free, open-source apps as clean-room alternatives to Adobe's main tools:<br><br>- PhotoCraft → Photoshop<br>- VectorCraft → Illustrator<br>- FilmCraft → Premiere Pro<br>- LightCraft → Lightroom<br>- EffectCraft → After Effects<br>- DesignCraft → InDesign<br>- PrintCraft → Acrobat Pro<br><br>Built in Rust with help from Claude Opus 5.5, they run on Windows, Linux, and macOS under MIT/Apache licenses without using Adobe's source code.</p>&mdash; Pirat_Nation (@Pirat_Nation) <a href="https://twitter.com/Pirat_Nation/status/2108437553615425881">October 9, 2026</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>Na mesma semana, o <a href="https://github.com/morluto/rea"target="_blank" rel="noopener">REA</a>, &ldquo;Reverse Engineer Anything&rdquo;, passou de 100 mil estrelas no GitHub, ganhando dezenas de milhares por dia. A promessa da página: &ldquo;viu uma feature num app que você quer no seu produto? Peça pro seu agente investigar com o REA e construir uma versão pro seu projeto&rdquo;.</p>
<p>E quem acompanha retrogaming sabe que 2026 é o ano que não para de dar. Tem uma <a href="https://docs.google.com/spreadsheets/d/1MT_xMPlKNbyhANRUMzOH94J3ybeF0b1Ws8F0kb-_598/edit"target="_blank" rel="noopener">planilha comunitária</a> tentando listar os ports não oficiais pra PC, com uns 100 jogos de Ship of Harkinian a Skate 3, e ela está sempre atrasada. O canal <a href="https://www.youtube.com/@VideoGameEsoterica"target="_blank" rel="noopener">Video Game Esoterica</a> publica port novo quase todo dia: Time Crisis decompilado do arcade, Daytona USA, Bloodborne.</p>
<p>A versão de Xbox One do <a href="https://almost-there-games.itch.io/azimuth"target="_blank" rel="noopener">Forza Horizon 2</a>, presa lá desde 2014, roda em Windows. O <a href="https://github.com/boykopovar/AnyPS5"target="_blank" rel="noopener">AnyPS5</a> promete converter executável de PS5 em binário nativo de PC, e emuladores de PS5 como o KytyPS5 e o SharpEmu já <a href="https://www.invenglobal.com/articles/26860/astro-bot-boots-on-pc-as-ps5-emulation-makes-rapid-progress"target="_blank" rel="noopener">rodam Astro Bot</a> com gameplay de verdade. Boa parte disso com IA no meio, alguns abertamente vibe-coded.</p>
<p>Daí o consenso: programar, em geral, acabou. Todo binário é reversível e reutilizável, basta apontar o agente. Minha posição é que ficou muito mais fácil mesmo, que ainda está longe da bala de prata que as pessoas imaginam, e que o motor por trás disso, o LLM, <a href="/2026/10/12/llms-agentes-competente-o-suficiente/">parou de crescer exponencialmente</a>. O resto do texto são os motivos.</p>
<blockquote>
  <p><strong>TL;DR:</strong> engenharia reversa é uma disciplina de 40 anos com ferramenta madura, e a IA acelerou o trabalho braçal dela, com destaque pro retrô, onde o binário está disponível, é minúsculo e o alvo não se move. Os clones da Adobe são alpha com centenas de bugs abertos e print bonito. O REA é uma caixa de ferramentas que exige o binário na mão, Ghidra ou IDA instalados, e muito token. O que vale dinheiro hoje nem binário tem: é SaaS, nuvem, parceria com fabricante e décadas de ciência de cor. Reescrever o Bun, com o autor do projeto e 64 agentes, custou US$ 165 mil. Pule pra <a href="#engenharia-reversa-n%c3%a3o-nasceu-semana-passada">linha do tempo</a>, pro <a href="#o-que-o-rea-faz-de-verdade">REA</a>, pros <a href="#photocraft-print-bonito-alpha-por-dentro">clones da Adobe</a>, pro <a href="#o-caso-bun-quanto-custa-reescrever-software-grande">caso Bun</a>, por que <a href="#por-que-o-retr%c3%b4-anda-t%c3%a3o-r%c3%a1pido">o retrô anda rápido</a> ou pras <a href="#respondendo-a-lista-do-come%c3%a7o">respostas no final</a>.</p>

</blockquote>
<h2>Antes de tudo: eu não gosto da Adobe<span class="hx:absolute hx:-mt-20" id="antes-de-tudo-eu-não-gosto-da-adobe"></span>
    <a href="#antes-de-tudo-eu-n%c3%a3o-gosto-da-adobe" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra ficar claro de onde eu falo. Eu detesto as práticas comerciais da Adobe tanto quanto qualquer um. Fui cliente pagante por anos, usei Photoshop e Premiere extensivamente, mais Lightroom e After Effects, e saí pelos mesmos motivos que todo mundo sai: assinatura compulsória, cancelamento com multa, preço subindo sem contrapartida.</p>
<p>E é exatamente por ter usado a fundo que eu sei o tamanho do que está dentro daquelas caixas. Quem acha que Photoshop é camada, crop e texto nunca precisou de prova de cor pra gráfica, nunca abriu um RAW de câmera cujo perfil de cor a Adobe mantém há vinte anos, nunca fez a conformação de material de cinema, o trabalho de reunir o material bruto da câmera pra finalização. Vou chegar lá, depois do básico.</p>
<h2>Engenharia reversa não nasceu semana passada<span class="hx:absolute hx:-mt-20" id="engenharia-reversa-não-nasceu-semana-passada"></span>
    <a href="#engenharia-reversa-n%c3%a3o-nasceu-semana-passada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Todo emulador que você já usou existe porque alguém abriu hardware, desmontou binário, farejou protocolo e, em alguns casos, quebrou criptografia. A disciplina tem jurisprudência, ferramenta comercial e uma história de décadas. Uma linha do tempo resumida, com os marcos que mais importam:</p>
<ul>
<li><strong>1982 a 1984</strong>: a Compaq reescreve a BIOS do IBM PC por engenharia reversa, e em 1984 a Phoenix vende uma BIOS licenciável feita em sala limpa, com um time que só lia a especificação. Nasce a indústria de PC compatível. <a href="https://en.wikipedia.org/wiki/Phoenix_Technologies"target="_blank" rel="noopener">Fonte</a>.</li>
<li><strong>1992</strong>: Andrew Tridgell cria o Samba farejando pacotes SMB. No mesmo ano, <a href="https://www.copyright.gov/fair-use/summaries/segaenters-accolade-9thcir1992.pdf"target="_blank" rel="noopener">Sega v. Accolade</a> e <a href="https://www.copyright.gov/fair-use/summaries/atari-nintendo-fedcir1992.pdf"target="_blank" rel="noopener">Atari v. Nintendo</a> firmam nos EUA que desmontar código pra chegar em elementos funcionais é <em>fair use</em>, uso justo (a Atari perdeu o caso dela por outro motivo: mentiu pro Copyright Office pra conseguir o fonte).</li>
<li><strong>1993</strong>: começa o Wine, que vira a base do Proton e de todo jogo de Windows que roda no Steam Deck hoje.</li>
<li><strong>1999</strong>: UltraHLE roda jogos comerciais de N64 em velocidade real; o bleem! emula PS1, a Sony processa e perde as liminares, e o bleem! ainda assim fecha em 2001; o DeCSS quebra a proteção do DVD.</li>
<li><strong>2000</strong>: <a href="https://law.resource.org/pub/us/case/reporter/F3/203/203.F3d.596.99-15852.html"target="_blank" rel="noopener">Sony v. Connectix</a> decide que copiar a BIOS do PS1 pra construir um emulador é uso justo.</li>
<li><strong>2002 e 2003</strong>: nascem PCSX2 e Dolphin, os dois emuladores que vinte anos depois ainda são referência.</li>
<li><strong>2007</strong>: o iPhone é aberto onze dias depois do lançamento. A Comissão Europeia obriga a Microsoft a documentar seus protocolos de rede, e a <a href="https://fsfe.org/news/2007/news-20071220-01.html"target="_blank" rel="noopener">equipe do Samba recebe a documentação</a> por 10 mil euros.</li>
<li><strong>2010</strong>: o grupo fail0verflow mostra no 27C3 que a Sony assinou o firmware do PS3 com um nonce fixo, expondo a chave privada (o nonce é o número que deveria ser aleatório em cada assinatura). Dias depois, já em 2011, o geohot publica a chave, a Sony processa e faz acordo em março.</li>
<li><strong>2011 e 2013</strong>: nascem RPCS3 (PS3) e Xenia (Xbox 360), os dois emuladores mais difíceis já feitos, por motivos que vou explicar.</li>
<li><strong>2019</strong>: a NSA libera o <a href="https://github.com/NationalSecurityAgency/ghidra"target="_blank" rel="noopener">Ghidra</a> como código aberto. A decompilação do Super Mario 64 vaza e vira pública, e no ano seguinte aparece o port pra PC.</li>
<li><strong>2021 e 2022</strong>: Ocarina of Time 100% decompilado depois de quase dois anos de trabalho; o Ship of Harkinian vira o port de referência.</li>
<li><strong>2024</strong>: Mr-Wiseguy lança o <a href="https://github.com/Zelda64Recomp/Zelda64Recomp/releases"target="_blank" rel="noopener">Zelda 64: Recompiled</a>, a primeira vitrine da recompilação estática de N64.</li>
<li><strong>2025</strong>: Sonic Unleashed Recompiled traz o mesmo truque pro Xbox 360, convertendo PowerPC pra C++ e shaders pra HLSL.</li>
</ul>
<p>Repare que a fase atual, decomp e recomp em escala, começa em 2019, bem antes de qualquer agente de código. A IA entrou numa linha de montagem que já existia.</p>
<h3>As ferramentas<span class="hx:absolute hx:-mt-20" id="as-ferramentas"></span>
    <a href="#as-ferramentas" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O ferramental também é maduro, e boa parte é gratuita:</p>
<ul>
<li><a href="https://hex-rays.com/ida-pro"target="_blank" rel="noopener">IDA Pro</a> (Hex-Rays), o disassembler e decompilador comercial de referência, de mil a quase nove mil euros por ano, com uma versão gratuita limitada a x86.</li>
<li><a href="https://github.com/NationalSecurityAgency/ghidra"target="_blank" rel="noopener">Ghidra</a>, a resposta aberta da NSA, com decompilador e scripting, Apache 2.0.</li>
<li><a href="https://binary.ninja/"target="_blank" rel="noopener">Binary Ninja</a>, com uma edição gratuita e licenças de 200 a 3.500 dólares.</li>
<li><a href="https://github.com/radareorg/radare2"target="_blank" rel="noopener">radare2</a> e o <a href="https://github.com/rizinorg/cutter"target="_blank" rel="noopener">Cutter</a>, interface do rizin, que é um fork do radare2: a linha de comando e a interface abertas.</li>
<li><a href="https://github.com/x64dbg/x64dbg"target="_blank" rel="noopener">x64dbg</a> e <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/"target="_blank" rel="noopener">WinDbg</a> no Windows, <a href="https://www.sourceware.org/gdb/"target="_blank" rel="noopener">GDB</a> no resto.</li>
<li><a href="https://frida.re/"target="_blank" rel="noopener">Frida</a>, pra injetar código em processo vivo e observar o que ele faz.</li>
<li><a href="https://github.com/skylot/jadx"target="_blank" rel="noopener">JADX</a> pra Android e <a href="https://github.com/icsharpcode/ILSpy"target="_blank" rel="noopener">ILSpy</a> pra .NET.</li>
</ul>
<p>Guarde essa lista. O REA, que a gente vai ver daqui a pouco, é um embrulho em volta dela.</p>
<h2>Como se desmonta um binário, na prática<span class="hx:absolute hx:-mt-20" id="como-se-desmonta-um-binário-na-prática"></span>
    <a href="#como-se-desmonta-um-bin%c3%a1rio-na-pr%c3%a1tica" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Em princípio, todo binário é reversível. O processador precisa conseguir executar aquilo, então as instruções estão lá. O caminho é sempre o mesmo:</p>
<ol>
<li><strong>Desmontar</strong>: o binário vira assembly, uma instrução de máquina por linha. Isso é mecânico.</li>
<li><strong>Decompilar</strong>: o assembly vira algo parecido com C. Ghidra e IDA fazem isso razoavelmente bem, com muito ruído.</li>
<li><strong>Recuperar o que o compilador jogou fora</strong>: nome de variável, nome de função, tipos, estruturas, comentários. Nada disso sobrevive à compilação. Esse é o trabalho de verdade, e é humano.</li>
<li><strong>Refatorar</strong> até virar código que uma pessoa consegue ler, manter e portar.</li>
</ol>
<p>Os projetos de decomp de N64 fazem o passo 4 até o fim: o código em C deles, recompilado, gera byte a byte a ROM original. É assim que o Super Mario 64 virou um projeto com 135 mil linhas de C de código de jogo (uns 100 mil de código efetivo, mais meio milhão de linhas de dados de fase e modelo gerados), e o Ocarina of Time passou de 430 mil linhas. A recompilação estática pula parte disso: traduz as instruções do processador original pra C genérico, sem tentar entender, e troca só as partes que falam com o hardware. É mais rápido e o resultado é menos legível.</p>
<h3>&ldquo;Mas a IA não pode ler o binário direto?&rdquo;<span class="hx:absolute hx:-mt-20" id="mas-a-ia-não-pode-ler-o-binário-direto"></span>
    <a href="#mas-a-ia-n%c3%a3o-pode-ler-o-bin%c3%a1rio-direto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Alguém sempre pergunta: se é IA, pra que passar por C? Deixa o modelo ler o binário e construir em cima. A resposta curta é que LLM só sabe o que estava no corpus de treino, e corpus de binário pareado com código fonte é escasso, porque nenhum humano escreve assembly de produção há décadas.</p>
<p>A resposta longa é que existe literatura, e ela é instrutiva justamente por ser modesta. A linhagem vai de redes neurais de 2018 a modelos como o <a href="https://arxiv.org/abs/2305.12520"target="_blank" rel="noopener">SLaDe</a> (200 milhões de parâmetros, um por arquitetura e nível de otimização), o <a href="https://arxiv.org/abs/2403.05286"target="_blank" rel="noopener">LLM4Decompile</a> (até 33 bilhões) e o <a href="https://arxiv.org/abs/2311.13721"target="_blank" rel="noopener">Nova</a>. A Meta publicou um <a href="https://arxiv.org/abs/2407.02524"target="_blank" rel="noopener">LLM Compiler</a> que traduz assembly pra IR do LLVM. Os melhores resultados, em funções isoladas de benchmark, giram em torno de 60 a 65% de código que roda de novo e dá o mesmo resultado, caindo forte quando o compilador otimizou com -O3.</p>
<p>Dois estudos recentes mostram o estado real. O <a href="https://arxiv.org/abs/2505.11340"target="_blank" rel="noopener">DecompileBench</a>, com 23 mil funções reais, achou que decompiladores baseados em LLM produzem código mais legível e com metade da correção funcional dos decompiladores comerciais. E um <a href="https://arxiv.org/abs/2609.05370"target="_blank" rel="noopener">trabalho de setembro de 2026</a> mediu que o refinamento por LLM da saída do Ghidra sobe a taxa de compilação de 75% pra 90% e derruba a fidelidade de comportamento de 74% pra 62%, com até uns 10% das funções com vulnerabilidade conhecida deixando de reproduzir a falha, ou seja, o bug sumiu sem ninguém pedir.</p>
<blockquote>
  <p>Resumindo o que existe: modelos pequenos, função por função, quase só x86-64 Linux, e os melhores são camadas de refinamento em cima do Ghidra, o que mantém o decompilador clássico no loop. Nenhum emulador, nenhum jogo, nenhum programa inteiro foi recompilado neuralmente. Enquanto alguém não inventa isso, você trabalha com a realidade, e a realidade inclui a camada intermediária de linguagem de programação e compilador.</p>

</blockquote>
<p>Os assistentes de IA que existem pra isso em produção confirmam o desenho: o <a href="https://plugins.hex-rays.com/aidapal"target="_blank" rel="noopener">aiDAPal</a>, plugin de IDA da Atredis, o Sidekick do Binary Ninja, os servidores MCP pro Ghidra. Todos são um LLM dando nome a variável e explicando função em cima do que o decompilador tradicional produziu.</p>
<h2>O que o REA faz de verdade<span class="hx:absolute hx:-mt-20" id="o-que-o-rea-faz-de-verdade"></span>
    <a href="#o-que-o-rea-faz-de-verdade" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Eu clonei o <a href="https://github.com/morluto/rea"target="_blank" rel="noopener">REA</a> e li o código. Ele é um servidor MCP e uma CLI em TypeScript que embrulha as ferramentas da lista acima e as expõe pro seu agente de código: Ghidra, Hopper, IDA, JADX, Frida, Wakaru pra JavaScript ofuscado, Playwright pra capturar o que um site faz. O agente pede, o REA roda a ferramenta e devolve pseudocódigo, grafo de chamadas, strings e traces de IPC, cada resultado com um registro de evidência e uma lista de limitações.</p>
<p>Esse fluxo eu já tinha feito na mão, meses antes de o REA viralizar. Em julho eu <a href="/2026/07/08/fable-5-analisar-codigo-tiktok-clash-of-kings-govbr-fingerprint/">coloquei o Fable 5 pra analisar o código do TikTok, do Clash of Kings e do gov.br</a>: baixei os APKs, descompactei (APK é um zip), rodei o JADX nos <code>.dex</code> e deixei o modelo ler o Java que saiu do outro lado. Deu pra ver os endpoints, as permissões, os SDKs embutidos e como cada app monta o fingerprint do aparelho, aquele identificador persistente que sobrevive a limpar dados e reinstalar. O TikTok saiu como coleta de grau industrial, blindada contra auditoria; o gov.br, como exemplo de como fazer direito.</p>
<p>As três lições daquele post valem pro REA inteiro. Android é fácil demais de descompilar, e mesmo assim o resultado serve só pra análise estática, porque recompilar o app fica fora de alcance: os nomes vêm embaralhados pela ofuscação, faltam recursos e chaves de assinatura. A parte mais sensível mora em código nativo, <code>.so</code> em C e C++, que descompila em nada legível, e no TikTok é justamente aí que fica o miolo. E o que saiu daquele trabalho foi um relatório sobre o que o app faz, com ressalvas, e nenhum clone do TikTok. O REA embala esse mesmo fluxo num servidor MCP; as paredes continuam no mesmo lugar.</p>
<p>Quem fornece o julgamento é o LLM que você já paga. O REA fornece o encanamento determinístico e a proveniência. A frase &ldquo;automatiza as heurísticas de engenharia reversa&rdquo;, que circula por aí, não aparece em lugar nenhum do repositório; a descrição que o próprio projeto usa é engenharia reversa dirigida por agente, com evidência rastreável.</p>
<p>O que ele exige: o artefato na sua máquina (binário nativo, app Electron, assembly .NET, APK, firmware, captura de rede), Node 22.19 ou superior, e as ferramentas pesadas instaladas por sua conta. Ghidra é grátis; IDA, Hopper e JEB o README manda você comprar. Não tem chave de API do REA porque ele roda local, e o custo de token cai inteiro na sua assinatura de agente.</p>
<p>E esse custo é o detalhe que ninguém cita. O catálogo de ferramentas MCP do REA tem 2,3 MB de JSON, que entra no contexto do agente. O próprio projeto publica um <a href="https://github.com/morluto/rea/tree/main/experiments/analysis-efficiency"target="_blank" rel="noopener">experimento</a> em que analisar o SQLite compilado em WASM, no caminho padrão, levou 42 chamadas de ferramenta, cinco minutos e 300 mil tokens de entrada. Um inventário completo de um app Electron como o Obsidian devolve quase 1 MB de JSON.</p>
<p>Tem issue aberta reclamando que inspecionar um app de Mac comum devolve de 80 a 230 mil tokens de uma vez.</p>
<p>O projeto é sério e vai longe: seis meses, 2.600 commits, mil arquivos de teste, versão 6.4 lançada em 11 de outubro. Também é instável: 190 issues abertas, várias mudanças incompatíveis a cada versão menor, subsistemas marcados como experimentais.</p>
<p>Os limites estão escritos no próprio repositório. O CONTRIBUTING exclui quebra de DRM, anticheat, licença e proteção comercial. O DISCLAIMER avisa que decompilação, reconstrução e análise assistida por IA &ldquo;podem ser incompletas ou incorretas&rdquo;. E até as instruções internas pra gerar as figuras do site dizem, com todas as letras: &ldquo;nunca implique que o REA clona um programa automaticamente&rdquo; e &ldquo;nunca diga reconstrução completa, clone exato ou recuperação automática de código fonte&rdquo;.</p>
<blockquote>
  <p>O REA é uma boa caixa de ferramentas pra quem já sabe fazer engenharia reversa e quer um agente segurando o Ghidra. Ele não é o botão &ldquo;copia esse app pra mim&rdquo;. Quem escreveu o projeto é o primeiro a dizer isso.</p>

</blockquote>
<h2>PhotoCraft: print bonito, alpha por dentro<span class="hx:absolute hx:-mt-20" id="photocraft-print-bonito-alpha-por-dentro"></span>
    <a href="#photocraft-print-bonito-alpha-por-dentro" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O <a href="https://github.com/storytold/photocraft"target="_blank" rel="noopener">repositório do PhotoCraft</a> foi criado em 30 de setembro. Em duas semanas, 54 mil estrelas, 8 versões lançadas e, no momento em que escrevo, mais de 800 issues abertas. O selo no README diz &ldquo;early alpha&rdquo;, e o próprio Brandon Thomas, citado pela Gizmodo, disse que não está nem perto de pronto. A imprensa que testou chegou na mesma conclusão: a Gizmodo chamou de &ldquo;Photoshop cheio de bug, mas real&rdquo;, e o OMG Ubuntu de &ldquo;fac-símile superficial com falhas pronunciadas&rdquo;.</p>
<p>As issues contam a história técnica:</p>
<ul>
<li>Qualquer imagem acima de 8.192 pixels sai do compositor de GPU e cai pra CPU, com pico de 6 GB de memória, porque o limite de textura do egui-wgpu é esse (<a href="https://github.com/storytold/photocraft/issues/49"target="_blank" rel="noopener">issue 49</a>, já fechada, e as que vieram depois, <a href="https://github.com/storytold/photocraft/issues/1015"target="_blank" rel="noopener">1015</a> e <a href="https://github.com/storytold/photocraft/issues/4017"target="_blank" rel="noopener">4017</a>).</li>
<li>Uma foto de 12 megapixels precisa de quase 800 MB só pra abrir, ajustar e exportar pela linha de comando (<a href="https://github.com/storytold/photocraft/issues/1637"target="_blank" rel="noopener">issue 1637</a>).</li>
<li>O gerenciamento de cor é rudimentar: espaço de trabalho só global, em vez de por documento, e sem transformação de visualização (<a href="https://github.com/storytold/photocraft/issues/321"target="_blank" rel="noopener">issue 321</a>). Dezoito issues mencionam CMYK, quinze mencionam perfil ICC.</li>
<li>Quase cem issues abertas mencionam PSD. Camadas de matiz e saturação compõem diferente do Photoshop, e salvar por cima sobrescreve a imagem mesclada correta do arquivo original (<a href="https://github.com/storytold/photocraft/issues/4089"target="_blank" rel="noopener">issue 4089</a>).</li>
<li>Cinquenta issues abertas mencionam &ldquo;crash&rdquo;.</li>
</ul>
<p>A interface lembra o Photoshop, então o print é compartilhável, e quem vê o print assume que é cópia carbono. Isso é esperado numa alpha de duas semanas. Com interesse, tempo e contribuição, parte disso vai sendo resolvida. E aí os contribuidores vão descobrir uma coisa que os projetos de N64 nunca enfrentaram.</p>
<blockquote>
  <p>O PhotoCraft hoje é aquela réplica de Ferrari montada em cima de um Fusca: a carroceria tem a forma certa, você tira uma foto e a maioria das pessoas diz &ldquo;uau, uma Ferrari&rdquo;. Aí você liga e sai o motor de Fusca. Se você se empolgou com a foto, o problema foi seu.</p>

</blockquote>
<h2>O alvo se move<span class="hx:absolute hx:-mt-20" id="o-alvo-se-move"></span>
    <a href="#o-alvo-se-move" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Decompilar o Zelda de 1998 é mirar num alvo parado. A Nintendo não vai lançar o Ocarina of Time 2026 com formato de arquivo novo. Copiar um produto vivo é outra conversa, e a gente já viu esse filme.</p>
<p>Nos anos 90 e 2000 o alvo era o Microsoft Office. O StarOffice virou OpenOffice em 2000 e LibreOffice em 2010, e todos eles passaram anos correndo atrás de um formato binário, o .doc, que mudava de versão pra versão e só ganhou especificação pública em 2008, quando o jogo já tinha acabado. A parte comprovada dessa história está nos processos: a Comissão Europeia multou a Microsoft em <a href="https://ec.europa.eu/commission/presscorner/detail/en/ip_04_382"target="_blank" rel="noopener">497 milhões de euros em 2004</a> por recusar informação de interoperabilidade, e em <a href="http://news.bbc.co.uk/2/hi/business/7266629.stm"target="_blank" rel="noopener">899 milhões em 2008</a> por descumprir a ordem. O .docx só virou padrão ISO em 2008, numa <a href="https://en.wikipedia.org/wiki/Standardization_of_Office_Open_XML"target="_blank" rel="noopener">votação tão controversa</a> que países protestaram formalmente.</p>
<p>Levou mais de dez anos. E quando o formato finalmente abriu, o Office já não importava do mesmo jeito. O valor mudou de lugar: hoje são <a href="https://office365itpros.com/2024/01/31/office-365-reaches-400-million/"target="_blank" rel="noopener">400 milhões de assentos pagos de Office 365</a>, <a href="https://www.googlecloudpresscorner.com/2025-12-04-Google-Workspace-Study-Reveals-More-Than-90-of-Rising-Leaders-Want-AI-With-Personalization"target="_blank" rel="noopener">3 bilhões de usuários de Google Workspace</a> e uns <a href="https://prodesigntools.com/number-of-creative-cloud-subscribers.html"target="_blank" rel="noopener">41 milhões de assinantes de Creative Cloud</a>. Ninguém quer instalar aquilo no desktop, e por isso ninguém tem o binário.</p>
<blockquote>
  <p>Fazer engenharia reversa do Google Docs é reescrever o produto do zero olhando só pra interface, porque o código que importa nunca sai do servidor. Depois que o mundo migrou pra SaaS, a maior parte do software proprietário virou jardim murado sem binário. Sem binário, não tem o que desmontar.</p>

</blockquote>
<p>Dá pra fazer versões disso, claro. O <a href="https://immich.app/"target="_blank" rel="noopener">Immich</a> substitui o Google Photos pra uma pessoa ou uma família: backup do celular, reconhecimento de rosto, busca semântica, tudo no seu servidor. O que ele não replica é a infraestrutura e o corpus de treino que fazem a busca do Google ser a busca do Google. E a Adobe fez a mesma migração, em pedaços.</p>
<h2>Photoshop é muito mais que camada e crop<span class="hx:absolute hx:-mt-20" id="photoshop-é-muito-mais-que-camada-e-crop"></span>
    <a href="#photoshop-%c3%a9-muito-mais-que-camada-e-crop" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Aqui entra a parte que eu conheço por ter pagado. As features que vendem assinatura hoje nem rodam no seu computador.</p>
<p>O Generative Fill e o Generative Expand rodam no Firefly, na nuvem, e consomem <a href="https://helpx.adobe.com/creative-cloud/apps/generative-ai/generative-credits-faq.html"target="_blank" rel="noopener">créditos generativos</a>. Vários Neural Filters, segundo a <a href="https://helpx.adobe.com/photoshop/using/neural-filters.html"target="_blank" rel="noopener">própria documentação</a>, precisam de internet porque o processamento é no servidor da Adobe. A seleção de objeto por IA tem, desde 2020, uma opção de processar na nuvem pra refinar o resultado. Isso tudo é código que não existe na sua máquina pra ninguém desmontar.</p>
<p>Depois vem o que está no binário, mas levou décadas e parceria pra existir. O Camera Raw carrega <a href="https://helpx.adobe.com/camera-raw/desktop/dng-and-file-formats/supported-lenses.html"target="_blank" rel="noopener">perfis de correção de lente</a> e perfis de cor por câmera pra centenas de modelos, construídos ao longo de vinte anos e atualizados a cada lançamento. Clone nenhum adivinha isso. Ou tem o dado, ou não tem.</p>
<p>E tem a ciência de cor. O exemplo que eu sempre lembro é o GIMP e o CMYK.</p>
<p>O GIMP nasceu em 1995. O primeiro suporte nativo a arquivo CMYK chegou em <a href="https://www.gimp.org/news/2022/08/27/gimp-2-99-12-released/"target="_blank" rel="noopener">2022</a>, e só de importação e exportação, com o pixel sempre em RGB. O <a href="https://www.cgchannel.com/2025/03/gimp-3-0-is-out/"target="_blank" rel="noopener">GIMP 3.0, de 2025</a>, ainda sem modo de edição em CMYK. Está no <a href="https://developer.gimp.org/core/roadmap/"target="_blank" rel="noopener">roadmap</a> como trabalho em andamento.</p>
<p>Trinta anos de projeto aberto, com gente boa e motivada, e a gráfica ainda pede o Photoshop. Isso é o tamanho do buraco da ciência de cor, e nenhum agente de código inventa ciência de cor: ele reproduz o que está no treino.</p>
<p>Por que demorou tanto, se era a feature mais pedida? Porque o núcleo do GIMP nasceu com RGB de 8 bits por canal embutido em cada camada, modo de mesclagem e filtro. Suportar outro modelo de cor exigiu reescrever o motor de pixel inteiro, a migração pro GEGL, que levou a maior parte de uma década e só ficou pronta no <a href="https://www.gimp.org/release-notes/gimp-2.10.html"target="_blank" rel="noopener">GIMP 2.10, em 2018</a>. O CMYK só pôde começar depois disso.</p>
<p>E CMYK é uma família de perfis de dispositivo, com quatro tintas, perfil ICC por impressora, limite de tinta e regra de geração de preto, e editar nele significa que toda a cadeia precisa calcular num modelo subtrativo de quatro canais e ir e voltar pelos perfis sem desviar a cor. Um desenvolvedor sozinho, por melhor programador que seja, sem experiência nessa área específica, vai sofrer pra implementar isso, porque o conhecimento está na gráfica e no laboratório de cor, e quase nada dele está em corpus de treino. No GIMP, quem levou a coisa adiante foram contribuidores que entendem de impressão, e mesmo assim foram anos. Ferramenta nova nenhuma atalha esse tipo de conhecimento.</p>
<p>E tem a camada que nem é técnica: licença comercial. Cor de gráfica é Pantone, e Pantone é uma biblioteca proprietária. Projeto aberto nenhum pode embarcar as paletas; os desenvolvedores do Inkscape <a href="https://lists.inkscape.org/hyperkitty/list/inkscape-devel@lists.inkscape.org/message/DOXDMUAPHORC4HEXPF37LYBQH6SSQ2M5"target="_blank" rel="noopener">chegaram nessa conclusão em 2005</a>, e a <a href="https://www.linux.com/news/pantone-and-free-software/"target="_blank" rel="noopener">Linux.com explicou o risco jurídico</a> na mesma época. A própria Adobe sentiu isso em 2022: quando a Pantone mudou o modelo de negócio, as bibliotecas <a href="https://petapixel.com/2022/10/28/you-have-to-pay-a-subscription-to-use-pantone-colors-in-photoshop-now"target="_blank" rel="noopener">saíram do Photoshop, do Illustrator e do InDesign</a>, arquivos antigos passaram a abrir com as cores Pantone trocadas por preto, e quem precisa delas paga uma assinatura separada de plugin. Se nem a Adobe, pagando, escapou, imagine um clone que vive de boa vontade.</p>
<h3>Vídeo é pior<span class="hx:absolute hx:-mt-20" id="vídeo-é-pior"></span>
    <a href="#v%c3%addeo-%c3%a9-pior" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>No Premiere, no After Effects e no DaVinci Resolve, o problema multiplica. Câmera de cinema não entrega vídeo, entrega dado bruto numa curva logarítmica própria: LogC4 da ARRI, IPP2 da RED. Pra transformar aquilo em imagem, você precisa das transformadas do fabricante, e a RED entrega as dela num <a href="https://community.acescentral.com/t/red-ipp2-color-workflows/1055"target="_blank" rel="noopener">SDK</a> que os editores integram e atualizam a cada versão. O ACES, o padrão neutro da indústria, depende de transformadas de entrada fornecidas por cada fabricante.</p>
<p>A Blackmagic dá o Resolve de graça e ele decodifica ARRIRAW e R3D. Faz isso porque paga o SDK e a parceria por você. Um clone feito por agente, &ldquo;adivinhando&rdquo; como a imagem da Alexa deve ficar, produz uma imagem que não bate com o monitor de referência, e no cinema isso significa que não serve. Esse conhecimento não está em corpus de treino nenhum: está em contrato.</p>
<h2>O caso Bun: quanto custa reescrever software grande<span class="hx:absolute hx:-mt-20" id="o-caso-bun-quanto-custa-reescrever-software-grande"></span>
    <a href="#o-caso-bun-quanto-custa-reescrever-software-grande" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra ter noção de ordem de grandeza, o melhor dado público que existe é o do Bun. Em maio de 2026, Jarred Sumner reescreveu o runtime inteiro de Zig pra Rust usando agentes, e em julho <a href="https://bun.com/blog/bun-in-rust"target="_blank" rel="noopener">publicou os números</a>:</p>
<ul>
<li>535 mil linhas de Zig em 1.448 arquivos viraram um diff de um milhão de linhas de Rust, em 11 dias.</li>
<li>Até 64 instâncias de Claude em paralelo, 24 horas por dia, cada linha revisada por dois agentes adversariais e um corretor.</li>
<li>5,9 bilhões de tokens de entrada não cacheados, 690 milhões de saída, 72 bilhões de leituras de cache: <strong>uns US$ 165 mil</strong> a preço de API.</li>
<li>16 mil erros de compilador corrigidos por 64 agentes antes do CI ficar verde em seis plataformas.</li>
<li>A suíte de testes existente, com mais de um milhão de asserções, passou 100% antes do merge.</li>
</ul>
<p>Lê de novo o que viabilizou isso:</p>
<ul>
<li>o autor original do projeto pilotando</li>
<li>um código fonte completo e funcional como referência</li>
<li>uma suíte de testes de um milhão de asserções servindo de oráculo</li>
<li>orçamento ilimitado, porque o Bun pertence à Anthropic desde dezembro de 2025, e uma versão pré-lançamento do Claude Fable 5</li>
</ul>
<p>O Sumner estima que três engenheiros com contexto total levariam um ano.</p>
<p>Agora tira cada uma dessas vantagens. O PhotoCraft não tem o fonte do Photoshop pra portar, tem que inventar. Não tem suíte de testes que diga &ldquo;isso aqui bate com o Photoshop&rdquo;. Não tem o autor do Photoshop.</p>
<p>E o Photoshop é um produto de 36 anos, cujos requisitos de disco a Adobe lista entre <a href="https://helpx.adobe.com/photoshop/desktop/get-started/technical-requirements-installation/adobe-photoshop-on-desktop-technical-requirements.html"target="_blank" rel="noopener">10 e 100 GB</a>, contra um runtime de JavaScript. Mesmo sem saber o tamanho do código da Adobe, estamos falando de um alvo maior, sem referência, sem oráculo e sem dono. Ordens de grandeza acima de US$ 165 mil, e ainda assim seria uma aproximação.</p>
<h2>Por que o retrô anda tão rápido<span class="hx:absolute hx:-mt-20" id="por-que-o-retrô-anda-tão-rápido"></span>
    <a href="#por-que-o-retr%c3%b4-anda-t%c3%a3o-r%c3%a1pido" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se é tão difícil, por que sai port de jogo antigo todo dia? Porque o retrô acumula todas as vantagens que o resto não tem.</p>
<p><strong>O binário está disponível e é minúsculo.</strong> Super Mario Bros. do NES: 40 KB. Super Mario 64: 8 MB. Ocarina of Time e Majora&rsquo;s Mask: 32 MB cada. Um CD inteiro de PS1: 650 MB.</p>
<p>Compare com <a href="https://store.steampowered.com/app/1086940/Baldurs_Gate_3/"target="_blank" rel="noopener">Baldur&rsquo;s Gate 3</a>, 150 GB, ou com o <a href="https://support.activision.com/black-ops-6/articles/pc-system-requirements-for-black-ops-6"target="_blank" rel="noopener">Call of Duty mais recente</a>, acima de 100 GB. São três ou quatro ordens de grandeza, e o escopo encolhe junto: um jogo de 1996 tem um punhado de sistemas, não um motor de física com rede, loja e anticheat.</p>
<p><strong>A tecnologia é antiga e documentada.</strong> Vinte e cinco anos de comunidade já mapearam o hardware do N64, do PS1 e do SNES instrução por instrução. O agente não descobre nada; ele acelera um trabalho com mapa pronto. É o mesmo padrão que eu <a href="/2026/09/28/llms-sao-boas-em-reproduzir-o-que-existe-e-coisas-novas/">vi nos meus projetos de retrocomputação</a>: onde tem referência e oráculo, a IA voa; onde não tem, ela gira em força bruta.</p>
<p><strong>O alvo não se move.</strong> Ninguém vai atualizar o Zelda de 1998.</p>
<p><strong>E o jurídico está resolvido há trinta anos.</strong> Sega v. Accolade e Sony v. Connectix deram o contorno. Os ports exigem que você forneça a sua ROM.</p>
<h3>Console moderno é um PC<span class="hx:absolute hx:-mt-20" id="console-moderno-é-um-pc"></span>
    <a href="#console-moderno-%c3%a9-um-pc" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A segunda metade da explosão, os ports de PS4, PS5, Xbox One e Switch, tem outra explicação: esses consoles são PCs ou celulares com outro invólucro. O <a href="https://www.playstation.com/en-us/ps4/tech-specs/"target="_blank" rel="noopener">PS4</a> usa um AMD Jaguar x86-64, o <a href="https://blog.playstation.com/2020/03/18/unveiling-new-details-of-playstation-5-hardware-technical-specs/"target="_blank" rel="noopener">PS5</a> um Zen 2, o <a href="https://news.xbox.com/en-us/2020/03/16/xbox-series-x-tech/"target="_blank" rel="noopener">Xbox Series</a> outro Zen 2, o Switch um <a href="https://www.techinsights.com/blog/nintendo-switch-teardown"target="_blank" rel="noopener">Tegra X1</a> ARM da NVIDIA. O processador do seu PC roda aquele código nativamente. Então ninguém precisa emular a CPU.</p>
<p>O trabalho que sobra é traduzir o sistema operacional e a GPU. O <a href="https://github.com/shadps4-emu/shadPS4"target="_blank" rel="noopener">shadPS4</a> reimplementa as bibliotecas do sistema do PS4 e recompila os shaders: os jogos saem da fábrica com código de máquina da GPU AMD, que o emulador decodifica instrução a instrução e converte pra SPIR-V, o formato do Vulkan.</p>
<p>O <a href="https://almost-there-games.itch.io/azimuth"target="_blank" rel="noopener">Azimuth</a>, que faz o Forza Horizon 2 rodar no Windows, traduz chamadas WinRT do Xbox pra Win32 e DirectX 11.X pra DirectX 12 desktop, com o código x86 do jogo rodando direto. O AnyPS5 religa o executável: pega o binário do PS5 e gera um binário nativo de Linux ou Windows, ligado a bibliotecas de sistema reimplementadas. Nos três casos, Proton, DXVK, VKD3D e MoltenVK já tinham resolvido a camada de tradução gráfica anos antes.</p>
<p>A prova inversa são os consoles que não eram PC. O PS3 usa o Cell, um PowerPC com sete coprocessadores ativos (os SPEs), cada um com memória local própria e sem cache, e o RPCS3 está nisso <a href="https://rpcs3.net/about"target="_blank" rel="noopener">desde 2011</a>, com a emulação dos SPUs sendo gargalo até hoje. O Xbox 360 é PowerPC também, e por isso o Sonic Unleashed precisou do XenonRecomp traduzir PowerPC pra C++. Engenharia reversa é caso a caso, e a arquitetura decide se o caso é fácil.</p>
<h3>A IA está no meio, e já tem briga<span class="hx:absolute hx:-mt-20" id="a-ia-está-no-meio-e-já-tem-briga"></span>
    <a href="#a-ia-est%c3%a1-no-meio-e-j%c3%a1-tem-briga" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Que os ports de 2026 usam IA, boa parte deles diz na cara. O <a href="https://github.com/SlickAmogus/silent-hill-decomp"target="_blank" rel="noopener">decomp do Silent Hill</a> escreve no README que o desenvolvimento é &ldquo;fortemente assistido por IA (Claude Opus 4.6, 4.7 e o Fable mais novo), e somos abertos sobre isso. Não é tradução automatizada do jogo&rdquo;. O recomp do Pokémon Stadium <a href="https://retrododo.com/a-pokemon-stadium-recomp-has-finally-arrived-but-lists-claude-as-a-contributor/"target="_blank" rel="noopener">lista o Claude como contribuidor</a>.</p>
<p>Os devs dos recomps de Zelda e Banjo, que passaram anos fazendo isso à mão, <a href="https://kotaku.com/zelda-and-banjo-recompiled-devs-go-to-war-with-ai-vibe-coded-slopcomps-2000739936"target="_blank" rel="noopener">entraram em guerra</a> com o que chamam de &ldquo;slopcomps&rdquo;, ports gerados por agente com qualidade de alpha.</p>
<p>E vale separar manchete de estado real também aqui. O AnyPS5 tem uma versão 0.1 de setembro que, segundo quem testou, roda um jogo indie 2D; a alegação viral de que &ldquo;o PS5 está 80% portado pra PC&rdquo; é dos fãs, e nenhum jogo 3D roda. Astro Bot roda, só que em emuladores (KytyPS5, SharpEmu), que fazem o caminho tradicional. O Forza Horizon 2 roda, e o Azimuth é um binário fechado de um estúdio que se declara &ldquo;AI assisted&rdquo; sem dizer mais nada.</p>
<h2>&ldquo;Mas daqui a dois anos o token custa nada&rdquo;<span class="hx:absolute hx:-mt-20" id="mas-daqui-a-dois-anos-o-token-custa-nada"></span>
    <a href="#mas-daqui-a-dois-anos-o-token-custa-nada" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>A resposta padrão pra tudo isso é: hoje é difícil, em dois anos é trivial, porque IA cresce exponencialmente. Os números dizem outra coisa.</p>
<p>O preço da ponta mais cara caiu pouco. O GPT-4 de março de 2023 custava US$ 30 e 60 por milhão de tokens de entrada e saída; o Claude 3 Opus de 2024, 15 e 75; os modelos de ponta de hoje, Fable 5.1 e GPT-6 Astra, <a href="https://claude.com/pricing"target="_blank" rel="noopener">custam</a> <a href="https://developers.openai.com/api/docs/pricing"target="_blank" rel="noopener">10 e 50</a>. Em três anos e meio, a fronteira ficou entre 1,2 e 3 vezes mais barata, dependendo de qual ponta você olha.</p>
<p>O que caiu de verdade foi o custo de uma qualidade fixa: a Epoch AI mediu quedas <a href="https://epoch.ai/blog/llm-inference-price-trends"target="_blank" rel="noopener">de 9 a 900 vezes por ano</a> dependendo da tarefa, e numa <a href="https://epoch.ai/gradient-updates/how-persistent-is-the-inference-cost-burden"target="_blank" rel="noopener">revisão de 2026</a> resume em &ldquo;muito grosseiramente 5 a 10 vezes por ano&rdquo;, com o aviso de que pode desacelerar. Isso é real e é ótimo.</p>
<p>Só que aplica a conta no Bun. US$ 165 mil divididos por 10 dá US$ 16 mil. Por 100, US$ 1.650. Isso é pra portar um runtime com fonte, testes e autor na mão.</p>
<p>Pra um amador tentando clonar o Photoshop sem nada disso, a conta começa ordens de grandeza acima e cai na mesma proporção. Barato não é o mesmo que trivial.</p>
<p>E a premissa do &ldquo;exponencial&rdquo; morreu em 2024. O Ilya Sutskever disse no NeurIPS que <a href="https://www.globalbankingandfinance.com/ai-with-reasoning-power-will-be-less-predictable-ilya-sutskever-says/"target="_blank" rel="noopener">o pré-treino como conhecemos vai acabar</a>, porque só existe uma internet. A Epoch projeta que o escalonamento dos modelos de raciocínio <a href="https://epoch.ai/gradient-updates/how-far-can-reasoning-models-scale"target="_blank" rel="noopener">converge pra taxa geral da fronteira</a> em cerca de um ano.</p>
<p>Eu já escrevi sobre isso três vezes nas últimas semanas: <a href="/2026/09/28/llms-sao-boas-em-reproduzir-o-que-existe-e-coisas-novas/">os limites</a> do que o modelo reproduz, <a href="/2026/10/12/llms-agentes-competente-o-suficiente/">a convergência</a> dos modelos crus com o ganho vindo do harness, e <a href="/2026/10/11/existem-linguagens-melhores-pra-programar-com-llms/">o benchmark de linguagens</a> em que os modelos de fronteira empataram no teto. A ferramenta que temos é a ferramenta que vamos ter por um bom tempo, com melhoria incremental. Planeje com ela.</p>
<p>Vale lembrar o número que abre o texto dos limites: pelo <a href="https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/"target="_blank" rel="noopener">Octoverse 2025</a>, relatório oficial do GitHub, 81,5% das contribuições na plataforma aconteceram em repositórios privados. É o código que importa de verdade, o que paga salário, e nenhum LLM viu uma linha dele. O modelo reproduz o que estava no treino, e esse código nunca esteve lá.</p>
<p>Agora estenda isso pra todo produto SaaS e pra cada componente que ele usa. O código da Adobe que realmente vale, o motor de cor, os perfis, os filtros na nuvem, não está disponível em lugar nenhum, nem como binário. Quem quiser reproduzir vai ter que refazer a pesquisa e o desenvolvimento, caro e demorado, pelos mesmos anos que a Adobe levou. E no fim ainda esbarra na propriedade intelectual licenciada de terceiros, Pantone, SDK de câmera, perfil de fabricante, que você nunca vai conseguir ter.</p>
<h2>Respondendo a lista do começo<span class="hx:absolute hx:-mt-20" id="respondendo-a-lista-do-começo"></span>
    <a href="#respondendo-a-lista-do-come%c3%a7o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p><strong>O PhotoCraft existe?</strong> Existe, é uma alpha de duas semanas com mais de 800 issues abertas, gerenciamento de cor rudimentar, travando acima de 8K e abrindo PSD errado. Parece Photoshop no print. Pode melhorar muito, e vai descobrir que o alvo se move e que as partes caras do Photoshop estão na nuvem ou em contrato com fabricante.</p>
<p><strong>O REA existe?</strong> Existe, é bom, e é uma caixa de ferramentas pra quem já sabe usar Ghidra, exige o binário na mão, as ferramentas instaladas e centenas de milhares de tokens por análise. O próprio projeto evita dizer que ele clona programa.</p>
<p><strong>Recomp e decomp aceleraram?</strong> Sim, de verdade, e a IA tem parte nisso. Aceleraram onde o binário existe, é pequeno, está documentado há 25 anos e não se move. Ou onde o console é um PC disfarçado e o trabalho é tradução de API, resolvida pelo Wine e pelo Vulkan anos antes.</p>
<p><strong>A Adobe morreu?</strong> A Adobe tem uns 40 milhões de assinantes, pela estimativa mais citada, as features que vendem assinatura rodam no servidor dela, e trinta anos de GIMP ainda deixam a gráfica pedindo Photoshop. Eu queria que fosse diferente. Os dados dizem que o que vai acontecer é o que sempre aconteceu com clone de produto vivo: anos correndo atrás, e quando alcança, o valor já mudou de lugar.</p>
<p><strong>Programador morreu?</strong> Reescrever o Bun precisou do autor do Bun. Os decomps que funcionam têm gente que conhece o hardware pilotando o agente, e os que não têm viraram &ldquo;slopcomp&rdquo; com nome e tudo. A engenharia reversa ficou mais barata e mais acessível, como ficou com o Ghidra em 2019, e continua sendo trabalho de engenheiro. Sem nós, a ferramenta devolve print bonito.</p>
<p>Software proprietário com binário na mão vai sofrer pressão, e deve. A maior parte do software que você usa não tem binário nenhum, e o que tem já depende de serviço na nuvem que não desmonta. Engenharia de software com IA tem um caminho ótimo pela frente. Manchete de &ldquo;acabou&rdquo; rende clique e não compila.</p>
]]></content:encoded><category>inteligencia-artificial</category><category>vibe-coding</category><category>retrocomputacao</category><category>open-source</category></item></channel></rss>