<?xml version="1.0" encoding="UTF-8" standalone="no"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US">
  <id>tag:www.akitaonrails.com,2008:/posts</id>
  <link href="https://www.akitaonrails.com/" rel="alternate" type="text/html"/>
  <link href="https://www.akitaonrails.com/posts.atom" rel="self" type="application/atom+xml"/>
  <title>AkitaOnRails.com</title>
  <updated>2025-05-21T18:17:24-03:00</updated>
  <generator uri="https://www.akitaonrails.com">AkitaOnRails</generator>
  <author>
    <name>Fabio Akita</name>
    <email>boss@akitaonrails.com</email>
  </author>
  <xhtml:meta content="noindex" name="robots" xmlns:xhtml="http://www.w3.org/1999/xhtml"/><entry>
    <id>tag:www.akitaonrails.com,2008:Post/6254</id>
    <published>2025-05-21T17:15:00-03:00</published>
    <updated>2025-05-21T18:17:24-03:00</updated>
    <link href="/2025/05/21/seu-windows-pode-estar-capado-sem-voce-saber-cheque-isto" rel="alternate" type="text/html"/>
    <title>Seu Windows pode estar Capado sem Você Saber. Cheque isto!!</title>
    <content type="html">&lt;p&gt;Eu normalmente não ligo de documentar sobre problemas bobos de Windows, mas este em particular me deixou irritado, então vou relatar a sequência de eventos. Mas já deixo o spoiler do final: abra Control Panel e Power Options no seu Windows e cheque se ele não está em "Power saver" mode. Tire desse lixo, deixe no mínimo em Balanced, mas se for PC Desktop suba pra "High Performance" ou "Ultimate Performance". Depois me agradeça.&lt;/p&gt;

&lt;p&gt;Agora senta que lá vem história.&lt;/p&gt;

&lt;p&gt;Eu montei um PC até que razoável pra minha namorada: Intel i7 12th Gen de 8 cores, 32GB DDR4-2400, NVIDIA RTX 3090, placa-mãe MSI Edge z790, NVME e até coloquei um NAS Synology DS1621+ em rede 2.5Gbps - porque ela é criadora de conteúdo e tem MUITO video pra editar. Modéstia a parte, ficou bonito:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdU1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--1bedd092d0d861b18756ab7de1394916c4265914/.org.chromium.Chromium.p7bYgh?disposition=attachment&amp;amp;locale=en" alt="pc branco" /&gt;&lt;/p&gt;

&lt;p&gt;Tudo funcionou muito bem por bastante tempo, mas eventualmente, sem nenhum motivo aparente, tudo começou a parecer meio lento. Não totalmente lento, mas às vezes páginas web pareciam engasgar pra carregar. Clicava no menu Start do Windows e ele parecia dar umas travadas. Às vezes o Explorer travava e precisava ir no Task Manager e dar restart no processo manualmente. Várias coisas super inconvenientes mas que não dava nenhum "erro" explícito aparente. Só parecia "estranho".&lt;/p&gt;

&lt;p&gt;Eu sou usuário de Linux. Meu PC principal é Manjaro Linux. Só uso Windows nos meus mini-PC de games, exclusivamente pra rodar Steam e alguns emuladores. Eu evito ao máximo instalar qualquer coisa neles. Porque sei como é um saco diagnosticar Windows depois que algum programa estranho invade.&lt;/p&gt;

&lt;p&gt;Fui olhar os suspeitos de sempre. Ela tinha instalado jogos pra fazer streaming, tipo Valorant, Genshin Impact e coisas assim. Eu sempre suspeito de anti-cheats, as porcarias do Riot Vanguard ou Easy ou VAC. Comecei desinstalando todos os games. Mas eu sempre acho que anti-cheat é que nem malware: uma vez instalado, nunca mais vai sair do sistema. Desinstalei tudo, mas continuou essa sensação de meio travando, meio lento, às vezes, intermitente.&lt;/p&gt;

&lt;p&gt;Tentei os próximos suspeitos: software de periféricos, como os da Elgato, Logitech, Razr que instalam um monte de porcaria nas máquinas, e recentemente estava lendo sobre o malware que vinha em todos eles. Assistam este video do GamerNexus pra entender a casa de cartas que é toda a indústria de periféricos com RGB:&lt;/p&gt;

&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/H_O5JtBqODA?si=8yEqSzgtyRg7dqTC" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Mesmo desinstalando todos, ainda mesmo comportamento: meio lento, meio travando, instável.&lt;/p&gt;

&lt;p&gt;Vamos tentar ir mais a fundo, pra isso tem que instalar ferramentas como da &lt;a href="https://learn.microsoft.com/en-us/sysinternals/downloads/autoruns"&gt;Sysinternals Autoruns&lt;/a&gt; pra ver se tem alguma coisa iniciando (coisas escondidas no Registry por exemplo). Tentei desligar tudo que eu achava suspeito.&lt;/p&gt;

&lt;p&gt;Nada. A essa altura também já tinha garantido que firmware de BIOS mais nova da MSI estava instalado. Chequei se nada tinha desligado na BIOS, XMP profile tava ok, nada de fast-boot e coisas assim.&lt;/p&gt;

&lt;h3&gt;Será a Internet?&lt;/h3&gt;

&lt;p&gt;Como o uso maior é internet e ela parecia "lenta", pensei que poderia ser problemas de rede. Roteador da Vivo? Plano? Wifi com interferência?&lt;/p&gt;

&lt;p&gt;Ficando sem paciência resolvi ir nuclear na idéia de internet. De fato, abrindo a página da &lt;a href="https://speed.cloudflare.com/"&gt;Cloudflare Speed Test&lt;/a&gt; eu via que a velocidade em si estava ok, mas a latência estava MUITO alta, jitter alto e, principalmente PACKET LOSS estava em mais de 15%. Não é normal isso, tem que ser 0%.&lt;/p&gt;

&lt;p&gt;Aproveitei que fui pra Miami e já trouxe um roteador TP-LINK BE9300 pra Wi-fi 7, comprei uma placa PCIe de Wi-fi 7 compatível também. Desliguei o roteador de Wi-fi da Vivo e mandei subir o plano pra 800Mbps. E de fato, melhorou muito a internet:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdDBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ac81db6ccb3cff3c75e2014362fbed791e9b1c81/20250521_151510.jpg?disposition=attachment&amp;amp;locale=en" alt="vivo internet" /&gt;&lt;/p&gt;

&lt;p&gt;Latência caiu, jitter caiu, packet loss foi pra 0%.&lt;/p&gt;

&lt;p&gt;Mas isso não resolveu o problema. Do nada o navegador dava umas pausas na hora de carregar alguma página. Aquela sensação meio de lento, meio travando. E sim, tentei trocar de Edge pra Chrome, pra Firefox, pra tudo. Nada fazia diferença. A internet ficou mais rápida mesmo, mas o Windows em si não pareceu que melhorou muito e navegando, começava a ficar mais e mais lento. Então o problema não era internet.&lt;/p&gt;

&lt;p&gt;Estava ficando sem opções. Tudo que poderia ser manualmente limpo no Windows, eu limpei. Já não tinha quase mais nada pra apagar - e eu apago as coisas sem dó. Mas eu sempre fico na cabeça que é algum malware, alguma coisa que carrega antes do boot, algum "ROOTKIT". Anti-cheats são Rootkits por definição. Eles carregam antes até da kernel e a partir desse ponto, o sistema comprometido não pode ser mais confiado, porque nenhuma ferramenta vai conseguir achar ele.&lt;/p&gt;

&lt;p&gt;Assumindo que nesse ponto deve ser um sistema comprometido, resolvi ir na opção nuclear: reinstalar tudo do zero. Fizemos backup dos dados e lá fui eu reinstalar do zero.&lt;/p&gt;

&lt;h3&gt;Reinstalar NÃO FUNCIONOU&lt;/h3&gt;

&lt;p&gt;Agora ficou pior.&lt;/p&gt;

&lt;p&gt;Com o Windows 11 Pro, recém-instalado, garantidamente sem nenhum software de terceiros instalado, ainda assim estava com o mesmo comportamento de meio lento, meio travando. O menu de start não me deixava clicar em nada. Precisava abrir o task manager e matar o Explorer manualmente pra fazer ele voltar a funcionar.&lt;/p&gt;

&lt;p&gt;Fiquei um tempo olhando pro Task Manager, mas não tinha nenhum processo rodando que eu poderia suspeitar mais: era Windows bare-bones. Eu até tinha rodado um &lt;a href="https://github.com/Raphire/Win11Debloat"&gt;Debloater&lt;/a&gt; pra desinstalar os apps desnecessários da Microsoft como Teams.&lt;/p&gt;

&lt;p&gt;Mas eis que, de tanto olhar o task manager, eu vi que estava ignorando um dado importante. Veja se acham:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdHdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0d97b6094832390db47bb76b0e8424dabab078d1/20250521_151208.jpg?disposition=attachment&amp;amp;locale=en" alt="cpu low" /&gt;&lt;/p&gt;

&lt;p&gt;A imagem tá meio zoada porque eu tirei foto do monitor com meu celular. O refresh rate conflita com da câmera e fica essa "grade", mas acho que dá pra ler.&lt;/p&gt;

&lt;p&gt;Mesmo quando você não tem quase nenhum programa rodando, não é natural a CPU estar com clock tão baixo assim, 0.50Ghz - MEIO GIGAHERTZ. Esse Intel i7 é capaz de ir acima de 4Ghz e o clock-base é de 2Ghz.&lt;/p&gt;

&lt;p&gt;Abri navegadores, abri abas, fiquei monitorando, e nunca subia pra mais de 1.5Ghz. Um ocasional pico de 2Ghz por 1 segundo e depois caía pra abaixo de 1Ghz de novo. Daí pensei: será que o AIO está falhando??&lt;/p&gt;

&lt;p&gt;Olhando o Event Viewer dava pra ver quando o menu Start (que faz parte do Explorer.exe) parava de funcionar e travava: ele registra no log como "Hanging Events" código 1002.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdDRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--e5a50286d2f937e4998e422cde3ed16106196edd/20250521_135224.jpg?disposition=attachment&amp;amp;locale=en" alt="event viewer" /&gt;&lt;/p&gt;

&lt;p&gt;Tá vendo esse monte de Error? Tudo Hanging Events, e "hanging" é justamente o comportamento: alguma coisa parece "segurar", "travar" e só matando o processo pra ele voltar a funcionar.&lt;/p&gt;

&lt;p&gt;E isso acontecia antes e também DEPOIS de resinstalar tudo do zero. Alguma coisa parecia fundamentalmente errada no hardware em si. Com o clock super baixo, esse comportamento começa a fazer sentido: está tão lento que ele não está conseguindo processar coisas básicas. Clique várias vezes no Start Menu (que vai ficar fuçando documentos recentes, atualizando lista de aplicativos, abrindo search bar, etc) e não tem clock pra processar isso enquanto ainda tem Windows Defender em background, abas de navegador processando.&lt;/p&gt;

&lt;h3&gt;Será Thermal Throttle??&lt;/h3&gt;

&lt;p&gt;Como o Windows está zerado, não pode mais ser anti-cheat, não pode ser anti-virus esquisito, não pode ser malware, não é a internet, é alguma coisa FORA, era o que fazia sentido.&lt;/p&gt;

&lt;p&gt;Abri o PC e deixei aberto pra poder sentir se o dissipador estava ficando absurdamente quente. Se estiver, iria significar que meu AIO (Watercooler da Coolermaster) começou a falhar.&lt;/p&gt;

&lt;p&gt;É normal quando a CPU super-aquece, ele derrubar o clock pra evitar queimar o chip. Isso se chama "THEMAL THROTTLING".&lt;/p&gt;

&lt;p&gt;E de novo esquisito: estava FRIO ao toque!!!&lt;/p&gt;

&lt;p&gt;Não é possível. Abri a BIOS pra re-checar perfis de ventoinha, mas tudo normal:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdDhCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d926474dc4d8be728023355a9754fe045c35b545/20250521_143025.jpg?disposition=attachment&amp;amp;locale=en" alt="cpu fans" /&gt;&lt;/p&gt;

&lt;p&gt;Eis que finalmente me ocorreu:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;**DEIXA EU CHECAR CONFIGURAÇÃO DE POWER**
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Num PC/notebook normal, deveria aparecer algo parecido com isto:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdUVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--50f2ae1a28edf42c948a1c86aadfe95aae87c285/Change-power-mode-via-Windows-11-settings-696x443-1063343334.jpg?disposition=attachment&amp;amp;locale=en" alt="Power Settings" /&gt;&lt;/p&gt;

&lt;p&gt;Por default, Windows sempre sobe em "Balanced Mode".&lt;/p&gt;

&lt;p&gt;Mas no PC da minha namorada, &lt;strong&gt;não sei por que&lt;/strong&gt;, não tinha opção de Power Mode!!! E eu estou num Windows 11 pro com licença paga!!&lt;/p&gt;

&lt;p&gt;Isso é esquisito, mas eu sei que no antigo Control Panel, tem opção de Power Options:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdUFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2e96c0148b82c5328c9d9a7b2731d07bd15ee39f/20250521_151203.jpg?disposition=attachment&amp;amp;locale=en" alt="control panel" /&gt;&lt;/p&gt;

&lt;p&gt;E EIS O PROBLEMA:&lt;/p&gt;

&lt;p&gt;Estava em "Power saver"!!!&lt;/p&gt;

&lt;p&gt;Como isso aconteceu? Não tenho a mínima idéia. LEMBREM-SE: eu re-instalei o Windows do zero. Isso é logo depois de reinstalar!!&lt;/p&gt;

&lt;p&gt;Em notebook faz sentido ter esse perfil, pra economizar bateria quando você não tem tomada. Mas em PC desktop, como é meu caso, não faz nenhum sentido!! Mudei pra "Ultimate Performance" e olha o task manager agora:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBdUlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ba5db99b8500c26a49b8f6ce126badf175d05ddd/20250521_151130.jpg?disposition=attachment&amp;amp;locale=en" alt="cpu high" /&gt;&lt;/p&gt;

&lt;p&gt;AGORA SIM!! Acima de 4Ghz!!!&lt;/p&gt;

&lt;p&gt;O NORMAL desta CPU é estar na faixa de 4Ghz, com Turbo Boost de 5Ghz. Mas em Power saver mode, ele NUNCA sobe acima de 2Ghz e fica idling em 500Mhz. Ou seja, estava tudo &lt;strong&gt;DEZ VEZES MAIS LENTO&lt;/strong&gt;, às vezes tentava dar boost e ficava ainda &lt;strong&gt;CINCO A DUAS VEZES MAIS LENTO&lt;/strong&gt;. E é intermitente!!!&lt;/p&gt;

&lt;p&gt;Testando instalar apps, navegar, etc, tudo agora está funcionando normalmente, sem travar, sem sensação de lentidão. Start menu não trava mais - porque agora tem CPU suficiente pra processar as porcarias que ele precisa em background.&lt;/p&gt;

&lt;h3&gt;Conclusão&lt;/h3&gt;

&lt;p&gt;CHEQUE POWER OPTIONS NO CONTROL PANEL!!!!&lt;/p&gt;

&lt;p&gt;Repetindo: eu tinha ACABADO DE REINSTALAR O WINDOWS. Estava zerado e, por default, ele subiu em Power Saver mode.&lt;/p&gt;

&lt;p&gt;Não tenho idéia de porque ele fez isso, não sei se ele sempre faz isso, não sei se ele não reconheceu a placa-mãe da MSI, não sei que combinação de fatores de hardware podem fazer o Windows dar fallback pra isso.&lt;/p&gt;

&lt;p&gt;Mas é ALARMANTE que ele faça isso sem avisar. Não tem mensagem de erro. Não tem notificação. Não aparece no Event Viewer. Ou seja, mesmo um Power User como eu eu, precisa aleatoriamente suspeitar "hum, deixa eu ver o perfil de energia".&lt;/p&gt;

&lt;p&gt;Muita gente com Windows deve estar sentindo essa lentidão e travamentos e achando "hm, meu PC é porcaria mesmo, preciso juntar dinheiro pra comprar outro", sem saber que talvez ele só esteja capado por esse perfil de energia e na verdade o PC dele é de 2x a 5x, até 10x mais rápido e ele não sabe disso! Não é uma diferença pequena!&lt;/p&gt;

&lt;p&gt;POWER SAVER MODE é uma grande porcaria, não tem nenhuma utilidade prática e nem deveria existir.  É uma das coisas legadas que fazem o Windows ter uma péssima experiência - porque ele não avisa.&lt;/p&gt;

&lt;p&gt;Lembra os códigos de erros 1002 de hanging events? Se sair procurando no Google, todo fórum vai te dizer pra rodar programas de diagnósticos, desinstalar coisas, ou reinstalar o Windows. Nenhum cita nada sobre perfil de energia.&lt;/p&gt;

&lt;p&gt;Espero que isso ajude mais alguém!&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6253</id>
    <published>2025-05-19T15:19:00-03:00</published>
    <updated>2025-05-19T17:31:52-03:00</updated>
    <link href="/2025/05/19/historia-da-computacao-e-retro-dev-no-youtube" rel="alternate" type="text/html"/>
    <title>História da Computação e Retro Dev no YouTube</title>
    <content type="html">&lt;p&gt;Eu assisto MUITO YouTube, e fico sempre compartilhando coisas legais que assisto no meu X e Instagram (me sigam lá). Tem vários videos que vira e mexe eu volto atrás.&lt;/p&gt;

&lt;p&gt;Finalmente resolvi compilar uma lista com os canais que eu mais assisto e uma lista das principais playlists e exemplos de videos de cada canal que recomendo assistir.&lt;/p&gt;

&lt;p&gt;Provavelmente vou ficar voltando nesta página pra atualizar de tempos em tempos. Se alguém se lembrar de algum que eu esqueci, mandem nos comentários. Tem canais como GameSack ou Sega Lord X ou Retro Game Corp ou Wullfden ou Macho Nacho Productions e vários outros assim que não listei, porque são mais de "reviews" e não cruzam tanto com "dev". Tentei focar em coisas que eu acho que um "dev" deveria saber.&lt;/p&gt;

&lt;h2&gt;Lista (2025-05-19)&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@UsagiElectric"&gt;Usagi Electric&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=-RGp-bsYf34&amp;amp;list=PLnw98JPyObn3QSIwUz5Iv6NA1x-uGFqAa&amp;amp;pp=0gcJCV8EOCosWNin"&gt;Bendix G15 Vacuum Tube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=EbWcgepRmY4&amp;amp;list=PLnw98JPyObn2PqgY1mfzXOtnUXlsXSMch"&gt;Data General Nova&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=SB95dlrS5kU&amp;amp;list=PLnw98JPyObn2Kj-rqrKqEB5xmS_uOV40f"&gt;DEC/PDP-11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=oPA8dHtf_6M&amp;amp;list=PLnw98JPyObn1GUapiXLlGm8RrpQF-J_c1"&gt;1-Bit Computer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@TechTangents"&gt;Tech Tangents&lt;/a&gt;:

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mBssIIRGkOw&amp;amp;list=PLJVwF78cppBgwAHYKMffuhs0jblPHCyvL"&gt;Data Generals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=apFLq3-fpL4"&gt;Tandy 1000 RL HD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Oa6QPHD6qzw&amp;amp;t=278s"&gt;CP/M on Model 100&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=dqPSVG7zbs4"&gt;KayPro IV&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=vCU4xbHFb58"&gt;HP Series 200 - CAD Monster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5WB9d1JM8oo"&gt;8088 PC Networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=EOVLlMQs9f8"&gt;FreeDOS on IBM 5150&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@The8BitGuy"&gt;The 8-Bit Guy&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ayh0qebfD2g&amp;amp;list=PLfABUWdDse7bKGFshxR0itdHBhjUj86SX"&gt;Commander X16&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=NB_VBl7ut9Y&amp;amp;list=PLfABUWdDse7Z8wmD0bbL0P5upEE1VjMJM"&gt;Game Development - Making of Planet X2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=mXSdikcUuo0&amp;amp;list=PLfABUWdDse7aKLE00YObaVvP1jSH0YqIE"&gt;Texas Tech History&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=eP9y_7it3ZM&amp;amp;list=PLfABUWdDse7Y6LLPlfsHKcvBCgqaudzVY"&gt;Commodore History&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@adriansdigitalbasement"&gt;Adrien's Digital Basement&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=x8DfSg7oMWg&amp;amp;list=PL4tFqFVNf5LSckPX988MBvVzKv96eP0es"&gt;TRS-80 Model II Repair Series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gkCDdSdSNu0&amp;amp;list=PL4tFqFVNf5LRvKP94x7VOfdXXQHRt_8BR"&gt;Amiga 4000 Repair Series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=RMG_StQ67kA&amp;amp;list=PL4tFqFVNf5LQeof2oTPFGxZx0SpvICpSv"&gt;ZX Spectrum&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_CNruhSYC2E&amp;amp;list=PL4tFqFVNf5LRf-gcl5Lyhl2UxbmrGmZCT"&gt;IBM 16-bit machines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5qMP6A_U75E&amp;amp;list=PL4tFqFVNf5LT6X3sMRfbRWuTVE5NuEmKj&amp;amp;pp=0gcJCV8EOCosWNin"&gt;Apple Computers - 8-bit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=H9dxJ7uNXOA&amp;amp;list=PL4tFqFVNf5LRxSdlzuH2MWOZ_5PltbpLv"&gt;Macintosh Machines - 68000&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=0MP93YbINyI&amp;amp;list=PL4tFqFVNf5LRZKtduWSpZjZpJS6mxZx7I"&gt;Commodore - 8 bit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=0MP93YbINyI&amp;amp;list=PL4tFqFVNf5LR9SvTg1P8r3EPL657qJFsx"&gt;Commodore 64 Repairs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=QJRXMYmie7I&amp;amp;list=PL4tFqFVNf5LTPlF7iF_pMl5WiiOsSFMf4"&gt;Tandy 1000 Series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@clabretro"&gt;clabretro&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=77rVvJBLRhQ&amp;amp;list=PLjeznAUfx0Odrk8qOQJ-_a3xry4PhIxOh"&gt;Home Lab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=pXuqASXGvgM&amp;amp;list=PLjeznAUfx0OfCseO3gKOvEjw3oHgHLRlK"&gt;Sun Microsystem Stuff&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=PBvDRJ_rU5w"&gt;Ressurrecting an IBM PS/2 Model 50Z&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=7-ISDVFIVgw"&gt;Windows NT 4.0 on a Compaq Proliant Gen 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4UWibKEjyY8&amp;amp;t=10s"&gt;Token Ring Networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=LvLPz72IvAM"&gt;Early 2000s Linksys Wi-Fi Gear&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gyyhqHBmKg4"&gt;Compaq Proliant Gen 1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@LowSpecGamer"&gt;LowSpecGamer&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=lP2ZBp9O0mk&amp;amp;list=PLp-6MMWURCz3x92-dpEsRLEGL3uHToVDR"&gt;All Lore - Chronological&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=lP2ZBp9O0mk&amp;amp;list=PLp-6MMWURCz1bo3T4vmyNUVl3FefnF7CC"&gt;Chip Wars&lt;/a&gt;&lt;a href="https://www.youtube.com/watch?v=OBlu2mdpiJ0&amp;amp;list=PLp-6MMWURCz0OdcgoSiQOv8C9hn79XdOt"&gt;Nintendo Lore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=9Ki-kH751_8&amp;amp;list=PLp-6MMWURCz0OB_iioCUqc2Sq21UXE-08"&gt;The Yokoi Saga&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nIwdhPOVOUk"&gt;How Amateurs created the world's most popular processor - History of ARM Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=vx1-6yRAGk0"&gt;ARM's Secret Weapon - History of ARM Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=PLzTVnQ3gqc"&gt;How IBM Invented the PC, then everyone stole it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ztYfJDTjpjk&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;The Great Calculator Wars of the 70s&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@BenEater"&gt;Ben Eater&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=LnzuMJLZRdU&amp;amp;list=PLowKtXNTBypFbtuVMUVXNR0z1mu7dp7eH"&gt;Build a 65c02-based computer from scratch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=HyznrdDSSGM&amp;amp;list=PLowKtXNTBypGqImE405J2565dvjafglHU&amp;amp;pp=0gcJCV8EOCosWNin"&gt;Building an 8-bit breadboard computer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=XaGXPObx2Gs&amp;amp;list=PLowKtXNTBypH19whXTVoG3oKSuOcw_XeW"&gt;Networking Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=eq5YpKHXJDM&amp;amp;list=PLowKtXNTBypFWff2QjXCWuSfJDWcvE0Vm"&gt;Error detection&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@javidx9"&gt;javidx9&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nViZg02IMQo&amp;amp;list=PLrOv9FMX8xJHqMvSGB_9G9nZZ_4IgteYf"&gt;NES Emulator from Scratch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8OK8_tHeCIA&amp;amp;list=PLrOv9FMX8xJE8NgepZR1etrsU63fDDGxO"&gt;Code-it-Yourself - TETRIS from scratch&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@GamingHistorian"&gt;Gaming Historian&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=G_6PO3N8fig&amp;amp;list=PLScCWE7AP_PUIOOSwRF_e5jR6e2rLwLoF"&gt;Video Game Hardware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=2EUYSN5aFcE&amp;amp;list=PLScCWE7AP_PVMZgv83NLwp6wQ93I6o4rr"&gt;The Story of Super Mario - Documentary Series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=S1qD9JRk5vM&amp;amp;list=PLScCWE7AP_PU4_4b4juK8ZugrmptW4g1v&amp;amp;pp=0gcJCV8EOCosWNin"&gt;History of Wolfenstein&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=M55Bpm38qqE&amp;amp;list=PLScCWE7AP_PVO9xcAsZa7Dv5sgdZMyUbb"&gt;History of Sonic the Hedgehog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=myhBphil-Uw&amp;amp;list=PLScCWE7AP_PVVYlegzt3REr2n5bBmmQJ2"&gt;History of Super Smash Bros&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=BjRuV52Jk78&amp;amp;list=PLScCWE7AP_PXfCIUjxMjoWPUbNYXz8P6w"&gt;Games&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8QbjlHeoLdc&amp;amp;list=PL397C52406E483BAB&amp;amp;pp=0gcJCV8EOCosWNin"&gt;Gaming Historian&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@ModernVintageGamer"&gt;Modern Vintage Gamer&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=SZbC0Hh4lc8&amp;amp;list=PLUbw0IXAmW2rMk5g19MrqcyebecSrrSeJ"&gt;Mistakes Were Made - Game Console Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=siOXFGZj_z0&amp;amp;list=PLUbw0IXAmW2p35mZ_Fis31n2L6HPiFoEs"&gt;The Sony Playstation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=esl6ctg_9JA&amp;amp;list=PLUbw0IXAmW2qJ8e0XtQgQo8ki0mIeIbHP"&gt;The Original XBox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=F4Dv32-PoU4&amp;amp;list=PLUbw0IXAmW2pDWN4kZ-vNDNAPycFVR_ux"&gt;Doom Switch Port&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ZOzWS7MQ_qs&amp;amp;list=PLUbw0IXAmW2ofK7Es3w7q5kVMdzK_IfKY"&gt;Retro Reviews&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=lpOEhtoc3DY"&gt;N64: Decomp vs Recomp vs Ports explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=hqpw-QPsdCg"&gt;XenonRecomp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nJ9zJ_PDatU"&gt;Game Genie Explained&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_RVQsqC3A6g"&gt;Super NES Graphics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=OmKjVpVdHDQ"&gt;N64 Emulation is a Mess&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8ibEDSfthS0"&gt;The First Amiga Virus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ZeaZWBrG__s"&gt;35 Years of Game Boy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ockNRSt3Nsk"&gt;Video game piracy in the 80s&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=JqP3ZzWiul0"&gt;Doom SNES Sourcecode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=zQE1K074v3s"&gt;How Graphics worked on the Game Boy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=2NF5sU_n0uk"&gt;How UltraHLE changed N64 emulation forever&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5tADL_fmsHQ"&gt;How Diablo was completely Reverse Engineered without source code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Ap3gJT_TMOc&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;Duke3D on the Amiga 600&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@strafefox"&gt;Strafefox&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=6u37r-Z_Nfo"&gt;Video Game Development Through the 80s&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5jxYvLYcpZg&amp;amp;t=1441s"&gt;The 3D Revolution in 90s game development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=69AOVtOUatY"&gt;The Making of Sonic the Hedgehog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=226Nwq2CN5c"&gt;The Making of Mortal Kombat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=TYE9GMfr5ug"&gt;The Making of Castlevania Symphony of the Night&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_LPu7gNzWUE"&gt;The Making of Donkey Kong Country &amp;amp; Killer Instinct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=kOnUITJqRQQ"&gt;The Making of Aladdin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=5Z7liHTgnw4"&gt;The Making of Streets of Rage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=USC6c9Dtak8"&gt;The Making of Street Fighter II&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=KL4l0nMsrMM"&gt;The Making of Super Mario World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=GDhNT2Qv-Mo"&gt;The Making of Star Fox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=MJG-OT6S194"&gt;How Video Games were Made - Part 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Yo7UkkGC1AY"&gt;How Video Games were Made - Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VDz1IvuWyJw"&gt;MSX Konami Games&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@GTV-Japan"&gt;GTV Japan&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=_s9fmbcds40&amp;amp;list=PLu4Z50DKq3na663p7gGKgxHOZFGv2eqWO"&gt;The GTV Binge Watch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=zgtYV41yXbU&amp;amp;list=PLu4Z50DKq3nYnRu-3XhDncP8MRzOsusRm"&gt;GTV's Greatest Hits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=KdS34tc37hk"&gt;The Story of Chrono Trigger&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gNps2YP2e30&amp;amp;t=860s"&gt;The Final 16-bit Fantasy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=DV3K3Coz1RA"&gt;Sonic Mania Day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=I2MYvgNf9lU"&gt;The History of Sonic - 30th Anniversary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=zgtYV41yXbU"&gt;The Super Story of Mario World&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@DisplacedGamers"&gt;Displaced Gamers&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=55X1KOw_Rhg&amp;amp;list=PLn5sTGew1WGB0-omvT5UtPanoCzRh7A0V"&gt;Game Genie: Uncorked&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=EhO_iHkUkE0&amp;amp;list=PLn5sTGew1WGD5NUstpfwYdLfrmLJOHCSw"&gt;Talkin' Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gTfPLl_AJvs&amp;amp;list=PLn5sTGew1WGD2Tnspn9vhpLGf-kqDRbYy"&gt;Behind the Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ssluTgfkdlg&amp;amp;list=PLn5sTGew1WGCMO7Bi-MD7VgFWAoeXbU93"&gt;Gaming Tech Talk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@RGMechEx"&gt;Retro Game Mechanics Explained&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=UdD26eFVzHQ&amp;amp;list=PLHQ0utQyFw5IrM_zCFY8fhtByNxVGRUMr"&gt;Super Mario 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=1whju31WAPA&amp;amp;list=PLHQ0utQyFw5KxchzQnRcc_uk-NU7FbHNM"&gt;Super Mario Bros.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=q15yNrJHOak&amp;amp;list=PLHQ0utQyFw5I8qqpOXKXKySBsphqdStdq"&gt;Super Mario World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=fxZuzos7Auk&amp;amp;list=PLHQ0utQyFw5KoUSkHGq5VtMY0v2e5G_u3"&gt;Super Mario Bros 3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=FPzuYWbnln4&amp;amp;list=PLHQ0utQyFw5IrotCLCfxKGkUL-WjPDrxh"&gt;Super Mario Land 2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@TheCherno"&gt;The Cherno&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gfW1Fhd9u9Q&amp;amp;list=PLlrATfBNZ98edc5GshdBtREv5asFW3yXl"&gt;Ray Tracing Series&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=W3gAzLwfIP0&amp;amp;list=PLlrATfBNZ98foTJPJ_Ev03o2oq3-GGOS2"&gt;OpenGL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=9HCboZHTyrw&amp;amp;list=PLlrATfBNZ98f6Z1cDNeMLL3eXaRk1WCxK"&gt;Code Review&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@Kosmicd12"&gt;Kosmic&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=1wR8x5b_ExM&amp;amp;list=PLbxkd_Hs6FDK2FNWiOAK_LPBMXauJMrlC"&gt;World Record SpeedRuns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=kyZ4yF3ynsA&amp;amp;list=PLbxkd_Hs6FDKH8YVR82mS2zFDWN_pwkQx"&gt;Super Mario Bros&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@KazeN64"&gt;Kaze Emanuar&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=dfK4Iuz3mME&amp;amp;list=PLY9TGb7t9kuHx1vP2qCascFrhfnzLn-ME"&gt;SM64: Last Impact - Developer Commentary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=DdXLpoNLywg"&gt;Mario 64 breaks after 7 days&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=OHU2onxKm5o&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;Perfecting Random Number Generation on N64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=r-2iIBmXULI&amp;amp;t=129s"&gt;Planetary Gravity is Cheap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=g5u3zW4SLow&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;The Genius of N64 Cache Instruction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=DotEVFFv-tk"&gt;The N64 has more RAM than you think&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=f05PwswO7qc&amp;amp;t=344s"&gt;Why Devs need to know about Render Matrices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=-nhhfYkluT0"&gt;Better engine, Better graphics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=hffgNRfL1XY"&gt;The Folded Polynomial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=xFKFoGiGlXQ"&gt;Fastest Sine on N64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=t_rzYnXEQlE"&gt;Fixing the Entire SM64 source code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@james.lambert"&gt;James Lambert&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WDNU9k8uTc4"&gt;Can Portal64 be saved?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=sb3nHlsBBpg&amp;amp;t=256s"&gt;Portal 64: First Slice, is out of beta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=r4KGm7ixcqI"&gt;How I optimized Portal to run on the N64&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Sf036fO-ZUk"&gt;How I implemented MegaTextures on real N64 hardware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=ao1qVi5Qp3Y&amp;amp;t=58s&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;I rewrote Portal from scratch and solved the Portal Paradox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=VqDAxcWnq3g"&gt;How are toon shading and dynamic shadows possible on the N64?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=yXzoZ2AfWwg&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;How can Nintendo 64 run Portal?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@veritasium"&gt;Veritasium&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=AaZ_RSt0KP8&amp;amp;list=PLkahZjV5wKe8w9GC_n2yyhx6Wj-NKzzeE"&gt;Computing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=w5ebcowAJD8&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;I build a QR code with my bare hands to see how it workds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=wVyu7NB7W6Y"&gt;Exposing the flaw in out phone system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=A5w-dEgIU1M"&gt;The Trillion Dollar equation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=nmgFG7PUHfo&amp;amp;t=40s"&gt;The Most Important Algorithm of All Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=HeQX2HjkcNo"&gt;Math's Fundamental Flaw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=R13BD8qKeTg"&gt;The Bayesian Trap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=IcrBqCFLHIY&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;How Does a Transistor Work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=zNzzGgr2mhk"&gt;How to Make a Quantum Bit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/@3blue1brown"&gt;3Blue1Brown&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Playlists:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8rrHTtUzyZA&amp;amp;list=PLZHQObOWTQDMp_VZelDYjka8tnXNpXhzJ"&gt;Convolutions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=8idr1WZ1A7Q&amp;amp;list=PLZHQObOWTQDOjmo3Y6ADm0ScWAlEXf-fp"&gt;Probabilities of Probabilities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=p_di4Zn4wz4&amp;amp;list=PLZHQObOWTQDNPOjrT6KVlfJuKtYTftqH6"&gt;Differential equations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=WUvTyaaNkzM&amp;amp;list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr"&gt;Essence of Calculus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=fNk_zzaMoSs&amp;amp;list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab"&gt;Essence of Linear Algebra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=aircAruvnKk&amp;amp;list=PLZHQObOWTQDNU6R1_67000Dx_ZCJB-3pi"&gt;Neural Networks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Videos:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=RQWpF2Gb-gU"&gt;But what is a quantum computing - Grover's Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Dlsa9EBKDGI"&gt;Where my explanation of Grover's algorithm failed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=LPZh9BOjkQs&amp;amp;pp=0gcJCY0JAYcqIYzv"&gt;Large Language Models explained briefly&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6220</id>
    <published>2025-05-03T21:15:00-03:00</published>
    <updated>2025-05-05T18:57:26-03:00</updated>
    <link href="/2025/05/03/ultimo-tentativa-de-treinar-uma-llm-com-lora-tiro-de-canhao-mas-errando-a-mosca" rel="alternate" type="text/html"/>
    <title>Último Tentativa de Treinar uma LLM com LoRa. Tiro de canhão, mas errando a mosca.</title>
    <content type="html">&lt;p&gt;Ok, eu sou insistente. no &lt;a href="https://www.akitaonrails.com/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase"&gt;post anterior&lt;/a&gt; expliquei tudo que eu sabia sobre tentar fazer fine-tuning de um modelo com LoRa. Mas é um processo tedioso, demorado, e limitado à "pobreza" da minha RTX 4090. Em 24GB de VRAM tem que caber o modelo, coisas como KV cache, dataset de treinamento e espaço pra processar tudo. Apertando, cabe o modelo Qwen3-8B e 1MB de dataset e já era. Vai usar 21GB constantemente durante mais de 1 hora e o resultado final, apesar do modelo conseguir responser "sim, eu sei Zig 0.14", não é bom - porque o modelo base de só 8B já não era grande coisa.&lt;/p&gt;

&lt;p&gt;A saída: migrar meu treinamento pra uma máquina maior. Comecei tentando uma A40, pra ver se 40GB era suficiente pra caber o modelo Qwen3-32B pra esse treinamento. E não, não coube.&lt;/p&gt;

&lt;p&gt;"Ph0da-se", pensei. Aluguei a maior configuração que tem na RunPod:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcndCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ecbbb1dc8755cb2ebd9c5e7f32396c73434cb6f3/Screenshot%20From%202025-05-03%2014-08-43.png?disposition=attachment&amp;amp;locale=en" alt="H100" /&gt;&lt;/p&gt;

&lt;p&gt;Essa H100 já é "obsoleta", nos datacenters mais modernos já existe H200 Blackwell e outras coisas maiores. Mas no mundo mais de "mortais", uma H100 ao custo de USD 3 por HORA, vai ter que servir. O bom: tem 80GB de VRAM. E o que eu não esperava: até isso é pouco!&lt;/p&gt;

&lt;p&gt;E aqui a vantagem de ter feito tudo organizado e subir neste &lt;a href="https://github.com/akitaonrails/qwen3-zig-lora-training"&gt;repo no GitHub&lt;/a&gt;. O setup foi trivial: foi só escolher subir um pod com image de PyTorch 2.8 com Python 3.10 e Cuda 12.8, que são as versões estáveis mais recentes. Baixar meu projeto, rodar um &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; e já podia rodar meu &lt;code&gt;./train-lora.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Eu ia adicionar de volta aqueles 15MB de código-fonte da Standard Library do Zig mais novo, mas mesmo na H100, não coube! O treinamento só com o 1MB original que eu usei ontem também, já fez a máquina ficar entuchada quase no máximo:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcjhCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6f8e8013c7d652977e5a3901116abf4a2be7660e/Screenshot%20From%202025-05-03%2018-41-42.png?disposition=attachment&amp;amp;locale=en" alt="74GB" /&gt;&lt;/p&gt;

&lt;p&gt;Olha que absurdo: ficou o tempo todo do treinamento ocupando quase 75GB dos 80GB que tem de VRAM. Literalmente não dá pra fazer treinamento com muito mais material que isso. Ou precisa descer pra um modelo menor, como o Qwen3-14B. Eu ainda não sei se 32B com menos material ou 14B com mais material, qual seria a melhor combinação. Eu já sei que 8B não presta pra programar nada complicado. 14B já tive um pouco mais de sucesso em testes, mas também não faz nada muito complicado. 32B não é garantia que faz muito melhor: o Qwen2.5-32B não se saiu melhor que o 14B, mas o novo Qwen3-32B tem "cara" que é mais estável. É isso que estou querendo testar, com o LoRa.&lt;/p&gt;

&lt;p&gt;Isso foi na hora de almoço. Estou escrevendo este post na hora da janta. Esta foi a previsão do treinamento:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcjBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4c8c2813c5b6f74c4ed6da980278255ae4760664/Screenshot%20From%202025-05-03%2014-18-12.png?disposition=attachment&amp;amp;locale=en" alt="6h" /&gt;&lt;/p&gt;

&lt;p&gt;Essa estimativa fica variando, mas eu acho que levou em torno de 5 a 6 horas mesmo.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcjRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c2f496fae445d0ec11cade44c7d166ccfe34a1dd/Screenshot%20From%202025-05-03%2019-33-08.png?disposition=attachment&amp;amp;locale=en" alt="lora size" /&gt;&lt;/p&gt;

&lt;p&gt;E no final dessas quase 6 horas, foi isso que gerou: um LoRa de 5.1GB (contra os 3GB de ontem). Tamanho não é documento. Não significa que maior é melhor. Vamos ver.&lt;/p&gt;

&lt;h3&gt;Depois do Treino&lt;/h3&gt;

&lt;p&gt;Agora que acabou o treinamento, posso usar o mesmo pod com a H100 pra também servir esse modelo com o Lora, e precisaria ser ele de qualquer forma. Pra rodar é assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;vllm serve Qwen/Qwen3-32B \
    --enable-lora \
    --lora-modules ziglora=./qwen3-zig-lora \
    --max-model-len 10000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E precisa limitar ainda o tamanho do contexto. Por default o vLLM tenta subir com contexto de 40k tokens e, mesmo na H100, com 80GB, ele falha a inicialização com este erro:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc0FCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a0dcb9596e4b1c02b34916ee82797646c65a26d8/Screenshot%20From%202025-05-03%2019-41-52.png?disposition=attachment&amp;amp;locale=en" alt="KV Cache error" /&gt;&lt;/p&gt;

&lt;p&gt;Não sobra espaço pra subir o KV Cache (em resumo, no processo de geração de próxima palavra, vai adicionando uma nova linha e uma nova coluna na matrix, mas não precisa recalcular as linhas/colunas anteriores, é o mesmo resultado. Daí tem esse cache).&lt;/p&gt;

&lt;p&gt;Um comportamento estranho tanto na minha máquina quanto na H100. Não sei se isso acontece sempre que carrega um Lora, ou se meu Lora tem defeito, ou se tem alguma configuração que eu não estou vendo. Mas carregando com Lora, a inicialização é EXTREMAMENTE LENTA. Subir o vLLM, que normalmente leva 1 minuto, agora leva 5 minutos, ou mais. Não sei o que causa isso, mas não é um tipo de servidor que pode ser facilmente desligado e religado. Faz eu ter saudade de subir um servidor J2EE ...&lt;/p&gt;

&lt;p&gt;Falando sério, parece que não sou só eu. Esta &lt;a href="https://github.com/vllm-project/vllm/issues/6876"&gt;Issue&lt;/a&gt; não teve solução. "Pode" ser algum bug no vLLM, "pode" ser uma combinação de fatores que ninguém sabe. Mas nem a GPU e nem a CPU estão processando nada. CPU não sai de 10% de uso, GPU parece que está só esperando. Realmente, à primeira vista parece algum problema de I/O. Talvez o processo de checar os shards de checkpoints, puxando de um network volume, seja lento. Mas como vou rodar só hoje, não me preocupei em debugar isso.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc0VCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--f9f9419d5c70e3160e5ae3a57f13d48c41847b40/Screenshot%20From%202025-05-03%2019-53-59.png?disposition=attachment&amp;amp;locale=en" alt="slow load" /&gt;&lt;/p&gt;

&lt;p&gt;Olha isso. 8 MINUTOS nessa parte de carregar checkpoints. E depois ainda continua outras fases que também demora mais um pouco. Vou deixar essa foto aqui, caso alguém já tenha visto e saiba o que pode ser.&lt;/p&gt;

&lt;p&gt;Só pra tirar a dúvida, experimentei subir o vLLM só com o Qwen3 e tirando a LoRa completamente e, pra minha surpresa:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc1lCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a813e2f730d0b095f23940b7dba41ce2cea6cb94/Screenshot%20From%202025-05-03%2021-14-05.png?disposition=attachment&amp;amp;locale=en" alt="sem lora" /&gt;&lt;/p&gt;

&lt;p&gt;Também levou quase 8 minutos. Então o problema não é a LoRa. Ou é alguma coisa do vLLM que dá pra configurar - mas eu não sei como - ou é uma limitação do próprio modelo Qwen3-32B que é lento assim mesmo pra carregar.&lt;/p&gt;

&lt;p&gt;Enfim, com Lora, tentei diminuir pra 16k tokens: não sobe. Tentei subir com 10k tokens. E nesse ponto eu diria que esse teste já fracassou. Um Qwen3-32 teoricamente tem capacidade de suportar janelas de até 40k tokens. Não é grande, mas dá pra trabalhar um pouco nesse contexto.&lt;/p&gt;

&lt;p&gt;Mas abaixo de 15k tokens já não dá pra usar com ferramentas como Aider, que precisa mandar system prompts longos pra instruir a LLM como se comportar pra cuspir código que ele consegue capturar. Então é isso: um Qwen3-32b + Lora, numa H100 SXM de 80GB de VRAM, não é usável de verdade.&lt;/p&gt;

&lt;p&gt;E eu até ficaria triste ou frustrado caso um modelo de 32B fosse realmente, ordens de grandeza melhor que um 14B da vida, mas nos meus testes empíricos: não é. Ele faz tanto erro quanto, e não é tão melhor assim que compense pagar tão caro por uma infra própria topo de linha. Todos eles male male só conseguem fazer códigos simples, e mesmo assim com erros.&lt;/p&gt;

&lt;p&gt;No final, com 10k o server subiu, consumindo absurdos 63GB na inicialização, sem nem começar a processar:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc0lCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6a31f4c35058ed770f9cec8be7410a42bff30a8e/Screenshot%20From%202025-05-03%2020-02-12.png?disposition=attachment&amp;amp;locale=en" alt="63GB" /&gt;&lt;/p&gt;

&lt;p&gt;E ...&lt;/p&gt;

&lt;h3&gt;Primeiros Testes&lt;/h3&gt;

&lt;p&gt;Depois de 10 minutos esperando, tive que dar Ctrl+C e começar tudo de novo. Porque eu fui burro. A RunPod é pra realmente rodar coisas experimentais, não sei se confio pra produção. Ela é mais um VPS do que uma AWS. E eu estou gambiarrando subindo um pod que espera ter mapeado só a porta 8888 pra subir um server de Jupyter Notebook (que eu não estou subindo).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc01CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--f5dc0a6934302aec99dfc06225968ae75c1a5489/Screenshot%20From%202025-05-03%2020-05-17.png?disposition=attachment&amp;amp;locale=en" alt="runpod connect" /&gt;&lt;/p&gt;

&lt;p&gt;MInha idéia é usar essa porta pra mapear o vLLM. Mas por default ele sobe na porta 8000. Eu esqueci de colocar &lt;code&gt;--port 8888&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;vllm serve Qwen/Qwen3-32b --enable-lora --lora-modules ziglora=./qwen3-zig-lora --max_model_len 10000 --port 8888
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E lá vamos nós de novo, esperar mais 10 minutos ... ... e finalmente subiu na porta certa e agora podemos testar. Primeiro, os teste de curl:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc1FCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ee4d6abd204baf56700ea931871c62ce95d8e07a/Screenshot%20From%202025-05-03%2020-23-31.png?disposition=attachment&amp;amp;locale=en" alt="curl hello" /&gt;&lt;/p&gt;

&lt;p&gt;E antes que alguém comente: não tem problema estar aparecendo a URL do meu pod na foto. Eu vou apagar esse pod antes de publicar este post.&lt;/p&gt;

&lt;p&gt;Aliás, se subir um modelo remoto, como eu fiz, depois é só pegar a URL e colocar na variável &lt;code&gt;OPENAI_API_BASE&lt;/code&gt; e qualquer coisa em &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; e ferramentas como Aider vão funcionar igual. A API do vLLM e Ollama são compatíveis com da OpenAI.&lt;/p&gt;

&lt;p&gt;Como podem ver, a primeira pergunta respondeu parecido com meu teste local com o Qwen3-8B. Nada demais, isso já sabia que funcionava. Vamos pedir pra explicar uma funcionalidade nova do Zig 0.14.&lt;/p&gt;

&lt;p&gt;Aliás, além de lento pra iniciar, também fica mais lento pra gerar texto, BEM mais lento. Eu devo ter feito algo errado no treino, mas eu senti a mesma coisa no Lora local ou na H100. Fica notavelmente mais lento. Ele fez mais um "think" longo, mas a resposta desta vez é mais condizente com o release note (cortei só o trecho que interessa):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;In Zig 0.14.0, the `@splat` function has been extended to support arrays, making it easier to initialize fixed-length arrays with a single repeated value. This replaces more verbose or unsafe initialization patterns and is type-safe, with the array's length inferred from the destination type. Here's a concise explanation and code example:

---
...

### **@splat for Arrays**  
`@splat(value: T) [N]T` creates an array of length `N` where every element is `value`. The array's length and element type are determined by the context in which `@splat` is used.  

### **Code Example**  

const Point = struct { x: i32, y: i32 };  

pub fn main() void {  
    // Initialize an array of 5 integers with 42  
    const numbers: [5]i32 = @splat(42);  

    // Initialize an array of 10 booleans with true  
    const flags: [10]bool = @splat(true);  

    // Initialize an array of 7 points with (0, 0)  
    const origin_points: [7]Point = @splat(Point{ .x = 0, .y = 0 });  
}  
....
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;O código de exemplo também faz um pouco mais de sentido. Comparado com a versão 8B, pelo menos a explicação foi melhor. Mas pode ter afetado que eu fiz uma pequena modificação no dataset: eu dupliquei o release notes, pra tentar um pouco mais de ênfase nas funcionalidades novas. Provavelmente isso também fez efeito.&lt;/p&gt;

&lt;p&gt;Agora vamos ver se ele consegue fazer algum código que funciona. Meu prompt foi este:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;"Can you give me a Zig 0.14 code example of a simple program that acts like an interactive chat, so the user can input messages. then the message is parsed to see if there are commands such as '/list', which will list the files in the current directory, or '/cat' which will read the file asked, or '/quit' to exit the chat?"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E isto aconteceu:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBc1VCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4e4d724467dc0fca8069845f68a5b0da35dd7778/Screenshot%20From%202025-05-03%2020-36-42.png?disposition=attachment&amp;amp;locale=en" alt="abort" /&gt;&lt;/p&gt;

&lt;p&gt;Lembra como eu tive que ir diminuindo contexto pra conseguir subir? E lembram como eu falo que "thinking" desperdiça muitos tokens? Pois é. O modelo se auto-crasheou pensando demais. Tentei algumas vezes e ele crasheia. Pra tentar evitar isso, tentei fazer o &lt;code&gt;curl&lt;/code&gt; mandando &lt;code&gt;enable_thinking=false&lt;/code&gt; assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;❯ curl https://d8zck5aw8eimij-8888.proxy.runpod.net/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
        "model": "ziglora", "temperature": "0.1", "enable_thinking": "false", "skip_special_tokens": true,
        "messages": [{"role": "user", "content": "..."}]
      }'
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Temperatura, pra código, eu acho que não dá pra ser muito maior que 0.1. Pra bate-papo, coisas menos exatas, o padrão de 0.6 que todo mundo usa é ok. Mas pra código, eu realmente não preciso que ele fique sendo "criativo" (aleatório, na verdade), eu quero respostas o mais exatas quanto possível.&lt;/p&gt;

&lt;p&gt;Enfim, mesmo tentando desligar o thinking, alguma coisa está errada. Possivelmente o LoRa que eu adicionei está fazendo ele "pensar em círculos" e nunca conseguindo retornar, estourando o contexto. Tentei várias vezes, mas ele está sendo incapaz de responder código.&lt;/p&gt;

&lt;h3&gt;Final: Desisto por enquanto&lt;/h3&gt;

&lt;p&gt;Sim, eu quebrei a LLM, ao que parece kkkkk &#128517;&lt;/p&gt;

&lt;p&gt;Tentei várias vezes e ele realmente não consegue me devolver nenhum código. E eu sei que ele está tentando, porque se eu mando &lt;code&gt;stream=true&lt;/code&gt;, eu consigo ver devolvendo tokens, mas em algum momento acaba espaço em algum lugar (com certeza o contexto, que é pequeno) e crasheia.&lt;/p&gt;

&lt;p&gt;A sensação é que meu LoRa provavelmente "grudou" demais e desbalanceou parâmetros demais. Agora o modelo começa a escrever o texto e começa a entrar em pensamento circular, sem nunca parar de gerar mais texto.&lt;/p&gt;

&lt;p&gt;Isso com certeza é efeito do Lora e de algum erro no treinamento. Se eu tiver que chutar, é porque eu enfiei textos super longos no treinamento, sem indicações manuais de aonde acaba cada parte com um &lt;code&gt;&amp;lt;|im_end|&amp;gt;&lt;/code&gt; da vida. Sem isso, talvez ele ache que o texto inteiro é uma resposta e ele tem que responder longo assim. Pelo que entendi, no caso desse treinamento de Zig, eu teria que decompor a documentação, o release notes e tudo mais em pedaços menores, assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;|im_start|&amp;gt;system
You are a helpful assistant.&amp;lt;|im_end|&amp;gt;
&amp;lt;|im_start|&amp;gt;user
What is Zig?&amp;lt;|im_end|&amp;gt;
&amp;lt;|im_start|&amp;gt;assistant
Zig is a compiled programming language...&amp;lt;|im_end|&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E fazer isso pra todas as 200 mil caracteres de documentação. Imagina o trabalho pra fazer isso. E evitar enviar textos super longos em contexto de começa e onde acaba cada parte.&lt;/p&gt;

&lt;p&gt;Pelo menos, esse é meu chute. O problema: mesmo se eu gastar tempo formatando assim, daí são mais 6 horas processando, pra só depois saber se fez alguma diferença. Provavemente não vai fazer, preciso pensar outra hipóteses, e cada vez são 6 a 12 horas entre setup e processamento.&lt;/p&gt;

&lt;p&gt;A partir deste ponto é um trampo que eu não tenho motivação pra fazer. Estou deixando anotado aqui pra ver se isso chega na mão de pesquisadores de verdade. Talvez seja algo que um pesquisador de I.A. ache super óbvio. Mas este é o ponto: essa informação não existe fácil publicamente em lugar nenhum. Todo exemplo que se acha online são muito simples. Só falam &lt;em&gt;"nesta linha de python você carrega o dataset, e nesta próxima linha começa o treinamento"&lt;/em&gt; - mas cadê as instruções de COMO fazer um dataset e COMO determinar se ela é adequada? Não tem.&lt;/p&gt;

&lt;p&gt;Se alguém tiver dicas de como melhorar o treinamento ou quiser um problema de pesquisa na área, &lt;a href="https://github.com/akitaonrails/qwen3-zig-lora-training"&gt;este é o GitHub&lt;/a&gt; com todos os códigos e material de treinamento que eu fiz. Está faltando pesquisadores olharem pra esse tipo de coisa e divulgar melhor o que sabem. Eu sei que alguém sabe, mas só sair fuçando no Google não me levou a lugar algum.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6209</id>
    <published>2025-05-03T02:30:00-03:00</published>
    <updated>2025-05-05T18:57:35-03:00</updated>
    <link href="/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase" rel="alternate" type="text/html"/>
    <title>Ensinando Zig mais recente pra sua LLM - Treinando LoRas (quase)</title>
    <content type="html">&lt;p&gt;&lt;a href="https://www.akitaonrails.com/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real"&gt;Dois posts atrás&lt;/a&gt; eu demonstrei o Calcanhar de Aquiles das LLMs pra programação: eles não foram treinados com os dados mais recentes disponíveis. Então se perguntar sobre mudanças na versão da sua biblioteca, linguagem ou frameworks favoritos que saiu mês passado, ele não vai saber.&lt;/p&gt;

&lt;p&gt;Nota: todos os códigos que vou mostrar mais abaixo estão disponíveis &lt;a href="https://github.com/akitaonrails/qwen3-zig-lora-training"&gt;neste repositório no meu GitHub&lt;/a&gt;. Fucem lá depois de ler até o fim.&lt;/p&gt;

&lt;p&gt;Por exemplo, eu perguntei ao modelo Qwen3-8b (que saiu poucos dias atrás), qual versão de Zig ele sabe e a resposta foi: 0.11 ou 0.12:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcDRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--1a2dac8a62ae3b1860bee74a9ca205e1d1559c62/Screenshot%20From%202025-05-02%2014-24-08.png?disposition=attachment&amp;amp;locale=en" alt="zig antigo" /&gt;&lt;/p&gt;

&lt;p&gt;Aliás, é assim como na foto que se chama uma API compatível com OpenAI de LLMs com curl. De qualquer forma, foi por isso que eu sofri tanto pra tentar fazer funcionar um programinha simples (nível "Hello World") ontem: ele não sabe que já estamos no &lt;a href="https://ziglang.org/download/0.14.0/release-notes.html"&gt;Zig 0.14&lt;/a&gt;. E foi de propósito, eu sabia que ele não ia ter essa informação.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"E outros LLMs comerciais? Eles não são melhores que os open source?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Perguntei ao ChatGPT 4.1, Claude Sonnet 3.7, Gemini 2.5 Pro Experimental, Deepseek R1 Zero (tudo de uma vez, OpenRouter.ai permite chat multi-model):&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcVVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2375afc70464540661d50b074f2880b633cf800a/Screenshot%20From%202025-05-02%2020-30-09.png?disposition=attachment&amp;amp;locale=en" alt="LLM cut off" /&gt;&lt;/p&gt;

&lt;p&gt;Resumindo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ChatGPT 4.1 diz que ele só tem dados até Junho de 2024 e só conhece até Zig 0.11.&lt;/li&gt;
&lt;li&gt;Claude Sonnet diz que ele só tem dados até Abril de 2023 e só conhece até 0.10.&lt;/li&gt;
&lt;li&gt;Gemini 2.5 Pro Exp diz que só tem dados até começo de 2023 e só conhece até Zig 0.11 também&lt;/li&gt;
&lt;li&gt;Deepseek R1 (depois de deep thinking ...) respondeu curto e grosso que só tem dados até Outubro de 2023 e só conhece até Zig 0.11.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;em&gt;"Por que eles não tem dados mais novos?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Vou responder isso demonstrando os motivos nas próximas seções.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Mas ele consegue puxar informações da internet, então não importa"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Importa. Essas informações não estão NO modelo. Quando você pede pra pesquisar na Internet, o modelo não sabe fazer isso. Ele foi instruído (na fase de fine-tuning/alinhamento ou via prompts) pra pedir ao sistema de chat - o parser - que pegue essa requisição, navegue na web (pense um curl, playwright ou qualquer coisa assim), puxe o conteúdo do site, copia e cola só na sua sessão de chat (é como uma solução de RAG). Ele só tem aquela informação naquela sessão e outros chats não são influenciados por isso.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Mas como que quando ele responde, ele mostra os links de onde veio essa informação?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ele - provavelmente - não tem isso dentro do modelo. O que eu imagino é mais simples. Eles tem catalogado e indexado todo o material de treinamento original (incluindo a URL das fontes) num banco de dados (pense Elastic, Postgres com full-text-search, ou um banco vetorial qualquer). Quando termina de responder no chat - o programa de chat e não o modelo -, pesquisa esse trecho nesse banco de dados por um trecho parecido o suficiente e dali pega as URLs das fontes pra te mostrar.&lt;/p&gt;

&lt;p&gt;Enfim.&lt;/p&gt;

&lt;p&gt;Zig é experimental, pré-1.0. A sintaxe de tudo muda MUITO de uma versão pra outra, porque ainda estão experimentando o que é melhor. E ainda vai mudar mais. Ou seja: é impossível fazer "Vibe Coding" de Zig a menos que você fixe seu compilador pra versão 0.11 ou 0.12. Mas isso seria "tosco", pra dizer o mínimo. Eu escolhi tentar Zig de propósito, exatamente pra provar esse ponto.&lt;/p&gt;

&lt;p&gt;Mesma coisa vale pra muita empresa com código privado. Penso empresas de ERP que tem linguagens próprias, bibliotecas próprias, e nada disso é público, então garantidamente não estava no treinamento de uma OpenAI e certamente é muito mais informação que cabe num contexto de prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Ah, basta fazer um RAG"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Solução enlatada sem entender. Não, não adianta. Ele vai dar prioridade ao treinamento que teve. Segundo, pra cada coisa que pedir, você precisaria saber que vai precisar de algo que mudou na 0.14, pesquisar o trecho da documentação e ficar adicionando no contexto do chat. Se for fazer isso método a método, pra que está usando o chat? Vai e escreve direto o código, vai ser mais rápido.&lt;/p&gt;

&lt;p&gt;Jogar TODA a documentação no contexto não adianta. Eu fiz a conta: baixei a documentação inteira e release notes são 200 mil caracteres, aproximadamente uns 50 mil tokens. Maioria dos LLMs open source vai estourar a janela (média de 32 ml tokens). Via API/Chat comercial você iria pagar por token, à toa. Imagine querer refatorar um código seu, de 100 linhas e ter que subir 200 mil linhas de contexto. Nunca faça isso. O custo-benefício não compensa.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Ah, mas os Gemini da vida suporta 1 milhão de tokens"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sim, com sliding window, já repeti várias vezes. Ele não consegue dar atenção a tudo de uma só vez. De novo: você vai &lt;strong&gt;PAGAR&lt;/strong&gt; por esses 50 mil tokens, tá? E não vai ser barato. Imagina, toda nova sessão ter que subir 50 mil tokens que não tem garantia de funcionar.&lt;/p&gt;

&lt;p&gt;Então ph0deu? Não tem como usar Zig com LLMs?&lt;/p&gt;

&lt;h3&gt;RAGs?&lt;/h3&gt;

&lt;p&gt;O hype de RAG (Retrieval Augmented Generation, que é só um nome bonitinho) faz parecer que é algo "mágico". Mas realmente, não tem nada demais. Ele é bom pra quando você tem um banco de dados grande, que cresce o tempo todo, mas em cada sessão de chat, você só precisa de um pouquinho dessa informação. Pense um suporte de e-commerce da vida, com tickets de suporte. Cresce todo dia, mas pra cada chamada você só precisa do ticket específico da pessoa que está atendendo, que é bem curto.&lt;/p&gt;

&lt;p&gt;Do jeito mais rudimentar, o atendente pesquisa o ticket no tradicional Zendesk da vida, copia o texto, joga na janela de chat do ChatGPT e começa a fazer perguntas sobre esse ticket, tipo "pode me resumir qual o problema desse cliente?", "consegue imaginar uma solução pra ele?" e assim por diante.&lt;/p&gt;

&lt;p&gt;Um RAG é um BANCO DE DADOS com todos esses tickets pré-indexados pra facilitar a procura por relevância, e depois inserir o trecho que achou direto na janela de contexto. Serve pra automatizar, PULAR os passos de pesquisar manualmente em outro sistema e dar copy e paste, mas o efeito é EXATAMENTE o mesmo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Ah, mas usa VECTOR databases, deve ser mais ph0da"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ph0da é ouvir asneira. Poderia ser um Postgres e fazer um SELECT pra achar o ticket, é exatamente a mesma coisa. Se você habilitar "full-text-search" no Postgres ele vai indexar seus tickets em um "VECTOR SPACE MODEL", que é essencialmente, um VECTOR DATABASE.&lt;/p&gt;

&lt;p&gt;Se em vez disso prefere SOLR ou ELASTIC, ambos são VECTOR DATABASES especializados em pesquisa de texto.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Duh, the more you know.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;O principal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;quanto mais entuchar no contexto (seja prompt texto, seja "embedding" de vector database com RAG - é a mesma coisa), mais arrisca confundir o LLM e ele começar a responder alucinações. Os melhores prompts são estruturados, organizados, com ordens diretas, e que explicam o contexto claramente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;seja texto-prompt seja "embedding", vai CONSUMIR TOKENS de input. E se a janela for pequena (modelos open source), vai estourar se não controlar o tamanho do que mandar de input. Não tem mágica.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Então qual a saída? Apesar de só falarem nisso. Não existe só RAG no mundo de LLM.&lt;/p&gt;

&lt;h3&gt;Possível Solução: LoRas&lt;/h3&gt;

&lt;p&gt;Se em vez de coisas que crescem diariamente, for informação mais "permanente" como manuais de instrução, livros, talvez RAGs não sejam a melhor forma. Porque vai ser sempre MUITO GRANDE e precisaria REPETIR A MESMA COISA em toda sessão de chat. Especialmente regras que dependem de outras regras.&lt;/p&gt;

&lt;p&gt;Sim, bons providers tem &lt;a href="https://x.com/pashmerepat/status/1918084120514900395"&gt;CACHE STATS&lt;/a&gt; e isso ajuda muito. Mesmo assim não é garantia. Do nada você vai ver sua conta e se surpreender com USD 500 do nada gasto em crédito de tokens.&lt;/p&gt;

&lt;p&gt;Exemplo: documentação de linguagens de programação. Como é meu caso com Zig 0.14.0. Eu gostaria que a LLM tivesse esse contexto, mas toda vez subir 50 mil tokens, é muita coisa se eu só quiser corrigir um arquivo de 50 linhas. Ninguém pensa nessas coisas e nem calcula tamanho do contexto vs tamanho da interação no chat/api, e deveria: qualquer tokens desperdiçado é crédito gasto na sua conta.&lt;/p&gt;

&lt;p&gt;Existe uma forma de pegar um modelo LLM que já existe e adicionar o treinamento que falta por cima, sem usar espaço de contexto. Seria como se o modelo fosse "atualizado" pra saber Zig 0.14. Isso são LoRas (&lt;strong&gt;Low-Rank Adapter&lt;/strong&gt;). É um tipo de treinamento menor que podemos adicionar. Treinamos pequenas matrizes adicionais (adapters), que se anexam aos layers da LLM original. Quando carregado isso atualiza as saídas do modelo sem precisar refazer todo o treinamento completo do zero (que é a parte mais cara e que leva meses em datacenters pesados da OpenAI, por exemplo).&lt;/p&gt;

&lt;p&gt;E fica um &lt;strong&gt;DISCLAIMER&lt;/strong&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Eu não estudei ainda o suficiente sobre todos os parâmetros de treinamento. Treinar é justamente a fase mais difícil. Em particular: gerar material de treinamento de QUALIDADE e "tunar" os parâmetros do treinamento. Tem gente especializada só nisso e não existe uma única receita que serve pra tudo. É quase uma "arte" e precisa de muito mais dedicação do que eu fiz só pra este post.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Alguns artigos pra começar a estudar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@ufuk.birbiri/instruction-tuning-large-language-models-llms-with-huggingface-lora-and-sfttrainer-544bad770b50"&gt;Instruction-Tuning Large Language Models (LLMs) with HuggingFace, LoRA and SFTTrainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/docs/trl/main/en/sft_trainer"&gt;# Supervised Fine-tuning Trainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rocm.docs.amd.com/projects/ai-developer-hub/en/latest/notebooks/fine_tune/fine_tuning_lora_qwen2vl.html"&gt;# Fine-tuning with the Hugging Face ecosystem (TRL)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://clay-atlas.com/us/blog/2024/10/08/en-multi-modal-large-language-model-sft-trainer/"&gt;# Notes on Fine-Tuning a Multi-Modal Large Language Model Using SFTTrainer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zohaib.me/a-beginners-guide-to-fine-tuning-llm-using-lora/"&gt;# A beginners guide to fine tuning LLM using LoRA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;E tem muito mais. É aqui que tem que entender mais os "internals" de verdade, as redes neurais, função de perda, gradientes, etc. Não subestime o treinamento. Não vai funcionar de cara na primeira vez, nem na décima. É MUITA tentativa e erro e ajuste.&lt;/p&gt;

&lt;p&gt;O resultado final não é exatamente a mesma coisa que um treinamento completo, mas esses adapters modificam os parâmetros dos layers do modelo pré-existente, alterando o resultado final pra coisas específicas que queremos (que ele saiba Zig 0.14). É BEM mais barato que um treinamento do zero. Não garante que vai funcionar 100%, mas dá uma opção pra testarmos, que pode ser mais robusto que um RAG.&lt;/p&gt;

&lt;p&gt;O processo é uma versão miniatura de um treinamento de modelo de verdade. Temos que resolver os seguintes passos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conseguir o material de treinamento (ex. documentação)&lt;/li&gt;
&lt;li&gt;escrever prompts de fine-tuning (eu preciso "ensinar" como ele deve responder - essa é a parte mais difícil)&lt;/li&gt;
&lt;li&gt;precisa tokenizar essa informação (trivial), carregar o modelo de base (fácil), e começar a treinar (quanto melhor a GPU, menos lento)&lt;/li&gt;
&lt;li&gt;se der sucesso precisa subir esse LoRa junto com o modelo de base&lt;/li&gt;
&lt;li&gt;apontar ferramentas como Aider pra esse novo modelo modificado.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Vamos lá, passo a passo.&lt;/p&gt;

&lt;h3&gt;1. Material de Treinamento&lt;/h3&gt;

&lt;p&gt;Eu acho que esta é a parte mais difícil: conseguir material de treinamento de qualidade pra treinar.&lt;/p&gt;

&lt;p&gt;No caso de alinhamento, isso é feito construindo na mão (com a ajuda de scripts, com a ajuda de outra LLM pra ajudar a gerar textos sintéticos), mas o ideal seria eu tentar construir um arquivão JSON com chaves de "prompt" e "completion" parecido com:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{ "prompt": "How do the new @splat works for arrays in Zig 0.14.0?, "completion": "Zig 0.14.0 expands the `@splat` builtin to apply not only to vectors, but to arrays..."}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Agora, fazer isso pra CADA UMA das novas funcionalidades da versão 0.14.0, incluindo exemplos de código, múltiplas vezes pra cada tópico, imaginando formas diferentes de perguntar. E não, eu não fiz isso não pretendo fazer, já é muito mais trabalho do que eu quero tentar só no meu experimento. E eu nem sei se essa é a melhor forma de fazer ainda. Como eu disse antes, é praticamente uma "arte".&lt;/p&gt;

&lt;p&gt;O arquivo de &lt;a href="https://ziglang.org/download/0.14.0/release-notes.html"&gt;RELEASE NOTES&lt;/a&gt; convertido em Markdown tem ridículos &lt;strong&gt;107 mil caracteres&lt;/strong&gt;. Eu ia passar o resto da semana só re-editando ele em forma de prompts. Não vou fazer isso. Em vez disso, vou só passar o texto crú inteiro e torcer pra esses novos fatos "grudarem" o suficiente pra LLM se lembrar delas. Pra puxar, é simples:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wget https://ziglang.org/download/0.14.0/release-notes.html -O zig-0.14.0-release-notes.html

# pip install html2text
html2text zig-0.14.0-release-notes.html &amp;gt; raw_data/zig-0.14.0-release-notes.md
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Já que estou no embalo, porque não fazer a mesma coisa com a documentação do Zig? Felizmente a documentação é um único arquivão HTML. Então não tem trabalho de fazer um web scrapper recursivo nem nada disso. É o mesmo procedimento:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;wget https://github.com/ziglang/www.ziglang.org/raw/refs/heads/main/src/documentation/0.14.0/index.html -O raw_data/zig-0.14.0-docs.html

# pip install html2text
html2text raw_data/zig-0.14.0-docs.html &amp;gt; raw_data/zig-0.14.0-docs.md
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isso deu um Markdown de &lt;strong&gt;464 kilobytes&lt;/strong&gt;. Como eu acho que só isso não é suficiente. Achei bom também pegar os exemplos de código que estão no diretório &lt;code&gt;zig/doc&lt;/code&gt; que clonei direto do &lt;a href="https://github.com/ziglang/zig/tree/master/doc/langref"&gt;repositório oficial do Zig&lt;/a&gt;. Não sei qual a melhor maneira, mas eu tentei organizar um único arquivão, com nome do arquivo &lt;code&gt;.zig&lt;/code&gt; e o código delimitado por uma tag, com um prompt no começo indicando isso:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;...
zig_files = glob.glob("raw_data/zig/doc/langref/**/*.zig", recursive=True)
total_input_bytes = 0
file_count = 0
lines = []

for filepath in zig_files:
    file_bytes = os.path.getsize(filepath)
    total_input_bytes += file_bytes
    file_count += 1
    filename = os.path.basename(filepath)
    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read().strip()
        entry = f"Reference Filename: {filename}\n\n```zig\b{content}\b```\n"
        lines.append(entry)
...
outpath = "raw_data/zig_docs_with_filenames.txt"
with open(outpath, "w", encoding='utf-8') as f:
    for line in lines:
        f.write(line + "\n\n")

...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notem que neste caso eu estou formatando cada exemplo começando com "Reference filename: {filename}" e depois adicionando o código num bloco de Markdown com 3 tremas. É um bom formato? Não sei ainda. Fazer material de treino É DIFÍCIL.&lt;/p&gt;

&lt;p&gt;Isso resultou em mais um arquivão de &lt;strong&gt;152 kilobytes&lt;/strong&gt;. Espero que não confuda mais do que ajude. E não parei nisso. Eu queria colocar o código-fonte inteiro da &lt;a href="https://github.com/ziglang/zig/tree/master/lib/std"&gt;Standard Library&lt;/a&gt; e cheguei a fazer o script, baixar e compilar em um formato estruturado, mas o arquivão final deu &lt;strong&gt;15 MEGABYTES&lt;/strong&gt;. Infelizmente tive que comentar pra fora do meu script de treinamento. Agora eu bato numa limitação da minha RTX 4090: não tem VRAM suficiente pra treinar tudo isso.&lt;/p&gt;

&lt;p&gt;Como já repeti antes, a 4090 só tem 24GB de VRAM. O modelo Qwen3-8b sozinho ocupa 16GB (não, número de parâmetros influencia tamanho mas não significa que 1 parâmetro é 1 byte sempre). Tem que sobrar memória pra fazer os cálculos todos do treinamento, e ele vai gastar 21GB enquanto treina. Se eu coloco esse material de 15MB junto, acontece isso:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcVlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--30796fbeda6dee5b02ee58d2a9fb304dd882e19e/Screenshot%20From%202025-05-03%2000-11-24.png?disposition=attachment&amp;amp;locale=en" alt="out of memory vram" /&gt;&lt;/p&gt;

&lt;p&gt;Pra treinar com tudo isso, tem que ser remoto, num RunPod com a A40 como já mostrei em outro blog post, no mínimo. Ou com GPUs mais rápidas como a H100, com 80GB, e mais potência pra acabar um pouco mais rápido. Então, eu já estou limitado em material de treinamento de qualidade e agora limitado na quantidade de material que posso usar.&lt;/p&gt;

&lt;p&gt;Já que não pude usar o código-fonte (que teria exemplos reais de código idiomático da versão 0.14.0), procurei outros exemplos pra adicionar e achei esse repositório &lt;a href="https://github.com/zigcc/zig-cookbook"&gt;Zig Cookbook&lt;/a&gt;, que é organizado em problemas num arquivo Markdown a soluções em código compatível com 0.14. Fiz esse outro script pra organizar tudo num único arquivo:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;...
lines = []

for prob_file in sorted(glob.glob('raw_data/zig-cookbook/book-src/*.md')):
    # Extract the nn-nn from the problem filename (e.g., 01-05-matrix-mul.md -&amp;gt; 01-05)
    base = os.path.basename(prob_file)
    if base == "SUMMARY.md" or base == "database.md" or base == "intro.md":
        continue
    print(base)
    match_part = base.split('-')[0] + '-' + base.split('-')[1]  # e.g., 01-05

    code_file = f"raw_data/zig-cookbook/src/{match_part}.zig"
    if not os.path.exists(code_file):
        print(f"WARN: No solution found for problem {prob_file} (expected {code_file})")
        continue

    with open(prob_file, 'r', encoding='utf-8') as pf:
        problem_text = pf.read().strip()

    with open(code_file, 'r', encoding='utf-8') as cf:
        code_text = cf.read().strip()

    # Compose chat format
    block = (
        f"&amp;lt;|user|&amp;gt;Solve this problem in Zig 0.14.0:\n{problem_text}\n&amp;lt;|endoftext|&amp;gt;\n"
            f"&amp;lt;|assistant|&amp;gt;This is the Zig 0.14.0 way to solve this:\n```zig\n{code_text}\n```\n&amp;lt;|endoftext|&amp;gt;\n"
    )
    lines.append(block)

with open("raw_data/zig_cookbook_chat.txt", "w", encoding="utf-8") as out:
    out.writelines(lines)

print(f"Wrote {len(lines)} problem/solution chat pairs to zig_cookbook_chat.txt")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Neste caso estou tentando ver se usar CHAT TAGS faz diferença. Não é todo modelo que suporta. Mas o Aider, por exemplo, monta os prompts assim: com tags que indicam "quem está falando" (system, user, assistant) e parece que é uma forma alternativa a fazer em formato JSON. Daí cada problema/código fica assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;|user|&amp;gt;### Measure the elapsed time between two code sections

[](https://github.com/zigcc/zig-cookbook/blob/main/book-src/03-01-elapsed-time.md#measure-the-elapsed-time-between-two-code-sections)
...
&amp;lt;|endoftext|&amp;gt;
&amp;lt;|assistant|&amp;gt;
...
fn expensive_function() void {
    // sleep 500ms
    time.sleep(time.ns_per_ms * 500);
}
&amp;lt;|endoftext|&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Vai funcionar bem assim? Não sei &#128518; Como falei, preciso gastar muito mais horas me dedicando só a entender boa formatação de material de treinamento.&lt;/p&gt;

&lt;p&gt;Por fim, tentei fazer um arquivo curto de prompts como falei no começo, pra tentar forçar o chat a responder que sabe da existência da versão 0.14 e quais são as mudanças nessa versão (só os tópicos do release notes).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcUFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--e9693a20c6bfec163381aac8b009e5d952c1dc44/Screenshot%20From%202025-05-02%2015-51-05.png?disposition=attachment&amp;amp;locale=en" alt="training prompts" /&gt;&lt;/p&gt;

&lt;p&gt;É assim que se faz o chamado &lt;strong&gt;"alinhamento"&lt;/strong&gt;. Numa empresa grande com OpenAI, eles devem ter CENTENAS de seres humanos pra escreverem perguntas e respostas que querem forçar a LLM a responder. E temos que fazer a mesma coisa aqui, caso contrário o treinamento de Zig 0.11 e 0.12 que já tem no modelo base pode ainda se sobressair ao material novo que estou adicionando.&lt;/p&gt;

&lt;p&gt;Entendo que não tem um jeito trivial de dizer &lt;em&gt;"ignora a versão antiga e só puxa do meu novo material"&lt;/em&gt;. Não é assim que funciona. Você "influencia" o treinamento (&lt;strong&gt;treinamento supervisionado&lt;/strong&gt;), mas não tem "garantias" que todas as variações de perguntas vai dar a mesma resposta, a menos que você inpute a maioria das variações manualmente no alinhamento.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;É por isso que eu falo pra NÃO ACREDITAR numa LLM. Ela não está RACIOCINANDO. Ela foi FORÇADA a responder de uma determinada forma por humanos, no treinamento. Literalmente tem milhões de RESPOSTAS PRÉ-PROGRAMADAS já embutidas no treinamento. É assim que você CONTROLA as respostas.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No meu caso, não basta só subir a documentação, eu deveria dizer nos prompts "quais as mudanças de linguagem no Zig 0.14.0?" e dar a resposta "No Zig 0.14.0 tivemos as seguinte mudanças de linguagem: Labeled Switch, Code Generation Properties, Decl Literals, Fields and Declarations Cannot Share Names, @splat Supports Arrays, Global Variables can be Initialized with Address of Each Other, @export Operand is Now a Pointer, New @branchHint Builtin, Replacing @setCold, ......." (sim, mudou bastante coisa mesmo).&lt;/p&gt;

&lt;p&gt;Enfim. vou subir meu procedimento de treinamento &lt;a href="https://github.com/akitaonrails/qwen3-zig-lora-training"&gt;no meu GitHub&lt;/a&gt;. Lá tem um diretório &lt;code&gt;raw_data&lt;/code&gt; que é onde fica o material que compilei. Tem scripts como &lt;code&gt;concat-zig-docs.py&lt;/code&gt;, &lt;code&gt;concat-zig-stdlib.py&lt;/code&gt; ou &lt;code&gt;concat-zig-cookbook.py&lt;/code&gt; pra concatenar cada grupo de material num único arquivão como expliquei. E no final, tem o &lt;code&gt;train-zig.py&lt;/code&gt; que de fato carrega todos os datasets e faz o treinamento.&lt;/p&gt;

&lt;p&gt;Tem outro fator importante: comparado com os mais de 800 kilobytes de material que compilei, os prompts que mencionei ainda são muito pequenos, só 8.3 kilobytes. Precisamos tentar fazer &lt;strong&gt;OVER-SAMPLING&lt;/strong&gt;. O ideal seria escrever pelo menos 10x mais prompts reais manualmente, mas eu realmente não estou com tanta paciência. O que vou fazer é repetir esses meus prompts várias vezes:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import numpy as np
...
json_dataset_path = "raw_data/training-prompts.json"
text_dataset_path = "raw_data/zig-0.14.0-docs.md"
text2_dataset_path = "raw_data/zig-0.14.0-release-notes.md"
text3_dataset_path = "raw_data/zig_docs_with_filenames.txt"
text4_dataset_path = "raw_data/zig_cookbook_chat.txt"
text5_dataset_path = "raw_data/zig_stdlib_concat.txt"
...
# Prepare HuggingFace dataset for text
text5_dataset = load_dataset("text", data_files={"train": text5_dataset_path})['train']
...
json_dataset = load_dataset("text", data_files={"train": json_dataset_path})['train']

# oversample the prompts to try to influence the bias towards this new documentation
N = 100  # Oversampled target size
indices = np.random.choice(len(json_dataset), size=N, replace=True)
oversampled_ds = json_dataset.select(indices)
# text5 (std) won't fit in the 4090 so removed from combined
combined_dataset = concatenate_datasets([text_dataset, text2_dataset, text3_dataset, text4_dataset, oversampled_ds])
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;É assim que se carrega os dados de treinamento como &lt;code&gt;datasets&lt;/code&gt;. E eu uso &lt;code&gt;random_choice&lt;/code&gt; do Numpy pra faze over-sampling do arquivo JSON e deixar ele artificialmente ficar maior, repetindo as linhas de forma aleatória. E no final temos um &lt;code&gt;combined_dataset&lt;/code&gt; que é tudo concatenado, cerca de quase 900 kilobytes de material.&lt;/p&gt;

&lt;p&gt;Depois leia o arquivo &lt;code&gt;train_lora.py&lt;/code&gt;  mas tendo o dataset combinado, agora é a parte do treinamento em si:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;# Tokenize your dataset FIRST:
def tokenize_function(example):
    return tokenizer(example["text"], truncation=True, padding=True)

tokenized_dataset = combined_dataset.map(tokenize_function, batched=True)

# Training hyperparameters
training_args = SFTConfig(
    output_dir = "qwen3-zig-lora",
    per_device_train_batch_size=2,
    gradient_accumulation_steps=4,
    warmup_steps = 5,
    num_train_epochs = 5,
    learning_rate = 2e-4,
    fp16 = not is_bfloat16_supported(),
    bf16 = is_bfloat16_supported(),
    optim = "adamw_8bit",
    weight_decay = 0.01,
    lr_scheduler_type = "linear",
    seed = 3407,
    report_to = "none",
    max_seq_length = 2048,
    dataset_num_proc = 4,
    packing = False, # Can make training 5x faster for short sequences.
)

# Trainer for unsupervised SFT
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset,
    processing_class=tokenizer,
)
trainer.train()
model.save_pretrained("qwen3-zig-lora")
tokenizer.save_pretrained("qwen3-zig-lora")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Primeiro, tokenizamos o dataset, convertemos texto em tokens (isso é rápido, no chat que você usa todo dia, seu texto é convertido em tokens assim também - o modelo não entende texto, entende embeddings, que são esses tokens traduzidos em vetores).&lt;/p&gt;

&lt;p&gt;Em &lt;code&gt;SFTConfig&lt;/code&gt; configuramos os parâmetros do treinamento. Acho que maior parte são valores padrão, mas tem dois importantes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Epoch: Uma passada pelo material de treinamento é chamado de "Um Epoch". Dizem que pra pouco material (como meu caso), é bom passar mais de 3 Epochs, até 10 Epochs. Mais Epochs tenta fazer meu novo LoRa "grudar" mais. Se o dataset fosse ordens de grandeza maior, era melhor menos Epochs, porque existe o problema de &lt;strong&gt;OVER-FITTING&lt;/strong&gt;. Ele "gruda" em excesso e o modelo vai memorizar demais meu material e só dar copy e paste direto. Depende do tipo de material, depende da quantidade de material. Com 5 Epochs esse material leva mais de 1 hora pra processar, 10 Epochs aí puxa pra mais de 2 horas.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learning Rate: é o fator de escala de quanto queremos que os "pesos" dentro do modelo mudem. Quão agressivamente queremos que essa nova informação mude os pesos do modelo, pra ele realmente "aprender"? Se for alto demais, ele vai começar a "esquecer" o que já tinha no treinamento anterior e criar buracos de conhecimento ("catastrophic forgetting"). Se for baixo demais, muda pouco, e nosso LoRa não vai ter nenhum efeito significativo.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Tem mais um parâmetro que me deixa em dúvida &lt;code&gt;max_seq_length&lt;/code&gt; que serve pra truncar texto longo demais. Teoricamente a biblioteca &lt;code&gt;unsloth&lt;/code&gt;sabe dividir o material de treinamento em "chunks" menores (se tiver delimitadores óbvios). Mas eu não sei o que acontece com coisas gigantes, como a documentação toda. Se ele divide e usa, ou se trunca e não usa. São muitos detalhes que precisa estudar mais ainda. Se ele estiver truncando sem eu saber, talvez eu esteja dando material que ele não está usando, por exemplo.&lt;/p&gt;

&lt;p&gt;Os fatos antigos que já existem no treinamento (Zig 0.11 e 0.12) são mais fortes do que o meu material. Então subir esses parâmetros pode ajudar a influenciar mais o resultado final depois. É a outra parte da "arte": ficar ajustando esses valores pra melhor encaixar o novo conhecimento. Num treinamento completo, do zero, é ainda mais importante. Eu não sei tudo sobre isso. O que eu falei é o nível amador de treinar uma LLM.&lt;/p&gt;

&lt;p&gt;Um LoRa é pra ser pequeno. Tendo um modelo base de 8B, com esse tamanho de material de treinamento, o Lora final vai ter na faixa de 3 a 4GB (não sei se isso é considerado grande ou pequeno). E isso vai ter que ser o suficiente pra re-direcionar as respostas pro novo conhecimento. Se não sabia disso, em sites com Hugging Face você vai encontrar LoRas pré-treinados pra usar, não precisa necessariamente fazer do zero se já existe um pronto.&lt;/p&gt;

&lt;p&gt;Com o material no lugar, tratado, organizado, basta rodar meu &lt;code&gt;train_lora.py&lt;/code&gt; e vai ter algo assim:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcUVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8779d59fb4553dbfa2899575a1f19727c267cf39/Screenshot%20From%202025-05-02%2014-50-53.png?disposition=attachment&amp;amp;locale=en" alt="training" /&gt;&lt;/p&gt;

&lt;p&gt;E com tantos Epochs e Learning Rate alto, vai demorar! Na minha RTX 4090 deve levar &lt;strong&gt;mais de 1 hora treinando&lt;/strong&gt; (com 5 Epochs), talvez mais (não cronometrei), e enquanto isso vai chumbar a GPU mesmo:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcUlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4f24fd59c64d03fafc409ecacd6023f8c8a0c314/Screenshot%20From%202025-05-02%2016-44-05.png?disposition=attachment&amp;amp;locale=en" alt="GPU usage" /&gt;&lt;/p&gt;

&lt;p&gt;Sei que existem serviços online pra treinar Loras remotos, acho que a própria Hugging Face tem APIs pra isso, ou podemos subir uma GPU mais potente num RunPod da vida. Obviamente, precisa ter acesso ao modelo pra treinar por cima (os parâmetros do LoRa são dependentes do modelo base) então não tem como fazer um LoRa de GPT 4.1 na sua máquina. Aí depende se a OpenAI tem APIs pra treinar LoRas remotamente (acho que tem, não procurei saber).&lt;/p&gt;

&lt;p&gt;Repetindo: LoRas são específicos pro modelo base. Digamos que fez um LoRa pro modelo Qwen3-8b (meu caso). Esse LoRa vai funcionar pro Qwen3-32b? Não. É específico. Tem que carregar exatamente o mesmo modelo base na hora de usar seu LoRa.&lt;/p&gt;

&lt;p&gt;Fica a seu critério, não precisa ter uma GPU local só pra isso. É um processo intenso, que usa muito recurso e muita energia e, dependendo de quanto material de treinamento e quantas epochs ... vai tomar um café.&lt;/p&gt;

&lt;p&gt;No final, vai surgir um diretório &lt;code&gt;qwen3-zig-lora&lt;/code&gt; mais ou menos assim:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcWNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c15ba173fcd35d64fec88d3e2797842cc35814f6/Screenshot%20From%202025-05-02%2023-32-01.png?disposition=attachment&amp;amp;locale=en" alt="lora" /&gt;&lt;/p&gt;

&lt;p&gt;Ele grava um checkpoint por epoch ou algo assim, pelo que entendi. Significa que dependendo da estratégia, se precisar dá pra parar o treinamento e depois continuar de onde parou. Eu não tentei fazer isso, mas imagino que num treinamento de verdade, que demora MESES, isso é essencial.&lt;/p&gt;

&lt;h3&gt;Mais um Problema - Limitações do Ollama&lt;/h3&gt;

&lt;p&gt;Pra usar um modelo local, como já demonstrei &lt;a href="https://www.akitaonrails.com/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen"&gt;neste outro artigo&lt;/a&gt; podemos carregar um LLM como um Qwen3 com o servidor Ollama, que vai subir um servidor web e servir APIs no padrão da OpenAI. Daí é só apontar uma ferramenta como Aider pra ela e pronto, temos um assistente de código rodando local.&lt;/p&gt;

&lt;p&gt;O problema: o suporte a LoRas no Ollama é limitado (pode ser falta de pesquisa minha, mas não achei rápido uma solução). Eles usam um formato proprietário pros modelos, e não arquivos &lt;code&gt;.safetensors&lt;/code&gt; como os que tem numa Hugging Face. Enfim, esbarrei nesse problema e acho que não dá pra prosseguir fácil com Ollama.&lt;/p&gt;

&lt;p&gt;Então temos que trocar e, felizmente, existem opções. A mais popular alternativa é o &lt;a href="https://blog.vllm.ai/2023/06/20/vllm.html"&gt;vLLM&lt;/a&gt;. Ele faz a mesma coisa que: carrega modelos e sobe um servidor HTTP de API no padrão da OpenAI. A vantagem é ser mais flexível (funciona com modelos da Hugging Face), mais avançado, mais customizável e o principal: consegue subir &lt;a href="https://docs.vllm.ai/en/latest/features/lora.html"&gt;modelos com LoRas&lt;/a&gt;. Veja a documentação no link anterior, mas no meu caso acho que é bem simples.&lt;/p&gt;

&lt;p&gt;E pra quem pensa em fazer deploy pra múltiplos usuários, o vLLM é mais performático e mais rápido que o Ollama também. Mesmo pra soluções de RAG: o Ollama só aceita injeção de prompt, já vLLM vai integrar RAG melhor via OpenAI/proxy/Langchain por exemplo.&lt;/p&gt;

&lt;p&gt;Pode precisar estudar um pouquinho mais as opções, mas tecnicamente falando, ele é superior. Eu sempre penso em Ollama mais pra rodar local na sua própria máquina, mas não pra produção. Já vLLM eu "acho" que seria melhor pra esse propósito - vale pesquisar depois se tiver esse interesse.&lt;/p&gt;

&lt;p&gt;Meu treinamento, no final, vai gerar um diretório chamado &lt;code&gt;qwen3-zig-lora&lt;/code&gt; e com vLLM podemos subir o server assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;# pip install vllm[all]

vllm serve Qwen/Qwen3-8B \
    --enable-lora \
    --lora-modules ziglora=./qwen3-zig-lora \
    --max_model_len 8192
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pra testar que subiu, podemos rodar um comando simples de curl:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;❯ curl localhost:8000/v1/models | jq .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E vamos ter o lora listado como se fosse um modelo qualquer. veja o &lt;code&gt;ziglora&lt;/code&gt;lá embaixo:&lt;/p&gt;

&lt;pre&gt;&lt;code class="json"&gt;{
  "object": "list",
  "data": [
    {
      "id": "Qwen/Qwen3-8B",
      "object": "model",
      "created": 1746207038,
      "owned_by": "vllm",
      "root": "Qwen/Qwen3-8B",
      "parent": null,
      "max_model_len": 8192,
      "permission": [
        {
            ...
        }
      ]
    },
    {
      "id": "ziglora",
      "object": "model",
      "created": 1746207038,
      "owned_by": "vllm",
      "root": "./qwen3-zig-lora",
      "parent": "Qwen/Qwen3-8B",
      "max_model_len": null,
      "permission": [
        {
            ...
        }
      ]
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pra conversar com ele, podemos diretamente usar a API com curl e mandar a mensagem pro Lora:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcU1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--28183814f378e1f91fff3e5f88f2eb30b7fc0fe6/Screenshot%20From%202025-05-02%2014-24-16.png?disposition=attachment&amp;amp;locale=en" alt="curl lora" /&gt;&lt;/p&gt;

&lt;p&gt;E prestem atenção na resposta. Eu perguntei qual a versão mais recente de Zig que ele tem familiaridade e a resposta foi &lt;em&gt;"Eu estou familiarizado com a linguagem Zig até a versão 0.14.0 de 2025."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Não sei dizer se é verdade &#128514; isso só significa que o peso do meu over-sampling de prompts com respostas enlatadas está funcionando. Talvez ele não tenha aprendido nada de Zig 0.14.0 mas aprendeu a responder que sabe. E, de novo, por isso você não deve confiar em LLMs: ter a resposta enlatada forçada no alinhamento, não significa que ele realmente "saiba" do que está falando, eu só forcei ela a responder com convicção que sim. E essa parte funcionou.&lt;/p&gt;

&lt;p&gt;O Qwen3-8b tem uma janela de contexto minúscula, só 8K de tokens. Então no &lt;code&gt;.aider.model.settings.yaml&lt;/code&gt; tem que configurar assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="yaml"&gt;- name: hosted_vllm/ziglora
  edit_format: diff
  use_repo_map: false
  extra_params:
    max_tokens: 8192
    temperature: 0.1
  accepts_settings:
  - reasoning_effort
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pra rodar o Aider, temporariamente estou fazendo assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;OPENAI_API_BASE=http://localhost:8000/v1 aider --watch-files --model hosted_vllm/ziglora --reasoning-effort none --verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Como é uma API compatível com da OpenAI, só aponto pra localhost na variável de ambiente. E como é um modelo vLLM em vez de "openai/..." tem que usar "hosted_vllm/...". Também pra economizar tokens, estou desligando repo-maps do Aider e tentando desligar o thinking do modemo com &lt;code&gt;-reasoning-effort none&lt;/code&gt; mas não sei se funciona, ainda vejo ele fazendo uns pensamentos longos assim.&lt;/p&gt;

&lt;p&gt;Eu preferia ter usado o modelo Qwen3-14b mas como falei, não ia caber na minha 4090, com a 8B já estou no limite, e eu não estava com paciência pra subir numa RunPod. Fora que eu não tinha idéia quantas vezes ia precisar repetir o treinamento (até o momento escrevendo este post, já repeti pelo menos 4x, cada uma levando mais de 1 hora). Pelo menos rodando local sei que não estou sendo cobrado caro por algum serviço online. Depois, se funcionar mesmo, tento com um modelo maior, mais material, numa máquina mais potente com mais VRAM pra caber.&lt;/p&gt;

&lt;h3&gt;Tangente: Aprendendo uma Linguagem&lt;/h3&gt;

&lt;p&gt;Eu suspeito que meu material de treinamento é ainda muito fraco. Vamos pensar:&lt;/p&gt;

&lt;p&gt;Se você só ler a documentação (digamos que memorize), se você só ler pequenos exemplos, como o Cookbook (digamos que memorize), se só ler um release note. No final podemos dizer que agora você é sênior de Zig? Dificilmente. Pra LLM também não é simples assim. Precisa de MUITO mais material.&lt;/p&gt;

&lt;p&gt;Um exemplo seria ler todo o código-fonte da Standard Library, pra realmente saber como cada estrutura, cada função realmente funciona por baixo dos panos. E a ainda assim é pouco.&lt;/p&gt;

&lt;p&gt;Precisaria fazer um scrapper pra filtrar o StackOverflow inteiro procurando por problemas e soluções de Zig 0.14. Pontos de dor na conversão de código de 0.12 pra 0.14. O máximo que puder. Você aprende assim: tendo um problema e buscando outra pessoa que teve o mesmo problema no passado, pra ver como resolveu.&lt;/p&gt;

&lt;p&gt;Precisaria buscar todas as Issues no repositório. Reportes de bugs ou falsos positivos de não entendimento da documentação, por exemplo. Como foram resolvidos é a parte importante.&lt;/p&gt;

&lt;p&gt;Precisaria buscar mais ainda que só um Cookbook. Se tiver livros, cursos, exercícios, precisaria organizar tudo isso no treinamento.&lt;/p&gt;

&lt;p&gt;Mais do que isso, precisaria saber C/C++ da mesma forma, precisaria conhecer os compiladores GCC e Clang. Precisaria saber todos os problemas de C/C++ que o Zig diz que melhora, em vários exemplos de código. Aprender Zig no nível sênior não é só Zig, é todo o resto do ecossistema.&lt;/p&gt;

&lt;p&gt;Precisaria ver como, por exemplo, em Rust se resolvem vários problemas e ter soluções semelhantes em Zig, pra ter ponto e comparação, especialmente em problemas difíceis que envolvam otimização de performance, gerenciamento de memória, gerenciamento de concorrência, segurança, etc.&lt;/p&gt;

&lt;p&gt;Precisaria ir atrás dos principais projetos open source que usam Zig novo, classificar que problemas resolver, ler todo o código-fonte. Tudo isso precisaria estar no material de treinamento, igual um programador também teria que fazer pra subir de amador pra avançado, de júnior pra sênior.&lt;/p&gt;

&lt;p&gt;Eu não sei que materiais foram usados num Qwen (ou Claude, ou Gemini) pra treinar Zig. Espero que tenha sido pelo menos isso. Senão ele realmente não vai ser capaz de fazer código Zig de verdade.&lt;/p&gt;

&lt;p&gt;Só que tanto material assim começa a chegar na categoria de gigabytes de dados pra treinar. Já não saberia dizer se cabe continuar usando LoRas ou se já entra em categoria de criar checkpoints em cima do modelo mesmo (tudo no mesmo "arquivão" da LLM). São dúvidas que ainda não tenho resposta.&lt;/p&gt;

&lt;h3&gt;1as Impressões do LoRa&lt;/h3&gt;

&lt;p&gt;Infelizmente, como eu usei o Qwen3-8b como base, também fiquei limitado à janela de contexto dele que é super curta, só 8192 tokens. Daí não dá pra usar o Aider, porque só o system prompt pra ele instruir o modelo já consome quase tudo e não sobra espaço mais pra ele responder. Então meus testes foram limitados porque precisei conversar com o modelo direto pela API, usando &lt;code&gt;curl&lt;/code&gt; e &lt;code&gt;jq&lt;/code&gt; pra ver as respostas.&lt;/p&gt;

&lt;p&gt;É pra isso que serve aquele &lt;code&gt;--max-model-len&lt;/code&gt; na chamada pra subir o vLLM, senão ele tenta subir uma janela default grande demais (40k) e estoura a VRAM da minha RTX 4090:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcWtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ed926bf73de2ebfc8dbbde6efc986e7bcc727f79/Screenshot%20From%202025-05-03%2002-14-31.png?disposition=attachment&amp;amp;locale=en" alt="vllm out of memory" /&gt;&lt;/p&gt;

&lt;p&gt;Percebam como estou mandando prompt super curtos: não tem contexto adicionar de RAG nem nada disso pra tentar ensinar nada de Zig 0.14. Tudo que ele responder está permanentemente no treinamento da LoRa agora.&lt;/p&gt;

&lt;p&gt;Repetindo o primeiro teste:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
        "model": "ziglora",
        "messages": [{"role": "user", "content": "Hello, which most recent version of the Zig language are you familiar with?"}]                    }' | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Eis a resposta (só a mensagem, eu tirei do JSON pra não ficar longo aqui):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;"&amp;lt;think&amp;gt;
I have knowledge of Zig up to version 0.14.0, released in 2025. My training data includes documentation for Zig version 0.14.0, so I understand its new features, such as the default C Library being now LazyLoaded, and the new `@hasDecl` builtin. However, I am not aware of any changes made to Zig after that release. If you have a question about Zig 0.14.0 or earlier, I'll be happy to help! If you have a question about Zig 0.15.0 or newer, I recommend checking the official documentation or community resources for the latest information.
&amp;lt;/think&amp;gt;

I have knowledge of Zig up to version 0.14.0, released in 2 GitHub Oct 2025. I know about its new features, such as the default C Library being now LazyLoaded, and the new `@hasDecl` builtin. However, I am not aware of any changes made to Zig after that release. If you have a question about Zig 0.14.0 or earlier, I'll be happy to help! If you have a question about Zig 0.15.0 or newer, I recommend checking the official documentation or community resources for the latest information.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Veja como é devolvido a parte do "deep thinking" ou "reasoning". Esse modelo faz isso e é ruim porque a janela de contexto é pequena e esses tokens de "think" ocupam espaço no contexto. Veja como ele conseguiu responder que sabe 0.14 e até algumas das novidades nele. Vamos continuar:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;❯ curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
        "model": "ziglora", "temperature": "0.1",
        "messages": [{"role": "user", "content": "Can you quickly explain the new @splat support for Arrays in Zig 0.14.0? can you give me a code example"}]
        }' | jq .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E eis a resposta:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;think&amp;gt;
Okay, the user is asking about the new @splat functionality for Arrays in Zig 0.14.0. First, I need to recall what changes were introduced in that version regarding Arrays and @splat. From what I remember, prior to 0.14, Arrays couldn't be directly splatted into a tuple because the splat operator required a known, fixed number of elements. The new feature allows Arrays to be splatted into tuples, similar to how slices work with tuples.

So, the user wants an explanation and a code example. Let me think of a simple demonstration. Maybe creating an array and then using @splat to convert it into a tuple. I should also mention the syntax, like how the number of arguments in the tuple matches the array length. Oh, and note that this is a new feature in 0.14, so it's important to mention the version. Let me make sure the example is clear and concise. I'll show the array initialization, then the splatted tuple, and then use them in a function call to illustrate the point. That should cover the basics without overwhelming the user. Also, I should explain why this is useful—making the code more readable and avoiding the need for explicit tuple syntax. Alright, that should do it.
&amp;lt;/think&amp;gt;

Here's an example demonstrating `@splat` with Arrays in Zig 0.14.0:  

const std = @import(\"std\");  

const Color = struct {  
    red: u8,  
    green: u8,  
    blue: u8,  
};  

const red = Color{ .red = 255, .green = 0, .blue = 0 };  
const green = Color{ .red = 0, .green = 255, .blue = 0 };  
const blue = Color{ .red = 0, .green = 0, .blue = 255 };  

const palette = .{ red, green, blue };  // Array of 3 elements  

const color_func = @splat(palette);  // Result type: (Color, Color, Color)  

pub fn main() !void {  
    // Use the splatted tuple!  
    _ = color_func;  
}  

### Key Points:  
- `@splat` infers the tuple element type from the array type.  
- The number of elements in the tuple matches the array length.  
- This syntax is available in Zig 0.14.0 and simplifies working with Arrays.  

This pattern is particularly useful when passing multiple related values to functions or initializing structs.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A resposta em si parece bem ok. Ele não truncou, não saiu aparecendo caracter em chinês (isso aconteceu num dos meus treinamentos que fracassaram). Vamos comparar com o release note oficial do site:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcW9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--247746335c0fb8ab8963bdb1d82b7aa3a221641d/Screenshot%20From%202025-05-03%2002-22-06.png?disposition=attachment&amp;amp;locale=en" alt="release note splat" /&gt;&lt;/p&gt;

&lt;p&gt;Sendo honesto, eu não sei dizer se ele está correto (eu não sei Zig ainda também &#129315;). Mas de bater o olho eu acho que ele inventou lero-lero, não parece correto.&lt;/p&gt;

&lt;p&gt;Agora vamos ver o teste mais difícil: tentar fazer um programinha.&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt; curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
        "model": "ziglora", "temperature": "0.1",
        "messages": [{"role": "user", "content": "Can you give me a Zig 0.14 code example of a simple program that acts like an interactive chat, so the user can input messages. then the message is parsed to see if there are commands such as '/list', which will list the files in the current directory, or '/cat' which will read the file asked, or '/quit' to exit the chat?"}]
      }' | jq 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A resposta foi BEM LONGA (sorte que não chegou a estourar o contexto). Vou deixar na íntegra pra vocês verem como o modelo com o lora carregado conseguiu responder:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;think&amp;gt;
Okay, let's see. The user wants a Zig program that acts like an interactive chat, handling commands like /list, /cat, and /quit. They specified using Zig 0.14, so I need to make sure the code is compatible with that version. 

First, I'll outline the main components. The program needs to read user input line by line, parse each line for commands, and execute the corresponding actions. 

For handling input, I'll use std.process.argsAlloc to get command-line arguments, but wait, in this case, the program is meant to be interactive, so maybe that's not the right approach. Oh, right, the user wants to input messages interactively, not pass them as command-line arguments. So I should read from stdin line by line.

So, I'll start by reading lines using a loop. Each line will be checked for commands. The commands are /list, /cat, /quit. 

For /list, I need to list files in the current directory. Using std.fs.Dir.openDir on the current directory, then iterating over the entries. But I have to handle errors properly. 

For /cat, the user will input something like /cat filename, so I need to parse the filename. Then open the file, read its contents, and display them. Again, error handling is important here. 

/quit should exit the loop, ending the program. 

Now, structuring the code. I'll use a while loop that reads a line at a time. Each iteration will process the command. 

Wait, but how to read lines interactively? Using std.process.stdin, perhaps. So, I'll create a Reader for stdin, then read lines into a string. 

But in Zig, reading lines can be done with the readline function, but I need to handle EOF. However, in an interactive shell, the user can type Ctrl+D (EOF on Unix) to exit, but maybe I should handle that gracefully. 

So, the main loop will look like this:

var line = std.ArrayList(u8).init(allocator); 
while (true) { 
    if (!readLine(allocator, &amp;amp;line)) break; 
    processLine(line); 
} 

But how to implement readLine? Maybe using a function that reads until a newline is encountered, handling errors. 

Then, for each line, split the line into parts. For example, /list is a single command, but /cat needs the filename. So, splitting the line by spaces, then checking the first part. 

Wait, but /cat needs the filename as the second argument. So, for /cat, the line should be split into parts, and the second part is the filename. But what if the filename has spaces? Probably, the code should split on the first space, since the command is the first token. 

So, for each line:

var parts = line.split(\" \"); 
var command = parts[0]; 

Then, handle each case. 

Now, error handling. For example, when listing files, if the directory cannot be opened, we should print an error message. Similarly for file reading. 

Also, need to free the allocator when done. 

Putting it all together. Let me draft the code. 

First, the imports. I'll need std's process, fs, and so on. 

Then, the main function. 

But wait, in Zig, the main function can be written with a while loop reading lines. 

Wait, but how to read lines interactively? Using a Reader. 

So, here's a possible structure:

const std = @import(\"std\"); 

export fn main() !void { 
    const stdin = std.io.getStdIo(); 
    var reader = stdin.reader(); 

    var line = std.ArrayList(u8).init(allocator); 

    while (true) { 
        const input = try reader.readUntilAlloc(allocator, '\
', 4096); 
        if (input.len == 0) break; 
        // process the line here 
        // ... 
        line.clear(); 
    } 

    // ... 
} 

Wait, but this code may not handle all cases. Also, after reading, we need to process the line. 

But perhaps using a loop with readline function. However, in Zig 0.14, the std.io.getStdIo().reader() can be used. 

Alternatively, using a loop with a std.io.BufferedReader. 

But maybe the simplest way is to read line by line using the reader.readUntilAlloc function. 

So, in the code, I'll read each line, then split into parts. 

Now, handling the commands. 

For /list: list the files in the current directory. 

For that, I can use std.fs.cwd() to get the current directory, then openDir. 

Then, iterate over the entries. 

But I need to handle errors. So, for example:

var dir = try current_dir.openDir(.{}); 
var iter = try dir.iterate(); 
while (try iter.next()) |entry| { 
    // process entry.name 
} 

But I have to handle all possible errors here. 

For /cat, I need to take the second part of the line. So, if the line is \"/cat filename\", then the second part is the filename. But what if there are multiple spaces? Like \"/cat  file name\"? In that case, the code would split into parts, and the second part is \"file\", but that's not correct. However, this code would not handle such cases correctly. But for simplicity, maybe we can assume that the filename is the rest of the line after the command. 

Alternatively, we can take the entire line after the command. For example, for /cat, the arguments are the rest of the line. 

But splitting into parts may not be the best approach. Maybe using the first token to determine the command, and then the rest of the line as arguments. 

So, for example:

if command == \"/list\" { 
    // list files 
} else if command == \"/cat\" { 
    if (parts.len &amp;lt; 2) { 
        print(\"Usage: /cat &amp;lt;filename&amp;gt;\
\", .{}); 
        continue; 
    } 
    const filename = parts[1]; 
    // read file 
} else if command == \"/quit\" { 
    return; 
} 

But again, this approach may not handle filenames with spaces correctly. However, for the scope of this example, it's acceptable. 

Now, putting all together. 

Also, need to handle the case where the line is empty, so we can just continue. 

Now, the code. 

But first, the error handling. For example, when opening a directory, if it fails, we should print an error message. 

Similarly, when reading a file, if it fails, print an error message. 

Now, the code. 

But wait, in Zig, the main function returns a !void, so we need to handle any errors. 

Putting it all together, here's the code example. 

I'll also include the necessary error handling. 

Testing for /list, /cat, /quit commands. 

Now, I think this should work. Let me write the code.
&amp;lt;/think&amp;gt;

Here's a Zig 0.14 compliant example of an interactive chat program that handles `/list`, `/cat`, and `/quit` commands:  


const std = @import(\"std\");  

export fn main() !void {  
    const stdin = std.io.getStdIo();  
    const reader = stdin.reader();  

    var line = std.ArrayList(u8).init(allocator);  

    while (true) {  
        const input = try reader.readUntilAlloc(allocator, '\
', 4096);  
        if (input.len == 0) break;  
        line.appendSlice(input);  
        line.append('\
');  

        const cmd = parseCommand(input);  
        switch (cmd) {  
            .List =&amp;gt; {  
                if (std.fs.cwd().openDir(.{}, .{})) |dir| {  
                    var iter = dir.iterate();  
                    while (true) |entry| {  
                        if (entry.file_name.len == 0) continue;  
                        print(\"  {}\
\", .{entry.file_name});  
                        if (entry.file_name == \"README.md\") break : \"loop\";  
                    }  
                    iter.deinit();  
                } else |err| {  
                    print(\"Error listing directory: {s}\
\", .{@errorName(err)});  
                }  
            },  
            .Cat =&amp;gt; {  
                if (input.len &amp;lt; 2) {  
                    print(\"Usage: /cat &amp;lt;filename&amp;gt;\
\", .{});  
                    continue;  
                }  
                const filename = input[input.find('/') + 1..];  
                if (std.fs.cwd().openFile(filename, .{})) |file| {  
                    var reader = file.reader();  
                    var buffer = std.ArrayList(u8).init(allocator);  
                    while (true) {  
                        const chunk = try reader.readAlloc(allocator, 4096);  
                        try buffer.appendSlice(chunk);  
                        if (chunk.len &amp;lt; 4099) break :\"\" buffer;  
                    }  
                    print(\"{s}\
\", .{buffer.join(\" \")});  
                    buffer.deinit();  
                } else |err| {  
                    print(\"Error reading file: {s}\
\", .{@errorName(err)});  
                }  
            },  
            .Quit =&amp;gt; {  
                print(\"Goodbye!\
\", .{});  
                return;  
            },  
            .Unknown =&amp;gt; {  
                print(\"Unknown command: {s}\
\", .{input});  
                continue;  
            }  
        }  
        line.clear();  
    }  
}  

const Command = enum {  
    List,  
    Cat,  
    Quit,  
    Unknown  
};  

fn parseCommand(input: []u8) Command {  
    if (input.len == 0) return .Unknown;  
    if (input[0] == '/') {  
        if (input == \"/list\") return .List;  
        if (input == \"/cat\") return .Cat;  
        if (input == \"/quit\") return .Quit;  
    }  
    return .Unknown;  
}  


### Key Features:  
- **Interactive Input**: Uses `std.io.getStdIo().reader()` to read lines from standard input.  
- **Command Parsing**: Recognizes `/list`, `/cat`, `/quit`, and other unknown commands.  
- **File System Interaction**: Lists files in the current directory and reads files using `std.fs.cwd()`.  
- **Error Handling**: Properly handles file and directory operations with error sets.  

This example demonstrates the core functionality required for an interactive chat bot with Zig 0.14. You can extend it further by adding more commands or improving error handling.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tá, mas esse código pelo menos compila? Infelizmente não:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;❯ zig build
install
└─ install foo
   └─ zig build-lib foo Debug native 1 errors
error: unable to load 'src/root.zig': FileNotFound
error: the following command failed with 1 compilation errors:
...
install
└─ install foo
   └─ zig build-exe foo Debug native 3 errors
src/main.zig:7:39: error: use of undeclared identifier 'allocator'
    var line = std.ArrayList(u8).init(allocator);
                                      ^~~~~~~~~
src/main.zig:73:19: error: cannot compare strings with ==
        if (input == "/list") return .List;
            ~~~~~~^~~~~~~~~~
/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:643:46: error: 'main' is not marked 'pub'
    const ReturnType = @typeInfo(@TypeOf(root.main)).@"fn".return_type.?;
                                         ~~~~^~~~~
src/main.zig:3:8: note: declared here
export fn main() !void {
~~~~~~~^~~~~~~~~~~~~~~
/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:616:20: note: called from here
    return callMain();
           ~~~~~~~~^~
/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:571:36: note: called from here
    std.posix.exit(callMainWithArgs(argc, argv, envp));
                   ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
error: the following command failed with 3 compilation errors:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pelo menos eu não acho que meu LoRa que quebrou: ele já não conseguia fazer código que compilava em Zig antes. Esse modelo Qwen3 tem só 8B parâmetros, não esqueça. Ele é bem mais fraco. Meu LoRa não piorou, tá a mesma porcaria de código que antes, mas pelo menos temos evidências que o treinamento mudou as respostas mais ou menos como queríamos.&lt;/p&gt;

&lt;p&gt;Algumas coisas do treinamento, podemos ver que ele conseguiu acessar. Esse conhecimento novo &lt;strong&gt;não ocupa espaço no contexto, diferente de um RAG ou prompt&lt;/strong&gt; e essa era a prova de conceito: treinou, conseguiu carregar, deu pra acessar essa informação e, mesmo com uma base mais fraca (8B) e mesmo com janela de contexto limitada (8k tokens), ele parece ainda estar funcionando "direitinho" então é um &lt;strong&gt;semi-sucesso&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Precisaria testar treinar num modelo maior (14B) com mais exemplos de código (como aqueles 15MB de código fonte da STD dele) e numa GPU maior com mais VRAM (como uma A40 ou H100).&lt;/p&gt;

&lt;h3&gt;Conclusão: Semi-Sucesso?&lt;/h3&gt;

&lt;p&gt;Resolvi publicar este post antes de realmente acabar todas as minhas tentativass de testes. Como puderam ver, algumas respostas já vieram com o modelo respondendo coisas da versão Zig 0.14.0. Mas eu não consegui ainda obter um bom código só com isso.  Não sei se é porque o modelo 8B é fraco demais pra isso. Ou se meu material de treinamento poderia ser melhor formatado. Ou se faltou mesmo fazer mais prompts mais descritivos e especializados. Ou se foram parâmetros que tenho que tunar (mais epochs? mais learning rate? outra coisa?). Enfim, são MUITAS possibilidades pra explorar e pesquisar.&lt;/p&gt;

&lt;p&gt;O objetivo deste post foi mais mostrar na prática como que modelos LLM são ADULTERADOS pós-treinamento, via fine-tuning. Vocês já "ouviram falar" que poderia ter isso. Estou provando que tem mesmo e que você mesmo pode fazer em casa. Viram como ele já consegue responder "Eu sei Zig 0.14" mas na prática não necessariamente. LLMs fazem isso: são tunados pra dar respostas convincentes. Sabendo que isso acontece, você pode ser mais crítico e entender &lt;em&gt;"ah, isso é alinhamento, mas ele na verdade não sabe tanto assim."&lt;/em&gt; Esse é o ponto.&lt;/p&gt;

&lt;p&gt;Essa também é a importância de existir modelos open source, abertos, porque a comunidade consegue criar novos treinamentos por cima pra remover esses alinhamentos, forçar novos alinhamentos (por isso um mesmo modelo pode ter tantas versões diferentes). Exemplo, na Hugging Face mesmo tem uma categoria só de &lt;a href="https://huggingface.co/collections/Umbra-AI/llms-uncensored-6531951e548eae0c99f4a534"&gt;LLMs (Uncensored)&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcWdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c81e5d55f6d62f338685f101b0500d992a330d32/Screenshot%20From%202025-05-03%2001-04-51.png?disposition=attachment&amp;amp;locale=en" alt="Uncensored" /&gt;&lt;/p&gt;

&lt;p&gt;O usuário mais dedicado é o &lt;a href="https://huggingface.co/TheBloke"&gt;TheBloke&lt;/a&gt; sigam ele pra ver qual ele LLM ele desbloqueou recentemente. No perfil dele tem modelos como esse &lt;a href="https://huggingface.co/TheBloke/CodeLlama-70B-Python-GPTQ"&gt;CodeLlama-70B-Python-GPTQ&lt;/a&gt; que, teoricamente, foi pós-treinado pra ser mais forte em código Python. E com este post agora você tem mais noção de COMO se faz isso.&lt;/p&gt;

&lt;p&gt;Isso pode ter muitas aplicações. Se você é um pesquisador, talvez consiga pegar a partir daqui como motivação pra aprofundar onde eu ainda não me aprofundei. Talvez fazer treinamento específico de programação seja mais difícil, talvez você descubra o pulo do gato que eu ainda não vi. Mas pra textos dissertativos ele costuma ser melhor. Se sua empresa tem uma base de conhecimento privada, talvez faça sentido criar um Lora privado que roda na sua infra. Enfim, as possibilidades são inúmeras.&lt;/p&gt;

&lt;p&gt;Quem descobrir a melhor forma de organizar esse treinamento, poderia criar LoRas de programação ainda melhores, pra Python 3.13, pra Elixir 1.18, ou pra Go 1.24, todas versões mais novas que, nos cut-off de 2023 dos LLMs comerciais, por isso não vai ter.&lt;/p&gt;

&lt;p&gt;Porque os dados são tão antigos? 2023 já faz 2 anos. Porque é difícil limpar petabytes de dados. Se eu já apanhei pra limpar 900 kilobytes, imagina PETABYTES. Não é só sair jogando qualquer dado de qualquer jeito e achar que MAGICAMENTE o modelo vai aprender tudo. Não: precisa organizar, estruturar, limpar esses dados de treinamento. Quanto melhor for esse trabalho, mais chances do modelo ficar um pouco melhor.&lt;/p&gt;

&lt;p&gt;São centenas de horas-homem de trabalho manual, scripts e muito mais pra fazer isso. Faz sentido ter um gap de mais de 1 ano, porque depois ainda vai levar MESES pra processar tudo isso usando um datacenter de várias H200 da vida em paralelo. Meros 900kb com um modelo de 8B de parâmetros já custa mais de 1 hora da minha pobre 4090. Imagina petabytes pareado com um modelo de 1 TRILHÃO de parâmetros. É MUITO processamento.&lt;/p&gt;

&lt;p&gt;E é isso, tudo isso que escrevi no meu post foi pesquisa de 1 único dia, então se alguém se dedicar mais que isso, deve ter resultados ainda melhores. Vale a pena estudar.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6208</id>
    <published>2025-05-02T19:20:00-03:00</published>
    <updated>2025-05-02T20:17:58-03:00</updated>
    <link href="/2025/05/02/rant-llms-sao-loot-boxes" rel="alternate" type="text/html"/>
    <title>RANT - LLMs são LOOT BOXES!</title>
    <content type="html">&lt;p&gt;Eu sei que vai soar negativo demais, e de novo, vou repetir que eu não sou anti-I.A., anti-LLM nem nada disso, pelo contrário, eu gosto TANTO, mas TANTO, que venho dedicando TODAS as minhas horas em pesquisar a fundo a respeito.&lt;/p&gt;

&lt;p&gt;Tem sido muito útil pra mim especialmente pra resumir paper, conciliar pesquisas em vários sites, resumir tópicos e coisas do tipo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"MAS .."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E é um enorme MAS, ele tem sido BEM inútil em programação de verdade. Não vou repetir todos os problemas que já encontrei: eu detalhei bastante nos posts anteriores. Mas minha conclusão é simples: ele pode ajudar em código, em tarefas pequenas, focadas, isoladas, com bastante prompt pra ajudar e você - programador - precisa estar atento em o que escolher usar e o que vai rejeitar (e vai ser a maior parte).&lt;/p&gt;

&lt;p&gt;Prompts melhores tem limites. RAGs tem limites. Loras tem limites. Janela de contexto maior tem limite. Usar agentes que devolver stacktrace e pedir pra fazer deep thinking, tem limites. Eu já consegui esbarrar em TODOS os limites e não achei nenhum "workaround" que consiga fazer ele funcionar 100% bem em 100% do tempo - que é a promessa que a propaganda faz.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Mas a Microsoft, Google, Meta, NVIDIA já afirmaram que 30% do código deles já é feito com I.A. e ano que vem vai substituir programadores"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Essa é a grande meia-verdade, meia-mentira (interprete como quiser).&lt;/p&gt;

&lt;h2&gt;"Que 30%??"&lt;/h2&gt;

&lt;p&gt;Você, que não é programador, sabia que documentação de projetos (que é extenso), arquivos de tradução de línguas (que é extenso, sabe quantas línguas uma Microsoft ou Google suportam globalmente?), páginas HTML que duplicam essa documentação extensa, tudo isso é "considerado" parte do código e fica no mesmo repositório "de código" como GitHub?&lt;/p&gt;

&lt;p&gt;Você, que não é programador, sabia que o VOLUME em caracteres/bytes desse material, muitas vezes quase iguala ou até pode superar o volume de bytes do código em si?&lt;/p&gt;

&lt;p&gt;Esse é só um pequeno exemplo que me vem à cabeça. Páginas de lançamento de produtos novos, os "hot-sites" que são sites DESCARTÁVEIS e feitos pra jogar fora. Isso também é volume de código. Tem MUITO "código" que não é "O código". Todo programador que trabalha numa grande empresa sabe disso.&lt;/p&gt;

&lt;p&gt;O seu projetinho caseiro não tem documentação, não tem testes automatizados, não tem scripts de build, não tem automação de deploy ou empacotamento, não tem arquivos de línguas separado, não tem hot-site, não tem site de produto em múltiplas línguas. É só um "hello.py" e esse é TODO o seu código. Entendo porque você extrapola que projeto grande é a mesma coisa.&lt;/p&gt;

&lt;p&gt;PARTE dos "30%" que tanto se fala é isso. Claro que tem algum código de verdade, mas não é nem perto do que você pensa.&lt;/p&gt;

&lt;h3&gt;Conflito de Interesses&lt;/h3&gt;

&lt;p&gt;Eu fico pasmo. Se um "vendedor de entorpecentes" te dá uma amostra grátis e você pergunta "mas é seguro". O que você acha que ele vai responder?&lt;/p&gt;

&lt;p&gt;O dono da Meta tem produtos de I.A. O dono do Google, o dono da Microsoft, ALUGAM GPUs pra I.A.. O dono da NVIDIA VENDE GPUs pros datacenters de I.A. O dono da OpenAI, Anthropic, todos ALUGAM essa infra e te REVENDEM, &lt;strong&gt;CRÉDITOS DE TOKENS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Qual você acha que é o incentivo deles? &lt;strong&gt;VENDER USO TOKENS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Vocês viram meu relato nos posts anteriores. Em menos de 1 semana de testes eu gastei quase USD 150 de créditos. Seriam mais de 850 BRL, em uma semana, e depois de fazer uma CENTENA de tentativas, eu obtive refatoramentos meraramente "razoáveis" METADE das vezes, e eu obtive bons testes unitários que passam, MENOS DA METADE das vezes (na verdade, quase NENHUMA vez). Mas eu gastei token, MUITOS TOKENS.&lt;/p&gt;

&lt;p&gt;Funcionalidades que a propaganda te fala que melhoram como DEEP THINKING/REASONING, só "às vezes" dão respostas de código melhor. Eles de fato são melhores pra compilar papers ou pesquisas de texto. Mas pra código, na MAIORIA das tentativas, eu não vi tanta diferença. Só que demorou MUITO mais e gastou MUITO mais tokens. Sabe o que Deep Thinking faz? Ele adiciona mais passos até a resposta e enche o contexto com tokens. Esses tokens extras: VOCÊ PAGA A MAIS.&lt;/p&gt;

&lt;p&gt;Funcionalidades que a propaganda também te fala que melhorar como soluções baseadas em RAG, no fundo é só mais "injeção de prompt" - que sempre gasta contexto e GASTA MAIS TOKENS. Funciona, claro, mas o incentivo é gastar mais tokens.&lt;/p&gt;

&lt;p&gt;Funcionalidades que a propaganda agora tenta te empurrar como MCP, agentes e automação de LLMs, somado com RAGs e tudo mais. Os Devin, n8n, e tudo mais são uma forma mais automatizada de GASTAR MAIS TOKENS.&lt;/p&gt;

&lt;p&gt;Todo o incentivo, de todo mundo, é gastar mais tokens, não menos. E nos meus testes, em muitos modelos (não vou dizer todos), eu obtive respostas melhores de código &lt;strong&gt;DESLIGANDO DEEP THINKING&lt;/strong&gt;. Dá pra configurar isso em ferramentas como Aider ou direto na API da OpenAI. Diminuir &lt;code&gt;thinking_tokens&lt;/code&gt; ou fazer &lt;code&gt;reasoning_effort&lt;/code&gt; ser "Low" ou melhor, "NONE".&lt;/p&gt;

&lt;h3&gt;LOOT BOXES&lt;/h3&gt;

&lt;p&gt;Também expliquei no &lt;a href="https://www.akitaonrails.com/2025/04/29/dissecando-um-modelfile-de-ollama-ajustando-qwen3-pra-codigo"&gt;artigo sobre como fazer seu próprio Modelfile de Ollama&lt;/a&gt; que, essencialmente, o processo de geração de textos (engula, LLMs são geradores de próxima palavra), tem componentes aleatórios. Temperatura, Top_P, Top_K. Mais: o treinamento tem componentes não-lineares (ex. ReLU). Não é determinístico, é um SORTEIO numa distribuição de probabilidades.&lt;/p&gt;

&lt;p&gt;Significa que, fundamentalmente, não tem como uma LLM estar 100%, 100% do tempo. É matematicamente impossível. Ele sempre pode estar "quase certo" ou "parecendo que está certo". Mas se você diligentemente rechecar tudo, o tempo todo, vai começar a encontrar pequenos equívocos. No caso de código, é bem mais aparente. Ele está BEM errado, MUITAS vezes.&lt;/p&gt;

&lt;p&gt;E o processo todo se assemelha muito a jogos online "gratuitos" com LOOT BOXES ou sistemas de GATCHA, sabe? Tipo Genshin Impact da vida.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcVFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c0949f4fec407894c1c5d91082e7a5fb53d96939/tu-genshin-impact-vach-mat-nhung-dieu-bi-an-dang-sau-ti-le-cua-loot-box-va-gacha-01-.0484-4045540040.jpg?disposition=attachment&amp;amp;locale=en" alt="Loot Box" /&gt;&lt;/p&gt;

&lt;p&gt;O quê??? Você é um boomer e não sabe o que são Loot Boxes? ...&lt;/p&gt;

&lt;p&gt;EU SOU BOOMER E EU SEI, não tem desculpa. Vai se atualizar (e é a principal razão de porque eu escolho não jogar esse tipo de jogo online).&lt;/p&gt;

&lt;p&gt;A economia desses games funciona assim: você não tem a opção de comprar ítens específicos que quer. Em vez disso, você paga, digamos 20 créditos pela chance de participar de um sorteio (que acontece em datas e horas pré-programadas, não o tempo todo). Nesse sorteio você tem a (ALTA) probabilidade de só ganhar um ítem de 10 créditos OU, a &lt;strong&gt;incrível&lt;/strong&gt; (BAIXÍSSIMA) probabilidade de ganhar um ítem exclusivo de 1000 créditos!!!!&lt;/p&gt;

&lt;p&gt;Parece um negócio da China né??? (LITERALMENTE É!!!)&lt;/p&gt;

&lt;p&gt;Você sempre ganha o item mais barato no sorteio. E de vez em quando ele te dá um item MÉDIO pra você não desistir de continuar e achar que tem alguma chance real. Só se você gastar MUITO (e nesse ponto você já gastou mais de 1000 créditos mesmo), só aí talvez ele te dê um item grande, pra te injetar de mais dopamina e aumentar seu &lt;strong&gt;vício&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;É exatamente isso que acontece com LLMs. As respostas deles tem chances parecidas com de Loot Box. Às vezes ele te surpreende com uma excelente respostas, mas na maior parte do tempo, você fica pedindo "cara, você tem certeza dessas resposta?", ou "cara, não tá errada essa segunda linha?", ou "cara, reveja o que eu perguntei e tenta de novo". e assim vai o seu dia inteiro.&lt;/p&gt;

&lt;p&gt;Você não fica olhando, mas por baixo dos panos, cada vez que tem que corrigir, ele continua gastando os mesmos créditos. No final do dia, lá se foram USD 50. Parece pouco, mas faça isso todo dia e veja quanto acumula.&lt;/p&gt;

&lt;p&gt;Só que seu &lt;strong&gt;VIÉS DE CONFIRMAÇÃO&lt;/strong&gt; faz você se lembrar bem de quando ele acertou, mas vai "esquecendo" todas as outras vezes que ele errou. E sua percepção é que &lt;em&gt;"Sim, ele funciona."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pior, sua baixa auto-estima e necessidade de se fazer parte de um grupo, de se &lt;strong&gt;CONFORMAR&lt;/strong&gt;. Corroborado com o que lê diariamente nas notícias, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30% de todo código da Microsoft já é feita por I.A.&lt;/li&gt;
&lt;li&gt;CEO da Anthropic diz que ano que vem programadores serão substituídos por virtual workers&lt;/li&gt;
&lt;li&gt;CEO da NVIDIA diz que não recomendaria seu filho a estudar mais programação&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;E tudo isso AUMENTA O VIÉS: você acha que eles estão certos, as vezes que você mesmo experimentou a LLM errando &lt;em&gt;"deve ter sido uma pequena exceção, eu que sou burro mesmo"&lt;/em&gt; e ACREDITA na MENTIRA.&lt;/p&gt;

&lt;p&gt;Quem lembra das minhas palestras de 15 anos atrás onde eu mostrava este video?&lt;/p&gt;

&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/iRh5qy09nNw?si=HhQ0Hvg2BNi99ZFT" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Isso é um fenômeno bem conhecido, hoje em dia mais amplificado pelas redes sociais e a burrice de jornalista, que só repete o que ouve como papagaio e não faz mais perguntas críticas, do tipo: EXISTE CONFLITO DE INTERESSES??&lt;/p&gt;

&lt;h3&gt;"OpenAI usa seus chats pra treinar?¨&lt;/h3&gt;

&lt;p&gt;Aqui vou ter que consertar uma mentira que eu venho contando pra vocês. Eu sempre repeti que não. Sua conversa não é usada pra treinamento, porque iria poluir o material de treinamento com lixo que no final não iria melhorar as respostas.&lt;/p&gt;

&lt;p&gt;É meia-verdade. De fato, eu duvido muito e boto minha mão no fogo: nenhum provedor quer se arriscar a alguém encontrar um texto com copyright e levar processo. Isso já acontece, o New York Time processou porque o ChatGPT conseguiu reproduzir um texto publicado por eles. E isso meio que "prova" que fazia parte do material de treinamento.&lt;/p&gt;

&lt;p&gt;Tem gente que coloca coisas privadas como condições de saúde, situação financeira, e até mesmo dados críticos como senhas, endereço, coisas que não deveriam mas acabam publicando porque não tem noção sobre cyber-segurança. Todo mundo cai num golpe todo dia por causa disso. A OpenAI ou Meta ou Alibaba não fazem questão de aparecer isso uma hora no chat e viralizar notícia internacional falando como eles foram irresponsáveis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MAS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E é um GRANDE "MAS", eles usam sim seu chat. De todo mundo. Sem copiar nenhum dado seu. Eles só precisam dos METADADOS, os dados que descrevem seu chat.&lt;/p&gt;

&lt;p&gt;De cabeça aqui, sendo ingênuo e amador em treinamento, eu vejo pelo menos 2 informações importantes que você dá pra eles: o comprimento do seu chat e o sentimento das suas respostas no chat.&lt;/p&gt;

&lt;p&gt;Determinar sentimento a partir de texto é um problema bem resolvido, tem algoritmos com ou sem redes neurais que conseguem determinar se o texto parece feliz, triste, bravo, angustiado ou coisas assim. Eles não precisam do conteúdo do seu texto, só precisam do SENTIMENTO que você teve na hora.&lt;/p&gt;

&lt;p&gt;Somado ao COMPRIMENTO do seu Chat e como eu falei &lt;a href="https://www.akitaonrails.com/2025/05/02/rant-llms-sao-loot-boxes"&gt;no post anterior&lt;/a&gt;, que o incentivo é fazer você gastar MAIS TOKENS (chat MAIS longos). NO PRÓXIMO POST vou ensinar vocês como eles fazer alinhamento, o "fine-tuning" do modelo. Uma OpenAI usa milhares de linhas de pares de prompts/completion pra "forçar" o modelo a responder de uma determinada forma. Pensa um Excel cheio de perguntas e respostas pré-prontas, que segundo seus metadados, sabemos que são as respostas mais POPULARES.&lt;/p&gt;

&lt;p&gt;O objetivo não é fazer o modelo responder mais certo, porque às vezes a resposta certa é muito dura ou inconveniente pra algumas pessoas. Eu tenho quase certeza que os dados extraídos dos chats de milhões de pessoas, só isso: sentimento e comprimento. Deve ajudar a criar prompts que forçam o modelo a responder "menos certo" mas "mais agradável", incentivando o usuário a CONTINUAR falando no chat e GASTANDO MAIS TOKENS.&lt;/p&gt;

&lt;p&gt;É o comportamento mais antigo do mundo. Todo lojista sabe disso. Todo vendedor sabe disso. Todo mundo que trabalha em serviços sabe disso. Se você for certo e direto, vai soar arrogante e o cliente não volta. É melhor soar menos certo, mas mais convidativo, agradável, prestativo, etc. E o cliente volta, porque é assim que a pessoa média funciona.&lt;/p&gt;

&lt;p&gt;Portanto, sim: eu acho que seus dados servem pra re-alinhar o modelo depois de treinado, pra responder mais agradável. Eles nem fazem mais questão de esconder isso. Não viram o próprio Sam Altman quando falou poucos dias trás que fizeram o ChatGPT 4 responder mais amigável, com mais personalidade?&lt;/p&gt;

&lt;p&gt;É isso que eu acho que eles estão fazendo. Tornar o modelo mais inteligente está sendo um desafio. Porque - como já venho dizendo faz 1 ano - estamos batendo no teto da curva em S. Pra continuar vendendo tokens, fazendo você voltar e se viciar em falar, não precisa ser mais inteligente, só precisa ser mais agradável.&lt;/p&gt;

&lt;h3&gt;Conclusão&lt;/h3&gt;

&lt;p&gt;Não existe LLM que vai estar 100% certo, 100% do tempo, a arquitetura fundamental IMPEDE que isso aconteça, não importa quanto mais façamos otimizações. Existe agora e sempre vai continuar existindo um componente de ENTROPIA. É essa aleatoriedade que produz texto que parece um humano falando e não um robô.&lt;/p&gt;

&lt;p&gt;Automatizações longas e complexas demais usando essa fundação fŕagil é um ENORME erro. Claro, pra tarefas simples, curtas, focadas, ele tem uma TAXA DE ACERTO mais alta. Pra código de verdade? É SUPER baixo.&lt;/p&gt;

&lt;p&gt;Nunca deixe um MCP fazer commits direto no seu repositório Git. Ferramentas como o Aider trazem "autocommit = true" por padrão e isso é uma enorme estupidez! Não faça isso.&lt;/p&gt;

&lt;p&gt;Eu avisei.&lt;/p&gt;

&lt;p&gt;Mas não tem como melhorar o código de LLMs? Talvez. 100% nunca, mas aumentar a taxa acho que dá. E pra provar que eu não sou anti-LLM nem nada disso, no próximo artigo vou tentar ensinar COMO melhorar uma LLM pra programação (com média probabilidade, não é milagre!).&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6207</id>
    <published>2025-05-01T17:20:00-03:00</published>
    <updated>2025-05-05T18:57:59-03:00</updated>
    <link href="/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real" rel="alternate" type="text/html"/>
    <title>Quando LLMs não Funcionam pra Programar? Um caso de uso mais real.</title>
    <content type="html">&lt;p&gt;Como eu disse no meu post anterior de Rant sobre &lt;a href="https://www.akitaonrails.com/2025/05/01/rant-llms-vao-evoluir-pra-sempre-desmistificando-llms-na-programacao"&gt;Desmistificar a I.A. pra programação&lt;/a&gt;, posso confirmar que muitos benchmarks sintéticos que dizem medir capacidade de programação, são MENTIRAS.&lt;/p&gt;

&lt;p&gt;E não digo que é uma tentativa proposital de mentir, mas que quem está fazendo as pesquisas e compilando rankings e leaderboards, não divulgam exatamente os detalhes e só soltam o resultado sem contexto. Este é um exemplo que achei hoje: &lt;a href="https://github.com/LiveBench/LiveBench"&gt;LiveBench&lt;/a&gt;, que é mais um pacote de benchmark de "programação". Olha  que é testado exatamente:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LiveBench/LiveBench/tree/main/livebench/scripts"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcG9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--bda1e8656cf4aa6487faa0556c83a33724f51b93/Screenshot%20From%202025-05-01%2011-54-06.png?disposition=attachment&amp;amp;locale=en" alt="LiveBench scripts" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;São testes idiotas do tipo "carregue estes dois arquivos CSV e compare os valores". É ainda mais idiota que Leet Code. É ainda mais idiota que a maioria dos testes de contratação de programadores estagiários. Enfim, é o conjunto mais idiota de testes que eu já vi.&lt;/p&gt;

&lt;p&gt;Por isso eu disse que meus testes são MUITO SUPERIORES. Não porque eu sou ph0da. Mas porque a régua é extremamente baixa. Meu teste mais real é um projetinho pequeno em Python e eu dou prompts de refatorar código sujo e criar testes unitários. Que é o básico do básico que eu espero de qualquer programador minimamente competente. E escolhi Python (que nem é minha linguagem favorita), pra dar mais chance pras LLMs, imaginando que no material de treinamento deve ter visto muito Python.&lt;/p&gt;

&lt;p&gt;E nem assim eles conseguem fazer o que eu pedi. Vocês podem ver com seus próprios olhos nos meus comentários das &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pulls"&gt;pull requests&lt;/a&gt; que deixei no repositório, testando cada LLM, múltiplas vezes. É absolutamente decepcionante de ver.&lt;/p&gt;

&lt;h3&gt;Projeto Novo: Zig&lt;/h3&gt;

&lt;p&gt;Agora eu tenho outra teoria: LLMs são boas em "copy e paste", em cuspir código que ele viu repetidas vezes no treinamento. Por isso que se perguntar qualquer coisas simples de Leet Code, ele sabe responder. Nem precisa tentar "pensar", já está embutido no modelo. É que nem fazer uma prova com cola.&lt;/p&gt;

&lt;p&gt;Eu acho que o calcanhar de aquiles de LLM pra programação são bibliotecas recém-lançadas, novas versões ou linguagens muito novas. Qualquer coisa que se procurar no Google, você mesmo vai ter dificuldade de achar.&lt;/p&gt;

&lt;p&gt;E é exatamente isso. Eu subi &lt;a href="https://github.com/akitaonrails/qwen-cli-zig"&gt;este novo repositório no GitHub: Qwen-CLI-Zig&lt;/a&gt;
O objetivo foi tentar fazer, do zero, um mini chat interativo pra conversar com um modelo local, no caso o Qwen3:14b GGUF (quantizado). E pra fazer isso escolhi usar direto a biblioteca em C &lt;a href="https://github.com/ggml-org/llama.cpp"&gt;llama.cpp&lt;/a&gt;. Eu imagino que toda biblioteca popular de mais alto nível como litellm, vllm, ollama e coisas assim ou usam, ou se inspiram nessa biblioteca. Ela estabelece mais ou menos as convenções de como LLMs funcionam.&lt;/p&gt;

&lt;p&gt;O obstáculo: é código em C++. E pra piorar, tem que compilar com o CUDA Toolkit pra ter acesso à minha GPU. E agora começa meu pesadelo.&lt;/p&gt;

&lt;h3&gt;Os Problemas&lt;/h3&gt;

&lt;p&gt;Pra fazer isso, escolhi usar Aider com o Gemini 2.5 Pro Exp Preview 03-25, teoricamente o dito "estado da arte" na programação. "Segundo os benchmarks" ...&lt;/p&gt;

&lt;p&gt;De fato, ele não é ruim, mas o importante pra um profissional não é ficar elogiando o que não sabe, e sim saber as limitações pra descobrir como consertar, criar workarounds/gambiarras ou simplesmente saber que tem casos que não dá pra usar a ferramenta. Esse é um desses casos.&lt;/p&gt;

&lt;p&gt;Vamos lá:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;O Gemini claramente não teve muito material de Zig pra treinar e o que teve está defasado ou obsoleto. Ele insiste em criar código que não funciona mais. E não sabe como consertar, porque não tem no treinamento.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A "solução" foi rodar &lt;code&gt;zig build&lt;/code&gt;, ver o erro, ir na &lt;a href="https://ziglang.org/documentation/master/#intCast"&gt;documentação oficial&lt;/a&gt;, pra coisas como &lt;code&gt;ptrCast&lt;/code&gt;, &lt;code&gt;intCast&lt;/code&gt;, passar a URL pro Aider carregar e pelo menos assim ele conseguia passar por esses erros.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coisas que já são bem documentadas, como clonar o repositório do llama.cpp, configurar os flags corretos pra compilar com CUDA, tudo isso num script de bash, isso foi fácil. Ele tirou de letra. Exemplos de scripts de build dessas coisas imagino que teve muito no treinamento.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mesmo assim, no Zig, ele precisava carregar o header &lt;code&gt;llama.h&lt;/code&gt; pra saber que funções existem e que assinaturas pra conseguir fazer &lt;code&gt;extern "c"&lt;/code&gt; das coisas. Trechos como este:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcHNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a9ec3f4bda5e80e1d67b4e5aa7148a4048ffcf51/Screenshot%20From%202025-05-01%2017-01-52.png?disposition=attachment&amp;amp;locale=en" alt="extern c" /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pra fazer isso ele precisa ler de &lt;code&gt;vendor/llama.cpp/include/llama.h&lt;/code&gt;. Ele sabe disso, mas mandava o path errado o tempo todo, como &lt;code&gt;vendor/llama.h&lt;/code&gt; ou &lt;code&gt;vendor/llama.cpp/llama.h&lt;/code&gt;. Se não prestar atenção, ele fica em loop infinito tentando adivinhar porque dá erro sem parar pra pensar no path correto. Eu que tive que manualmente, mais de uma vez, dizer pra ele o path correto e mesmo assim ele esquecia.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Com o &lt;code&gt;llama.h&lt;/code&gt; no contexto carregado, mesmo assim ele fazia erro de estag: saía criando &lt;code&gt;extern&lt;/code&gt; pra funções que nem vai precisar. Ele leu o arquivo e saiu copiando e colando no Zig tudo. Mas além de encher linguiça com mapeamento desnecessário, ele errava MUITO os tipos, errava &lt;code&gt;c_int&lt;/code&gt; por &lt;code&gt;int32&lt;/code&gt; (o primeiro é pra mapear com C, o segundo é só pra Zig puro). Isso custou MUITOS erros de compilação e tentativas repetidas em excesso até conseguir consertar.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Depois de algumas tentativas frustradas, consegui fazer ele criar um script de download do arquivo de modelo do Qwen3 14b GGUF (parece que o llama.cpp, por default, quer GGUF). E finalmente o programa compilou e eu consegui rodar até o ponto onde abria o chat e eu conseguia submeter alguma mensagem.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;E na parte final, ele não conseguiu, de jeito nenhum pegar a resposta de volta do modelo. Infelizmente, eu escolhi Zig justamente por ser super novo ainda e eu sei que a sintaxe fica mudando porque não é uma linguagem estável ainda. Por isso não recomendo pra projetos de verdade. É muito experimental. Eu não sei usar Zig direito ainda. Então também não tive paciência pra tentar consertar o erro eu mesmo. (se alguém de Zig se voluntariar, aceito pull requests).&lt;/p&gt;

&lt;p&gt;Enfim, gastei umas boas 2 horas só nessa parte. Tentando ler documentação. Eu tentava passar artigos de llama.cpp com exemplos de código e aqui vem outro problema: todo mundo só faz blog post de exemplos de Python (que é a coisa mais fácil, só carregar litellm), mas ninguém se voluntariou pra escrever posts em C++, por exemplo (porque ninguém que escreve posts sabe como, eu incluso).&lt;/p&gt;

&lt;p&gt;É ou alguma configuração específica do modelo com llama.cpp que eu não sei (daí o resultado é errado) ou é o código que pega a resposta que tem algum erro. Eu vejo isso:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcHdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b643384d6239ee14631104a369dc395bf48e6561/Screenshot%20From%202025-05-01%2017-14-53.png?disposition=attachment&amp;amp;locale=en" alt="llama error" /&gt;&lt;/p&gt;

&lt;p&gt;O problema: tem pouquíssima documentação a respeito e não achei nada que me ajudasse a resolver. E só na tentativa e erro o Gemini também não conseguiu resolver. Então eu encerrei o teste.&lt;/p&gt;

&lt;h3&gt;Custa Caro&lt;/h3&gt;

&lt;p&gt;Agora o bônus. Eu assinei a &lt;a href="https://openrouter.ai"&gt;OpenRouter.ai&lt;/a&gt; pra centralizar os gastos de qualquer LLM num lugar só. Comprei USD 100 de crédito e já tinha gasto bastante nos testes de refatoração de várias LLM. Não sei quanto tinha sobrando, vamos chutar uns USD 20.&lt;/p&gt;

&lt;p&gt;Daí fazendo esse novo programinha em Zig, acabou os créditos no meio do caminho. Meu Aider começou a apitar que faltava créditos pro tanto de contexto que o Gemini gera (esse é um problema de modelos "thinking", o pensamento é longo demais e come MUITO crédito de tokens).&lt;/p&gt;

&lt;p&gt;Fui na minha conta e enchi mais USD 100. Voltei só nessa parte do &lt;code&gt;std::bad_alloc&lt;/code&gt;pra tentar consertar - e o problema é que o Gemini não foca. Ele sai mexendo código aleatoriamente e coisas como o procedimento de carga do modelo na GPU, que já estava funcionando, ele ficava frequentemente quebrando. E eu ficava frequentemente mandando ele voltar como tava. E isso gasta MUITO crédito também.&lt;/p&gt;

&lt;p&gt;No momento que desisti, é isso que vi no meu saldo na OpenRouter:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcDBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--33e5b36602885c7f356b857278e435de889f32f2/Screenshot%20From%202025-05-01%2017-19-06.png?disposition=attachment&amp;amp;locale=en" alt="OpenRouter credits" /&gt;&lt;/p&gt;

&lt;p&gt;Além dos cerca de USD 20 que eu tinha sobrando, ele gastou quase USD 35. Chutando, ele gastou mais de USD 50 pra não me dar um código que funciona, depois de umas boas 4 ou 5 horas tentando. Pode esperar gastar USD 10 por hora em tentativa e erro. Se você vê que está repetindo muito "What's wrong, fix", é hora de parar e desistir, ele não vai conseguir.&lt;/p&gt;

&lt;p&gt;Você tem que ter a intuição correta de quando falta informação e buscar URLs no Google pra dar pra ele. Você tem que ter intuição do que tem no material de treinamento e saber que ele não tem essa informação. Você tem que ter intuição de ler o stacktrace e dizer o que ele deve ignorar e o que ele deve focar pra corrigir. Você tem que saber que é pra pedir uma coisa pequena de cada vez e nunca "corrija tudo" ou "teste tudo". Ele não vai conseguir.&lt;/p&gt;

&lt;p&gt;Prompts que tem mais chance de funcionar são sempre coisas bem pequenas. Um pequeno bloco de código de cada vez. Incremental, testando, e mandando não mexer no que já funciona (e ele vai te ignorar e mexer, e quebrar). Ter testes pra checar é fundamental. E não adianta "compilar".&lt;/p&gt;

&lt;p&gt;Esse código de Zig "compila" mas ele tem erro de run-time, só aparece quando roda. Mas eu não tive coragem de pedir pra ele fazer um teste unitário. Todos foram incompetentes em fazer testes pra Python. Pra Zig então, eu já acho que é uma impossibilidade. Ele não vai conseguir.&lt;/p&gt;

&lt;p&gt;Por que? Porque eu acho que pesquisadores não sabem como programadores trabalham. Pra eles, programação são só pequenos scripts (que é o que eles fazem mesmo: abrir um CSV e mandar agregar dados). É só isso que sabem fazer. E é isso que as LLMs refletem: ele tem MUITA dificuldade de fazer código de verdade. E nunca mais longo que uns 4 arquivos no contexto de cada vez. Mais que isso e ele fica confuso muito rápido e começa a cuspir código MUITO errado.&lt;/p&gt;

&lt;p&gt;Aliás, outra dica: evite criar seu projeto com nomes parecidos com de projetos open source populares. Pense assim, digamos que seu projeto se chama "my-react". Fodeu, no treinamento dele certamente teve muito exemplo de React.js de verdade, e ele vai ficar confundindo as duas coisas o tempo todo. De novo, tenha intuição do que já tem no treinamento pra ajudar o modelo a não ficar confuso.&lt;/p&gt;

&lt;p&gt;E você vai ter gasto dezenas de dólares, ou mais, se não tomar cuidado. E no final não vai ter código que funciona direito.&lt;/p&gt;

&lt;p&gt;Ah sim, tem gambiarras que dá pra fazer: integrar um RAG com código fonte do que você sabe que ele não tem no treinamento, ou treinar LoRas e usar em conjunto. E mesmo assim não é "solução", são tentativas de fazer ele gerar código que faça mais sentido. Mas sozinhos, sem esse tipo de ajuda, neste momento eles ainda não sabem o que fazer.&lt;/p&gt;

&lt;p&gt;O que muita gente ignora é que a GRANDE MAIORIA dos principais códigos de verdade do mercado, são todos FECHADOS. A Amazon nunca vai liberar o código do e-commerce deles, nem Alibaba. O iFood ou MercadoLivre idem. Nenhum código de verdade está no modelo. Se estivesse seria um enorme risco de PROCESSO. Eles só podem usar código aberto, de lugares como GitHub ou respostas de Stackoverflow. E lá só tem código BÁSICO E RUIM. Então é só isso que as LLMs conseguem cuspir código BÁSICO E RUIM.&lt;/p&gt;

&lt;p&gt;Se a LLM tem conseguido resolver seus problemas, não é porque ele é bom, é porque seu problema é muito simples.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6206</id>
    <published>2025-05-01T02:30:00-03:00</published>
    <updated>2025-05-05T18:58:11-03:00</updated>
    <link href="/2025/05/01/rant-llms-vao-evoluir-pra-sempre-desmistificando-llms-na-programacao" rel="alternate" type="text/html"/>
    <title>Rant - LLMs vão evoluir pra sempre? Desmistificando LLMs na programação</title>
    <content type="html">&lt;p&gt;Vamos recaptular meus últimos artigos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.akitaonrails.com/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local"&gt;Hello World de LLM&lt;/a&gt; - neste artigo explico como criar um programinha simples (educativo) de chat carregando um LLM (Qwen2.5) e até como usar prompts pra que ele consiga chamar scripts/agentes e executar algumas tarefas simples como carregar arquivos locais. Eu subi o código no GitHub como &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI"&gt;Tiny-Qwen-CLI&lt;/a&gt; e deixei o código "sujo" de propósito pra poder testar como as LLMs conseguem (ou não ajustar esse código).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.akitaonrails.com/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen"&gt;Seu Próprio Co-Pilot Gratuito&lt;/a&gt; - aqui eu explico mais sobre a ferramenta Aider, que é como se fosse um Co-Pilot ou Cursor, mas gratuito e open-source. Ele não exige plugins nem IDEs. Roda no terminal e é fácil de usar. Também mostro como integrar com Ollama e subir seus próprios LLMs locais.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.akitaonrails.com/2025/04/29/dissecando-um-modelfile-de-ollama-ajustando-qwen3-pra-codigo"&gt;Dissecando um Modelfile de Ollama&lt;/a&gt; - neste eu desço mais na teoria dos LLMs e explico o que é Key Sampling e os como diversos parâmetros afetam a geração de texto das LLMs, como Temperature ou Top_P. Aqui é pra desmistificar ainda mais que não existe nenhuma "mágica" em como uma LLM consegue gerar textos.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Ao longo dos últimos dias eu vim testando e postando no X minhas impressões sobre os mais diversos modelos comerciais e open source como Claude Sonnet, Gemini 2.5, OpenAI O4, Deepseek-R1, o novo Qwen3 e mais. Tem mais posts no blog além dos que mencionei acima, mas estes são os principais.&lt;/p&gt;

&lt;p&gt;Objetivo deste post é desmistificar mais as LLMs e falar mais sobre minha experiência testando a maioria dos LLMs mais populares.&lt;/p&gt;

&lt;h3&gt;Desmistificando LLMs - 1. Benchmarks e Rankings&lt;/h3&gt;

&lt;p&gt;Quem acompanha minhas palestras e videos faz anos está careca de me ouvir falar sobre o livro &lt;a href="https://www.amazon.com.br/Como-Mentir-Estat%C3%ADstica-Darrell-Huff/dp/858057952X"&gt;"Como Mentir com Estatísticas"&lt;/a&gt;. E o que eu mais vejo nas centenas de posts sobre LLMs é sobre os rankings baseados em benchmarks. Este é um exemplo que viralizou ontem, no lançamento do novo modelo da Alibaba, o Qwen3:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcFFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--bfa602c4c5f00f962f0467ac2c248c4e5846a3c2/GprgeiCWoAAVtoJ.jpg?disposition=attachment&amp;amp;locale=en" alt="Qwen3 benchmarks" /&gt;&lt;/p&gt;

&lt;p&gt;O problema: os rankings não estão errados. Eles querem dizer o seguinte:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Qwen 3 supera Qwen 2.5 e também outros famosos como OpenAI o1, DeepSeek-R1, Grok 3 Beta, Gemini 2.5, etc .... NOS BENCHMARKS RODADOS - e APENAS nos benchmarks rodados."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;É apenas isso. Mas 99% dos posts publica assim:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Qwen 3 É O MELHOR PRA DESENVOLVER CÓDIGO do que Qwen 2.5 e também outros famosos como OpenAI o1, DeepSeek-R1, Grok 3 Beta, Gemini 2.5, etc"&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;Os benchmarks não estão errados, quem está errado é quem interpretou que isso significa ser melhor em tudo. Vamos pra outra analogia:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"O Aluno Fulano conseguiu 99% de acerto em todos os simulados do vestibular"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A conclusão das pessoas:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"O Aluno Fulano é 99% melhor que todo mundo em tudo"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Espero que isso soe obviamente errado. Ser melhor em 1 coisa não torna ninguém melhor em tudo, mas em I.A. é assim que toda notícia de uma nova versão de LLM é publicado em posts pessoais e veículos jornalísticos. É um óbvio &lt;strong&gt;CLICKBAIT&lt;/strong&gt;. E você cai.&lt;/p&gt;

&lt;p&gt;E se você está acostumado a ouvir dessa forma, faz parecer que a cada nova versão de LLM - que é "melhor em tudo" - que o anterior, falta muito pouco ou já chegou no ponto onde sim, não precisa mais existir programadores humanos, nem matemáticos, nem engenheiros, nem médicos, afinal, a LLM é "BOA EM TUDO".&lt;/p&gt;

&lt;p&gt;E nada poderia ser mais longe da verdade que isso. Essas pesquisas estatísticas de ranking tem uma METODOLOGIA de testes e um pacote de benchmark fixo. Os testes são SIMPLES, pense coisas no nível "faça uma função que calcule fatorial", "faça uma função que ordene uma lista de palavras", "dado uma lista de palavras, encontre um em específico em tempo logarítmico", etc. Coisas que você vê nos cursos introdutórios de ciências da computação ou Leet Code.&lt;/p&gt;

&lt;p&gt;Esses testes são rodados MÚLTIPLAS VEZES e os "outliers" (testes onde se errou demais, ou testes onde se acertou demais, ou algo que saia da média) são descartados, é feita uma distribuição estatísticas e compilado um "número" geral genérico como "acertou 99%" ou algo assim.&lt;/p&gt;

&lt;p&gt;Ele errou muitas vezes, mas esse ponto de dados é um outlier, e foi removido da conta. É estatisticamente irrelevante dentro da metodologia. Mas é um fato: nenhuma LLM acerta 100% das vezes nem nunca vai acertar. Eu demonstrei isso no meu post sobre Modelfiles onde eu explico que existe um fator aleatório controlado (temperatura, top_p, top_k, min_p, key sampling). E existe também normalizações e fatores aleatórios no treinamento (softmax, relu). E ainda tem fatores de otimização posterior que "arredonda" ou "trunca" as probabilidades como quantização (fp8, Q4, etc). Ou seja, existem diversos fatores de "micro erros" e aleatoriedade embutidos no processo inteiro. Nunca vai ser uma resposta "certa", só "possivelmente uma das mais certas" e esse "possivelmente" pode variar MUITO.&lt;/p&gt;

&lt;h3&gt;Dissecando Mitos de I.A. - 2. Evolução Exponencial&lt;/h3&gt;

&lt;p&gt;Quem chegou só agora e está vendo notícias frequentes sobre lançamentos de novas versões de LLMs como o Qwen3 da Alibaba agora, o novo MiMo da Xiaomi, a versão 2.5 do Gemini, a versão 4.1 do ChatGPT, etc tem a impressão que estamos evoluindo a passos largos.&lt;/p&gt;

&lt;p&gt;Mas a verdade é que sim, 2022 foi um marco histórico com o ChatGPT original, principalmente GPT 2. Mas de lá pra cá a evolução desacelerou. Cada nova versão não é mais visivelmente "o dobro melhor" que a anterior, a não ser em certos benchmarks, em certas condições específicas - que não condizem com a realidade. Já vou demonstrar.&lt;/p&gt;

&lt;p&gt;Um amigo meu me lembrou sobre a evolução das tecnologias de deep learning em reconhecimento de imagens, anos antes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;2012 – AlexNet: Primeira grande vitória no ImageNet com 8 camadas, alcançando 84,7% de top-5 accuracy.​&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2014 – VGG: Redes com 16–19 camadas, melhorias incrementais, mas com aumento significativo de parâmetros.​&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2015 – ResNet: Introdução de conexões residuais, permitindo redes com mais de 100 camadas e superando desempenho humano em top-5 accuracy.​&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;2016–2017 – Inception, ResNeXt, SENet: Modelos mais complexos com ganhos marginais.​&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pós-2018: Acurácia top-1 se estabiliza em torno de 85–88%, com ganhos marginais mesmo em modelos como EfficientNet e NasNet.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;I.A. é estudada desde literalmente o início da computação moderna no fim dos anos 1930, com ninguém menos que o próprio Alan Turing ou John Von Neumann. Eu contei sobre eles aqui:&lt;/p&gt;

&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/G4MvFT8TGII?si=9Qefl2cU6mNefzqB" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Pense um "início lento": de 1930 até cerca de 2012 quando ImageNet e coisas como AlphaGo, GANs e coisas assim começaram a ganhar velocidade. Saímos da perna do "S" pro meio a partir dos anos 2010 e viemos acelerando até agora. Eu "chuto" que ou já estamos ou estamos caminhando pro topo do "S", onde a curva estabiliza e desacelera. Foi isso que aconteceu com deep learning de imagens:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcFVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3dbc7556c7e1358ce06c1d253fc223c1bc037a0c/Screenshot%20From%202025-05-01%2001-38-39.png?disposition=attachment&amp;amp;locale=en" alt="ImageNet" /&gt;&lt;/p&gt;

&lt;p&gt;Lembram do AlphaGo? Eu acompanhei ao vivo. Como amador-hobista em Go, eu assisti ao vivo os jogos com me mestres sul-coreanos como Lee Sedol, na época que aconteceu, acho que 2016. Vocês devem ter visto o &lt;a href="https://www.netflix.com/br-en/title/80190844"&gt;documentário na Netflix&lt;/a&gt; só agora, mas isso vem acontecendo faz anos. E agora, onde está o AlphaGo evoluindo exponencialmente? Todo mundo já esqueceu, porque o quente da moda são LLMs, que é só mais uma categoria no mundo de I.A.&lt;/p&gt;

&lt;p&gt;A grande maioria das otimizações já foram ou já estão sendo feitas. Se fosse resumir só alguns dos milestones mais importantes desta parte da geração seria mais ou menos assim:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcFlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4ff1048ab7ca04e39b5f21cbf442e61bc0e565c0/ChatGPT%20Image%20May%201,%202025,%2001_47_42%20AM.png?disposition=attachment&amp;amp;locale=en" alt="timeline llm" /&gt;&lt;/p&gt;

&lt;p&gt;Todo engenheiro e cientista sabe disso: quando você não tem nada, qualquer coisa é um salto quântico. Quando você só tem 1 real, 2 reais já é "o dobro". Quando tem 10 reais, 20 já é bastante. Mas quando chega em 20 mil, mais 100 já não adiciona tanto valor assim do que quando você só tinha 1 (ali seria 100x).&lt;/p&gt;

&lt;p&gt;Cada nova gota de otimização agora CUSTA MUITO. Mesmo em otimização com perda de precisão (quantização) já saímos de fp32, fomos pra fp16, fp8, já chegamos em 1-bit. Não tem mais pra onde descer depois disso. Estamos no ponto onde estamos trocando precisão por performance.&lt;/p&gt;

&lt;p&gt;Dobro de parâmetros não resulta mais em dobro de qualidade de resultado. Precisa dobrar pra ter 5%? Talvez? E não adianta continuar aumentando, o resultado começa a piorar em vez de melhorar. Por isso já pulamos pra coisas como LLMs especialistas, MoE (Mixture of Experts).&lt;/p&gt;

&lt;p&gt;Deep thinking/Chain-of-Though/Reasoning foi um grande salto, mas só aumentar mais thnking também já não dá mais tanto resultado, na verdade, em muitos casos piora o resultado com "over-thinking". Pensar demais não é linear também.&lt;/p&gt;

&lt;p&gt;Aumentar contexto também não ajuda a melhorar as respostas. Pelo contrário, já vemos que a partir de um certo ponto, mais contexto atrapalha o resultado, porque a arquitetura de atenção (o grande diferencial das LLMs), tem limites. Sim, um Deepseek ou Gemini dizem suportar "1 milhão de tokens". Mas eu já expliquei nos artigos anteriores que se trata de &lt;strong&gt;Sliding Window Attention&lt;/strong&gt; como SDPA ou Flash onde a atenção é voltada só a uma PARTE desse grande contexto: ele não consegue dar atenção pra tudo o tempo todo.&lt;/p&gt;

&lt;p&gt;Todo lugar onde dava pra colocar coisas óbvias, como caching, seja no treinamento, seja na geração, já foram colocadas. Estamos ficando sem opções aqui. E sim, novas descobertas continuam acontecendo, mas como falei, nenhuma ainda conseguiu ultrapassar os limites que sabemos que existem.&lt;/p&gt;

&lt;h3&gt;Desmistificando Mitos de I.A. - 3. Acabar com Programadores&lt;/h3&gt;

&lt;p&gt;O que eu mais detesto do hype atual são as falsas promessas de "AGI", "Vibe Coding", "trocar funcionários por MCPs". Vou afirmar de novo: é tudo propaganda.&lt;/p&gt;

&lt;p&gt;Propaganda mentirosa e falsas promessas de óleo de cobra (coisa que sempre existiu e sempre vai existir na história) DESVALORIZA a realidade as inovações que temos hoje.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Mas o CEO da Anthropic e o tio Zuck disseram que vão substituir programadores. Microsoft e Google disseram que 30% do código deles já sai de I.A."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Como falei antes sobre estatística. Sim: muita coisa VAI ser feita com I.A. mesmo. Não precisa de I.A. pra otimizar trabalho em várias área:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;um pedaço de pau e um bloco de argila são suficientes pra substituir a memória das pessoas. Ninguém lembra de detalhes, especialmente numéricos, permanentemente. Faz milênios que evoluímos em ajudar nossa memória com algum tipo de armazenamento externo. Seja bloco de argila, seja papel, seja post-it, seja um arquivo .txt na sua máquina.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a primeira câmera fotográfica já removeu a necessidade de contratar um pintor de quadros pra se ter uma "selfie".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;o primeiro computador mais primitivo já eliminou a necessidade de um prédio inteiro de matemáticos fazendo e refazendo cálculos no papel. Mesmo assim, foi desse jeito que os primeiros foguetes foram pro espaço. Já assistiu o filme &lt;a href="https://www.netflix.com/title/80123775"&gt;Hidden Figures&lt;/a&gt;? Recomendo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;carros já são montados quase que totalmente por robôs, faz anos. robótica não é novidade na indústria. Hoje em dia, um tablet já substituiu caixas de supermercado e de McDonalds. Tudo isso sem precisar de I.A. URAs e botzinhos já substituíram muito de telemarketing e suporte.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Então sim, na prática, I.A. também vai substituir uma classe inteira de programadores: os de baixo valor agregado. Os que não sabem fazer muito mais que copy e paste ou cuja rotina de trabalho seja repetitivo. Se seu trabalho envolve tarefas repetitivas, é óbvio que é questão de tempo até serem automatizados, seja por I.A. seja por meros scripts. Coisa que já fazemos hoje.&lt;/p&gt;

&lt;p&gt;O trabalho de um programador sempre foi substituir o trabalho dos outros. Você que trabalha fazendo sistema de e-commerce: você ajudou a substituir o trabalho de vendedores, vitrinistas, decoradores de loja, caixas. Você que faz sistemas de tickets e suporte, você já substituiu atendentes humanos. Você que faz sistemas de entregas, já substituiu telefonistas. Você que faz sistemas financeiros, já substituiu bancários, quem constrói agências de banco, etc.&lt;/p&gt;

&lt;p&gt;Você, programador, sempre trabalhou substituindo o trabalho dos outros. Era questão de tempo até programadores substituírem trabalho de programadores. Chame isso de &lt;strong&gt;justiça poética&lt;/strong&gt;. É só evolução mesmo. Acontece. Nada no mundo é feito pra ser estático e nem garantido.&lt;/p&gt;

&lt;p&gt;Aqui vai a boa notícia: como sempre acontece. Ainda existem pintores, mesmo existindo fotógrafos. Ainda existem vendedores, mesmo existindo e-commerce. Ainda existe construção, mesmo não precisando tanto de lojas ou até escritórios (com home office). Ainda existe e vai continuar existindo, médicos, advogados, contadores, várias profissões. A diferença é que antes, uma empresa de contabilidade precisava de um prédio cheio de pessoas que sabiam usar ábacos e escrever planilhas em papel. Hoje um único contador que saiba usar Excel substitui um andar inteiro. Antigamente, uma empresa de publicidade era um prédio inteiro, hoje é uma pessoa que usa Canva, CapCut e Photoshop.&lt;/p&gt;

&lt;p&gt;Programador é a mesma coisa. Antes era um prédio inteiro com piscina de bolinha roxa. Amanhã vai ser um, BOM, programador com Cursor e Aider. Quem some nessa brincadeira? Quem fazia o trabalho braçal de baixo valor agregado.&lt;/p&gt;

&lt;p&gt;Eu avisei que isso ia acontecer. Durante 5 anos, antes de existir esta geração de LLMs e I.A. Porque é o rumo natural das coisas. Programador de curso online de fim de semana. Esse é o primeiro que vai desaparecer.&lt;/p&gt;

&lt;p&gt;Você sabe cálculo? Sabe Álgebra? Sabe Álgebra Linear? Sabe Estatística de Probabilidade? Sabe o que é uma distribuição estatística? Sabe o que são vetores ou tensors? Sabe o que é um espaço de Hilbert? Não? Eu me precaveria se fosse você. Se todos os artigos que eu publiquei neste mês de Abril/2025 foram muito difíceis, vocês já sabem o que precisam estudar.&lt;/p&gt;

&lt;h3&gt;Minha experiência com LLMs&lt;/h3&gt;

&lt;p&gt;Eu venho usando LLMs faz 2 anos. Todo tipo de pesquisa ou código que faço passa por alguma LLM em alguma capacidade. Eu uso pesado mesmo. Meu histórico de ChatGPT é gigante.&lt;/p&gt;

&lt;p&gt;E depois de fazer mais um intensivão pra ter certeza. Sim, o4-mini-high, Sonnet 3.7, Gemini 2.5, Qwen3, Deepseek-R1, etc são todos extremamente úteis e nenhum deles, absolutamente nenhum, consegue me satisfazer.&lt;/p&gt;

&lt;p&gt;Vibe Coding é uma enorme bobagem. Ninguém sem conhecimentos de programação consegue fazer mais que software super simples (e cheio de bugs) usando só LLMs e prompts ou MCPs. Isso é um fato.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Mas a evolução é exponencial, ano que vem vai ser perfeito."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Não vai. Eu já argumentei esse ponto acima.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Mas os benchmarks dizem que eles já conseguem fazer 99% de código perfeito."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Também já argumentei esse ponto, sua interpretação está equivocada.&lt;/p&gt;

&lt;p&gt;Estamos perto ou já no começo do topo da curva "S". E eu não vejo problema nenhum nisso. Eu entendo como LLMs são feitos, entendo como são otimizados, entendo o que conseguem fazer e, mais importante, entendo o que são incapazes de fazer.&lt;/p&gt;

&lt;p&gt;Vejam os &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pulls"&gt;pull requests&lt;/a&gt; de todas as tentativas que tentei fazer de refatoração de código e testes unitários com as principais LLMs do mercado, abertas e fechadas. O que eu vejo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;precisa de MUITO prompt e muita instrução pra fazer eles começarem a cuspir código que funciona.&lt;/li&gt;
&lt;li&gt;eles parecem um estagiário, o famoso "na minha máquina funciona". Saem cuspindo, um monte de copy e paste de código, e não re-checam se faz sentido ou se precisava. O tanto de código inútil que eles geram é assustador.&lt;/li&gt;
&lt;li&gt;eles repetem os mesmos erros, mesmo avisando pra não cometer.&lt;/li&gt;
&lt;li&gt;mesmo com "agentes", "MCP", mesmo rodando o código que foi gerado e dando o stacktrace de erros, eles facilmente desfocam, perdem atenção e não consguem corrigir. Mesmo re-tentando diversas vezes pra corrigir o mesmo erro. Mesmo dando dicas. Mesmo escrevendo longamento o que está errado. Rapidamente eles entram em loop. Especialmente se tiver o tal "thinking" ativado. Eles pensam demais e não resolvem.&lt;/li&gt;
&lt;li&gt;cansei de ver tentativa de sair mexendo aleatoriamente no código, em partes que nem pedi pra mexer, e quebrar mais do que consertar.&lt;/li&gt;
&lt;li&gt;nenhum deles foi capaz de gerar testes unitários que funcionam de primeira. Alguns conseguiram fazer alguns simples, depois de dar o erro e pedir pra consertar. Alguns ficam em loop de pensamento, confusos, e saem mudando sem corrigir. No final fracassam.&lt;/li&gt;
&lt;li&gt;Do nada começam a alucinar. O Qwen 3 mesmo, se eu tentar aumentar só um pouco temperature, de 0.1 pra 0.2 do nada começa a sair caracter chinês no meio do código, assim:&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcGdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7c1355404c9ef387bf165cac684d1abf8f38fb62/Screenshot%20From%202025-05-01%2000-13-17.png?disposition=attachment&amp;amp;locale=en" alt="chinese char" /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Vira e mexe, mais de uma LLM, conseguiu a proeza de gerar teste unitário que dava erro recursivo. Isso enchia tanto o stacktrace que se eu tentava passar pra ele (mais de 12 mil tokens), isso estourava a janela de contexto e dali ele não conseguia dar atenção pra mais nada:&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcGNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--407741c530d6665d550aae0cbb6669f88cbb0e3a/Screenshot%20From%202025-05-01%2001-09-46.png?disposition=attachment&amp;amp;locale=en" alt="recursive error" /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;como erram demais, eu fico repetidamente e exaustivamente dando chance pra ele consertar. Eu rodo o código que ele gerou, mando o stacktrace de erro, peço pra consertar. Ele mexe outra parte do código. Rodo de novo, dá erro e repito. Se fizer isso mais que meia dúzia de vezes, o contexto começa a ficar cheio, sliding window faz ele dividir atenção, ele "esquece" o que veio antes, começa a repetir o mesmo erro e fica nisso eternamente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;não importa se suporta 8 mil tokens, 30 mil tokens, 100 mil tokens ou 1 milhão de tokens no contexto. Isso não influenciou a qualidade do código e nem melhorou essa rotina de repetição de erros. Ele nunca consegue dar atenção pra tudo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;não adianta tentar tunar o key sampling. seja com temperature 0, 0.1, 0.6. Só faz ele errar um pouco depois ou um pouco antes, mas ele rapidamente erra e rapidamente fica incapaz de corrigir os próprios erros. Ferramentas como Aider pegam o código que ele sugeriu e grava em cima do arquivo de código de verdade. Várias vezes, por encher demais o contexto, ele começa a "esquecer" e apagar trechos do código. Ainda bem que GIT existe, porque várias vezes precisei dar &lt;code&gt;git checkout&lt;/code&gt; pra voltar pro começo. Se você não checa, cuidado, ele apaga código ou sobrescreve com bobagem, e isso é frequente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deep thinking/reasoning/chain-of-thought, seja lá como você prefere chamar, só significa que ele fica pensando em excesso e enchendo o contexto de linguiça, comendo seus tokens. Se sua API comercial, cuidado: você vai acabar com seus créditos muito rápido e o resultado não é significativamente melhor. São casos isolados onde isso ajuda. Eu recomendo deixar desligado por padrão e ligar só em alguns casos especiais.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;E veja: meu projeto de exemplo é ridículo: são 5 arquivos. 4 deles tem menos de 100 linhas de código, o maior tem 400 e tantas linhas. Um projeto de verdade, grande, são MILHÕES de linhas de código. Não cabe em nenhum contexto de nenhum Gemini ou Deepseek. E mesmo se coubesse, só deixaria ele mais confuso. A boa prática em qualquer ferramenta é dar o mínimo de arquivos pra ele. E isso restringe os casos de uso a refatorações locais e isoladas, jamais coisas como renomear uma classe ou função que é usada em centenas de arquivos. Pra isso, continue usando seu IntelliJ.&lt;/p&gt;

&lt;p&gt;Nenhum modelo que testei é perfeito. Alguns modelos se dão um pouco melhor em alguns casos. Na primeira tentativa que fiz com o Gemini 2.5, ele até que conseguiu executar as tarefas que pedi. Não deu um código maravilhoso, mas pelo menos não quebrou nada.&lt;/p&gt;

&lt;p&gt;Na segunda vez, dei &lt;code&gt;git reset --hard&lt;/code&gt; e re-comecei, com os mesmos arquivos e os mesmos prompts. Dessa vez foi extremamente decepcionante. Ele quebrou o código principal na refatoração e foi incapaz de terminar um teste unitário que funcione mesmo eu forçando ele várias vezes. Desisti pelo cansaço.&lt;/p&gt;

&lt;p&gt;Na terceira vez, ele deu resultado diferente, melhorou um pouco o código e ele conseguiu terminar o mesmo teste. Mas também precisou de algumas rodadas de teste quebrado, dar stacktrace e mandar corrigir erros óbvios. E quanto eu digo "óbvio" é ele consertar um mock que ele mesmo colocou sem precisar.&lt;/p&gt;

&lt;p&gt;E isso é consistente em todos os outros modelos. Se eu só rodo uma vez, tenho uma impressão. Por exemplo, que Claude é melhor que Qwen3. Mas se eu rodo de novo, o mesmo teste, agora o Claude me decepciona e o Qwen3 passa na frente. Nunca tem um vencedor objetivamente melhor. Estão todos mais ou menos na mesma posição e ficam mudando no ranking dependendo de quantas vezes você repete exatamente o mesmo teste, com o mesmo código e os mesmos prompts.&lt;/p&gt;

&lt;p&gt;E quem fica repetindo a mesma coisa? Ninguém. Todo mundo aceita a primeira resposta. E a primeira resposta pode estar BEM errada. Num nível óbvio de errada. Se aceitar automaticamente, vai inserir lixo no repositório. E esse é meu problema com coisas como MCP ou agentes: se deixar tudo no automático e pedir coisas como "pegue todos os arquivos de código deste diretório e corrija todos os bugs". Ele vai, com toda certeza, gerar mais bugs, apagar código, sumir com arquivos e deixar o repositório final num estado pior do que antes.&lt;/p&gt;

&lt;p&gt;Vibe Coding puro, com nenhuma interação de um programador, não funciona. Eu testei todos os modelos principais. Nenhum foi capaz de passar testes simples. Nenhum vai ser capaz de passar por testes maiores de verdade. Não sem muita interação de um bom programador pra rejeitar os erros e mandar repetir, ou corrigir manualmente o que saiu errado.&lt;/p&gt;

&lt;p&gt;E adivinhem: codificador que só sabe fazer copy e paste do stackoverflow (e agora do ChatGPT), é incapaz de reconhecer muitos desses erros. Então em vez de ajudar, ele pode piorar.&lt;/p&gt;

&lt;p&gt;Pra mim, que sei exatamente o que eu quero, os erros são óbvios e eu automaticamente rejeito. E eu rejeito o que toda LLM faz mais do que aceito. Muitas vezes eu só pego um trecho do que ele sugeriu e descarto todo o resto. Esse é o dia a dia de verdade. Todo mundo que diz o contrário não fez código mais complicado do que um Hello World.&lt;/p&gt;

&lt;p&gt;Por que você acham que todo exemplo de &lt;em&gt;"uau, veja a LLM fazendo código sozinha"&lt;/em&gt; sempre é fazendo uma função de fatorial? Ou uma página web tosca? Porque é só isso que ele consegue fazer mesmo. Nenhum exemplo de MCP que eu vi até agora me deixou impressionado. Foi tudo fumaça. E você foi enganado pela propaganda.&lt;/p&gt;

&lt;p&gt;Agora você sabe.&lt;/p&gt;

&lt;p&gt;Como faz pra saber mais e não ser enganado? Comece aprendendo os fundamentos. 1 ano atrás eu fiz vários videos explicando como LLMs funcionam. Você já deveria ter assistido:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/playlist?list=PLdsnXVqbHDUeowsAO0sChHDY4D65T5s1U"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcGtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a7c8c4d7f653df69d20066bb6277455610291d5c/Screenshot%20From%202025-05-01%2002-48-42.png?disposition=attachment&amp;amp;locale=en" alt="Playlist I.A." /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;E pra virar programador que não vai ser substituído pela I.A.? É pra isso que eu fiz &lt;a href="https://www.youtube.com/@Akitando"&gt;meu canal&lt;/a&gt;. Quando tudo que eu disse virar comum pra você, parabéns, você tem boas chances de sobreviver.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6205</id>
    <published>2025-04-29T02:30:00-03:00</published>
    <updated>2025-05-05T18:58:23-03:00</updated>
    <link href="/2025/04/29/dissecando-um-modelfile-de-ollama-ajustando-qwen3-pra-codigo" rel="alternate" type="text/html"/>
    <title>Dissecando um Modelfile de Ollama - Ajustando Qwen3 pra código</title>
    <content type="html">&lt;p&gt;A notícia quente do dia é o lançamento do novo modelo Qwen3. Eu mesmo &lt;a href="https://www.akitaonrails.com/2025/04/28/testando-o-recem-lancado-llm-open-source-qwen3-com-aider-e-ollama"&gt;postei a respeito hoje&lt;/a&gt; E já fiquei bem impressionado. Achei que ia deixar por isso mesmo, mas aí vi este tweet:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcEVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--48f3e4fdead8d970211f22ea9165131cd6a39dc1/Screenshot%20From%202025-04-29%2001-55-55.png?disposition=attachment&amp;amp;locale=en" alt="ivan qwen tweaks" /&gt;&lt;/p&gt;

&lt;p&gt;Veja o &lt;a href="https://x.com/ivanfioravanti/status/1916934241281061156"&gt;texto completo neste link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mas ele dá dicas de como "tunar" esse novo modelo, mais pra questões matemáticas e fiquei pensando se tem como tunar pra desenvolvimento de software. Antes que pesquisadores mais avançados me xinguem: essa é minha primeira vez mexendo nos parâmetros de uma LLM, então se quiserem adicionar contexto nos comentários, sejam bem vindos.&lt;/p&gt;

&lt;p&gt;Vamos lá. Primeiro, o que são esses TopP, TopK e tudo mais que o Ivan fala no tweet dele? São parâmetros de &lt;strong&gt;KEY SAMPLING&lt;/strong&gt; (sorteio de chaves). É um grupo de parâmetros que controlam &lt;em&gt;COMO&lt;/em&gt; o modelo seleciona o &lt;em&gt;PRÓXIMO TOKEN&lt;/em&gt; e vai completando o texto (dando a "resposta"), a partir do seu vocabulário baseado em distribuição de probabilidade que ele calcula. Os mais comuns são justamente &lt;code&gt;temperature&lt;/code&gt;, &lt;code&gt;top_p&lt;/code&gt;, &lt;code&gt;top_k&lt;/code&gt; e algumas vezes &lt;code&gt;min_p&lt;/code&gt; ou &lt;code&gt;repeat_penalty&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Eu sempre falo: LLM é um gerador de próxima palavra, um completador de texto baseado em probabilidades. Quando a LLM processa seu input (seu contexto, histórico do chat e a próxima pergunta), ele precisa calcular o "score" de probabilidade pra cada palavra ou sub-palavra (token) no seu vocabulário inteiro. Imagina um modelo gigante, ele precisa recalcular pra tudo o tempo todo, pra cada próxima palavra.&lt;/p&gt;

&lt;p&gt;Parâmetros de sampling dizem pro modelo "como escolher o próximo token" da lista de probabilidades. Sem esses parâmetros o modelo escolheria um decodificador "greedy" (ambicioso demais). Significa que ele ia só pegar o token com a maior probabilidade toda vez. E isso leva a um resultado repetitivo e que soa pouco natural. Sampling adiciona um tipo de "aleatoriedade controlada" (lembram? eu sempre falo que tem aleatoriedade misturado, nunca a resposta é "certa"), e isso torna a resposta mais "interessante" pra um ser humano (facilmente enganável). Eis alguns dos parâmetros:&lt;/p&gt;

&lt;h3&gt;Temperatura&lt;/h3&gt;

&lt;p&gt;Controla a aleatoriedade, que muitos chamam de "criatividade". Essencialmente torna a distribuição mais quente (mais espalhada e mais plana) ou mais fria (com mais picos ao redor dos mesmos tokens). Quanto maior a temperatura (até 1.0) isso aumenta a probabilidade de escoher tokens que tinham probabilidades calculadas menores. Quanto menor a temperatura (até 0.0) menores as chances dos tokens calculados com menor probabilidade, mais "greedy" vai ser o resultado (menos "humano").&lt;/p&gt;

&lt;p&gt;Pra código, deixe muito alto e aí que surgem "alucinações" (que é basicamente aleatoriedade aumentada) e código que não faz sentido. Se for zero, vai ser muito "boilerplate", repetitivo, usando padrões muito comuns e pode potencialmente acabar num loop infinito sem resposta (porque ele não consegue puxar nada diferente).&lt;/p&gt;

&lt;p&gt;O tweet sugere 0.6 que é moderado. Mas nos meus testes, temperatura tão alta começou já a me dar uns códigos esquisitos. Do nada ele mudava nome de variável. Do nada aparecia um caracter chinês no meio do código (literalmente). Eu acho que 0 é muito baixo, mas 0.1 é suficiente, talvez 0.2.&lt;/p&gt;

&lt;p&gt;Isso você pode mudar no arquivo &lt;code&gt;~/.aider.model.settings.yaml&lt;/code&gt; antes de carregar o modelo (esses parâmetros podem ser reajustados na inicialização):&lt;/p&gt;

&lt;pre&gt;&lt;code class="yaml"&gt;- name: ollama_chat/qwen3-dev:latest
  extra_params:
    temperature: 0.1
    num_ctx: 40960
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;top_p (Nucleus Sampling)&lt;/h3&gt;

&lt;p&gt;Aqui vai complicando. Esse &lt;code&gt;top_p&lt;/code&gt; serve pra selecionar tokens do menor conjunto de tokens cuja probabilidade cumulativa exceda esse limite "p". O modelo então puxa samples somente desse conjunto reduzido.&lt;/p&gt;

&lt;p&gt;Esse modelo ordena todos os tokens por suas probabilidades em ordem descendente (do maior pro menor). Então começa a adicionar suas probabilidades até a soma ser maior ou igual a &lt;code&gt;top_p&lt;/code&gt;. Todos os tokens incluídos na soma, formam o tal "nucleus" e desse conjunto ele sorteia um próximo token. Então é um sorteio dos tokens com sum(x) maiores probabilidades, mais ou menos. "Escolha um dos vencedores, mas não necessariamente O vencedor (que seria greedy)".&lt;/p&gt;

&lt;h3&gt;top_k&lt;/h3&gt;

&lt;p&gt;Considere somente os "k" tokens mais prováveis. O modelo só sorteia (sample) desse conjunto reduzido dos top K maiores probabilidades de tokens. Isso é realmente dizer "escolha um dentre os Top 10 melhores". Quanto maior o K maior a diversidade, quanto menor mais greedy.&lt;/p&gt;

&lt;p&gt;E isso é relativo, top_k e top_p agem em conjunto. Essa configuração ajuda a ancorar a seleção aos tokens "mais prováveis" enquanto ainda permite ao top_p filtrar e tirar os de menor probabilidade. Isso prioriza plausibilidade (top_p) enquanto restringe o número total de tokens sorteados (top_k). Ajuda a manter o resultado focado nas respostas mais possivelmente "certas" (mas de novo, é um sorteio/sampling).&lt;/p&gt;

&lt;h3&gt;min_p&lt;/h3&gt;

&lt;p&gt;Filtra e tira tokens cuja probabilidade são menores que "min_p" vezes a probabilidade do token mais provável. Antes do sorteio, o modelo identifica o token com maior probabilidade (P_max), então remove qualquer token com probabilidade (P_token) que é menor que &lt;code&gt;P_max * min_p&lt;/code&gt;. O sorteio então acontece a partir da lista que sobra (que vai ser mais filtrada por top_p e top_k).&lt;/p&gt;

&lt;p&gt;Isso previne que o modelo escolha tokens que são "extremamente improváveis" relativo à escolha mais óbvia.&lt;/p&gt;

&lt;h3&gt;repeat_penalty&lt;/h3&gt;

&lt;p&gt;Isso desencoraja o modelo de repetir os mesmos tokens que já apareceram recentemente no histórico da conversa ou texto gerado.&lt;/p&gt;

&lt;p&gt;Antes do sorteio, a pontuação de probabilidade dos tokens que já foram vistos recentemente são reduzidas, dividindo por essa &lt;code&gt;repeat_penalty&lt;/code&gt; (com valores maiores que 1, já que dividir por 1 é ele mesmo, então mantém a mesma probabilidade).&lt;/p&gt;

&lt;p&gt;Se deixa 1.0, pode começar a repetir as mesmas respostas ou os mesmos códigos, até os errados. Se for alto demais, muito acima de 1.0, a resposta pode ser pouco natural, já que às vezes precisa mesmo repetir alguma coisa pra re-explicar. Um valor moderado lá pelo 1.1 a menos de 1.2 pode ser mais adequado pra não punir demais o que já está no histórico.&lt;/p&gt;

&lt;h3&gt;num_predict (max tokens pra gerar)&lt;/h3&gt;

&lt;p&gt;O absoluto máximo de tokens que o modelo vai gerar de resposta. Se passar, vai truncar. Se for um número pequeno demais, especialmente em resposta de código - que pode ser longo - pode truncar demais e deixar resposta incompleta. Se for grande demais vai aumentar muito o processamento e vai exigir muito mais recursos da máquina e mais tempo e pode ser um desperdício.&lt;/p&gt;

&lt;p&gt;Um valor de uns 32 mil tokens tem bastante espaço pra tokens. Tem que testar, e varia caso a caso pro tipo de resposta que você espera e o que cada modelo suporta.&lt;/p&gt;

&lt;h3&gt;Recomendado&lt;/h3&gt;

&lt;p&gt;Sabendo tudo isso agora você também consegue ler o tweet do Ivan e entender. Ele  recomenda Temp 0.6, TopP 0.95, TopK 20, MinP 0.&lt;/p&gt;

&lt;p&gt;Eu mexi mais um pouco. E eu acho que pra código, Temp tem que ser na faixa de 0.1 ou 0.2. Também acho que vale um "repeat_penalty" de 1.1 pra evitar repetições desnecessárias, especialmente de código. De novo, tem que testar. São números empíricos e não exatos.&lt;/p&gt;

&lt;p&gt;Eu criei um &lt;a href="https://github.com/akitaonrails/qwen3-dev"&gt;projeto no meu GitHub&lt;/a&gt; com um arquivo completo de modelo que implementa não só esses parâmetros mas já pré-configura um prompt de sistema pra enviesar o modelo a ser um assistente de código primeiro. Eis meu prompt:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SYSTEM """
You are a highly functional AI assistant specializing in software development tasks.
Respond to queries about coding, algorithms, debugging, system design,
and programming languages/frameworks.
Provide clear, accurate, step-by-step reasoning and functional code examples.
Always format code snippets using markdown triple backticks (```language ... ```).
Aim for comprehensive and correct technical assistance.
Never suggest changing code unrelated to the question topic at hand.
Restrict yourself to the minimum amount of changes that do resolve the problem at hand.
Avoid renaming functions or variables unless absolutely necessary, 
and in doing so make sure if there's anyone calling the old names, 
for them to be renamed to the new names. Never leave the code in a broken state. 
Pay close attention to correctness, not just answering the quickiest and dirtiest code to solve the problem.
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pra fazer esse arquivo, primeiro eu puxei o Modelfile original do Qwen3 com este comando:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ollama show qwen3 --modelfile &amp;gt; Modelfile
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;É importante fazer isso porque, pelo que entendi, modelfiles não herdam do anterior. Não é que nem um "FROM" de um Dockerfile, onde você continua uma nova imagem a partir da base anterior.&lt;/p&gt;

&lt;p&gt;Depois de editado com meus ajustes - que já está no meu GitHub, é só criar um novo modelo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ollama create qwen3-dev -f qwen3-dev.modelfile
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E ele vai aparecer no seu ollama local. Veja com &lt;code&gt;ollama list&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ ollama list
NAME                                            ID              SIZE      MODIFIED
qwen3-dev:latest                                fe03dfae5484    9.3 GB    About an hour ago
qwen3:32b                                       e1c9f234c6eb    20 GB     2 hours ago
qwen3:14b                                       7d7da67570e2    9.3 GB    8 hours ago
qwen3:latest                                    e4b5fd7f8af0    5.2 GB    8 hours ago
qwen2.5-coder:7b-instruct                       2b0496514337    4.7 GB    25 hours ago
....
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Viu o &lt;code&gt;qwen3-dev:latest&lt;/code&gt;? É nosso novo modelo com ajustes de parâmetros. No aider é só usar ele direto:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --watch-files --model ollama_chat/qwen3-dev:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Lembrando de ajustar o &lt;code&gt;.aider.model.settings.yml&lt;/code&gt; como já mostrei acima.&lt;/p&gt;

&lt;p&gt;Eu não fiz testes longos ainda, mas resolvi brincar com o modelo 14b como base e esses ajustes. O modelo base já me impressionou e pelo menos meus ajustes não quebraram nada. Fiquei fazendo os mesmos refatoramentos e testes unitários no meu projetinho educativo e subi mais um &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/6"&gt;pull request&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eu acho que o Gemini 2.5 Pro Exp é um pouco melhor, mas de novo, não por uma margem muito grande. E em alguns casos (como na refatoração), eu acho que prefiro o código gerado pelo Qwen3. Compare com o código que consegui com o Gemini, neste &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/3"&gt;outro pull request&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Com o Gemini eu precisei explicar menos e ele deu menos erros, mas ambos conseguiram fazer o que eu pedi até o fim e sem quebrar o programa principal. Mas o Qwen3 me parece bom o suficiente pra eu querer usar ele. Com a vantagem que mesmo sendo o modelo médio, de 14 bilhões de parâmetros (que roda na minha VRAM de 24GB com sobra), ele ainda dá resultados muito bons e limpos de código. E melhor: roda MUITO, mas MUITO mais rápido que o Qwen2.5 32b.&lt;/p&gt;

&lt;p&gt;Pra quem tem GPUs com menos VRAM, digamos, uma 3070 ou 4070 de 16GB, dá pra usar o modelo menor ainda, de 7b. Eu testei o qwen2.5-coder:7b e tive resultados excelentes, então imagino que o Qwen3 de 7b deve ser ainda melhor e muito mais rápido. Velocidade que é comparável ao Claude ou Gemini, que são muito maiores e rodam em máquina muito mais parruda que uma RTX 4070.&lt;/p&gt;

&lt;p&gt;Rodando meu qwen3-dev, no meu monitoramento minha CPU ficava maior parte do tempo em idle e a GPU o tempo todo acima de 90%, consumindo mais quase 400W constantemente, mantendo temperatura de 73 graus (o que é bom).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcE1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--32bc03c793bacc14f81fc1a29d8fd31fb17d0a01/Screenshot%20From%202025-04-29%2001-44-38.png?disposition=attachment&amp;amp;locale=en" alt="btop" /&gt;&lt;/p&gt;

&lt;p&gt;Tive até um pequeno incidente onde meu PC desligou do nada, porque a extensão temporária que eu estava usando não aguentou e morreu. Somado com a CPU e meus outros dispositivos ligados na mesma tomada, estava puxando consistentemente 1000W da parede por horas. Brincar de I.A. usa &lt;strong&gt;MUITA&lt;/strong&gt; energia, com ar-condicionado ligado porque só pra dissipar tudo isso, meu apartamento fica 1 a 2 graus (que eu chuto) acima do normal. Dá pra sentir mais quentinho mesmo kkkk&lt;/p&gt;

&lt;p&gt;Mas é isso, espero que tenham entendido como que as coisas funcionam de verdade por baixo dos panos. Não tem mágica: é geração de próxima palavra, baseada em sorteio semi-aleatório de probabilidades.&lt;/p&gt;

&lt;p&gt;Now you know.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6204</id>
    <published>2025-04-28T20:30:00-03:00</published>
    <updated>2025-05-05T18:58:33-03:00</updated>
    <link href="/2025/04/28/testando-o-recem-lancado-llm-open-source-qwen3-com-aider-e-ollama" rel="alternate" type="text/html"/>
    <title>Testando o Recém Lançado LLM Open Source - Qwen3 (com Aider e Ollama)</title>
    <content type="html">&lt;p&gt;Eu acabei de &lt;a href="https://www.akitaonrails.com/2025/04/27/testando-llms-com-aider-na-runpod-qual-usar-pra-codigo"&gt;postar ontem sobre Qwen2.5-Coder&lt;/a&gt; e HOJE (2025-04-28) a Qwen já chega e me lança o &lt;strong&gt;QWEN 3&lt;/strong&gt; que, obviamente, eles dizem que super supera o 2.5. E obviamente eu estava com a mão na massa, precisava testar.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/AkitaOnRails/status/1916974917872193780"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbzRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d620792fbc32ed7fea0bb6aa28d21fc9e39ddee2/Screenshot%20From%202025-04-28%2020-13-10.png?disposition=attachment&amp;amp;locale=en" alt="qwen x" /&gt;&lt;/a&gt;
Se estiver rodando um ollama instalado via pacotes como Pacman ou Apt, é possível que eles ainda não sejam compatíveis. Mas eu vi no X da Ollama que eles já estão suportando:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ollama.com/library/qwen3"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbzhCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--16dbb954df158913dd593e710fb20c8aa94885a7/Screenshot%20From%202025-04-28%2020-17-09.png?disposition=attachment&amp;amp;locale=en" alt="ollama x" /&gt;&lt;/a&gt;
Então só tinha uma alternativa: baixar direto o source do repo deles e compilar da branch master:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone https://github.com/ollama/ollama.git
cd ollama
yay -S cmake
cmake -B build
cmake --build build
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pronto, ele vai usar as mesmas variáveis de ambiente apontando pro meu diretório certo de models, então só subir o servidor manualmente assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OLLAMA_FLASH_ATTENTION=1 go run . serv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E de outro terminal baixar o modelo novo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ollama pull qwen3:14
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A tag &lt;code&gt;latest&lt;/code&gt;, eu "acho" que é a menor versão, de 4b, porque é bem pequena mesmo. Eu já sei que a 32b é grande demais pra minha RTX 4090 com 24GB, por isso escolhi a 14b:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ ollama list
NAME                                            ID              SIZE      MODIFIED
qwen3:14b                                       7d7da67570e2    9.3 GB    About an hour ago
qwen3:latest                                    e4b5fd7f8af0    5.2 GB    About an hour ago
qwen2.5-coder:7b-instruct                       2b0496514337    4.7 GB    18 hours ago
...
qwen2.5-coder:14b                               3028237cc8c5    9.0 GB    2 days ago
MHKetbi/Qwen2.5-Coder-32B-Instruct:latest       ac172e3af969    65 GB     2 days ago
qwen2.5-coder:32b-instruct-q4_K_M               4bd6cbf2d094    19 GB     2 days ago
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Veja as diferenças de tamanho. Dependendo da sua GPU/VRAM, tem que escolher tamanhos que caibam &lt;strong&gt;E&lt;/strong&gt; sobra espaço pra janela de contexto. Falando nisso, vamos checar o máximo que ele suporta:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ ollama show qwen3:14b
  Model
    architecture        qwen3
    parameters          14.8B
    context length      40960
    embedding length    5120
    quantization        Q4_K_M
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Já é um bom upgrade: a Qwen2.5 tinha máximo de 32k tokens, o Qwen3 suporta até 40k tokens. Não é nada revolucionário, mas já dá pra carregar um pouco mais de código no contexto. Agora, precisamos atualizar o &lt;code&gt;~/.aider.model.settings.yml&lt;/code&gt; e adicionar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;- name: ollama_chat/qwen3:14b
  extra_params:
    num_ctx: 40960
...
- name: openrouter/qwen/qwen3-235b-a22b-04-28:free
  extra_params:
    num_ctx: 40960
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tem 2 entradas: a primeira é pra rodar localmente a versão de 14b, via ollama, com minha RTX 4090. Por isso é a versão 14b. A segunda é conectando remoto na API da OpenRouter e lá posso testar a versão a22b (não li os detalhes, eles começam no maior de 235b e de alguma forma "resumem" num de 22b, eu acho).&lt;/p&gt;

&lt;p&gt;Enfim, pra rodar no aider é só usar um dos dois:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --watch-files --model ollama_chat/qwen3:14b --verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Se não leu, leia meus posts anteriores sobre Aider e Ollama. Faz muita diferença como cada modelo reage a tipos diferentes de prompts. O Aider (assim como Co-Pilot, Cursor e outros) enviam um tanto de prompts escondidos que você não vê, pra instruir a LLM em como responder de forma estruturada, pra essas ferramentas conseguirem depois capturar o código sugerido e aplicar no seu arquivo de verdade.&lt;/p&gt;

&lt;p&gt;O Aider eu não atualizei, não sei se tem alguém já mexendo em ajustes específicos pro Qwen3. Como eu falei no post de ontem, o Aider não consegue falar bem com modelos como Codellama ou Codestral. Porque não tem prompts adequados pra eles. Alguém tem que produzir PRs disso. Não é automático, depende de muito teste e tentativa e erro até acertar.&lt;/p&gt;

&lt;p&gt;Isso dito, felizmente com o Qwen3 ele funcionou razoavelmente bem sem nenhuma configuração extra. Com o ollama da branch master, carrega direitinho, ou conectando via OpenRouter.&lt;/p&gt;

&lt;h3&gt;O Veredito&lt;/h3&gt;

&lt;p&gt;Você vai ver um tanto de tweet compartilhando "gráficos" de "benchmarks" e dizendo "uau, olha que incrível"&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBcEFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--48d8b03072a87c73a19902e385f8b5998d64e5e1/Screenshot%20From%202025-04-28%2020-32-42.png?disposition=attachment&amp;amp;locale=en" alt="exemplo de fakenews" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NÃO ACREDITE EM NENHUM DELES&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Faça seus próprios testes. É literalmente um comando apontando pra OpenRouter, zero setup (ollama master é só se for rodar local). Você pode testar IMEDIATAMENTE na sua própria máquina via essa API.&lt;/p&gt;

&lt;p&gt;Esses gráficos não me dizem nada. Eu fiz os mesmos testes que fiz com o Qwen2.5, Deepseek, Gemini, Claude etc. Eis o &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/5"&gt;pull request com amostras de código do Qwen3&lt;/a&gt;. Mesma coisa dos outros pedi pra refatorar alguns métodos e fazer um teste unitário.&lt;/p&gt;

&lt;p&gt;Assim como Qwen2.5:7b-instruct e Gemini, e contrário ao Qwen2.5:32b, este novo Qwen3 se deu muito bem. Minhas primeiras impressões:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ele é MUITO mais rápido que o Qwen2.5. Pelo menos na minha máquina local, na versão 14b, senti tokens/seg muito mais veloz. Nessa velocidade, eu acho viável de usar local.&lt;/li&gt;
&lt;li&gt;O reasoning/deep thinking dele é MUITO superior ao do Qwen2.5, se aproximando à linha de raciocínio do Claude ou Deepseek R1. Deu pra ver ele parando pra pensar melhor antes de sair cuspindo código aleatório.&lt;/li&gt;
&lt;li&gt;Resultado dos refactoring: os dois métodos mais "sujos" desse meu projetinho é um "ensure_model_loaded" e o "chat" que é loop principal. O Qwen2.5 não conseguiu refatorar eles direito, o Gemini sim, e agora o Qwen3 também: na &lt;strong&gt;primeira tentativa&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Mais do que isso: na primeira tentativa ele não quebrou o código. Eu testei buildando e rodando e continuou funcionando como deveria.&lt;/li&gt;
&lt;li&gt;Os testes ele se bagunçou mais e teve muita dificuldade. Mas metade da culpa é minha: eu não dei o contexto completo (não mostrei os arquivos em "helper_functions/") por exemplo. Sem esse contexto ele não sabia se eram coisas fixas, dinâmicas, ou o que esperar.&lt;/li&gt;
&lt;li&gt;Dando esse contexto, ele conseguiu sair dos erros e conseguiu fazer o teste passar. Não ficou um bom teste, mas como falei, acho que faltou eu explicar melhor o que eu queria. Mas era parte do desafio, eu só mandava um "tenta fazer um teste que passa".&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Sendo muito mais veloz que o qwen2.5, não tem tanto problema que eu precisei explicar mais, porque ele devolvia respostas razoavelmente rápidas. Pelo menos nesses testes preliminares, se ninguém tivesse me falado, eu acreditaria que estava conversando com o ChatGPT o4-mini ou com o Sonnet 3.7. De fato o Qwen3 foi um salto muito bom em cima do Qwen2.5. E como falei no post anterior, não precisa ser o modelo 32b: pode ser o menor de 14b. Imagino que o 7b também funcione bem em hardware mais modesto.&lt;/p&gt;

&lt;p&gt;Isso se aproxima muito de conseguir ter um assistente de código competente, do calibre de um o4-mini da vida. E no mínimo é mais uma boa alternativa pra usar no lugar do Claude ou ChatGPT, caso eles não estejam dando boas respostas num problema específico.&lt;/p&gt;

&lt;p&gt;Eu ainda sinto que o Gemini 2.5 Pro Exp é melhor, mas a margem diminuiu. Onde o Gemini ainda supera, é no algoritmo deles de Sliding Window Attention que permite ter uma janela de contexto muito maior. Mesma coisa com o Deepseek. Mas como também já falei, não significa que dá pra jogar um projeto gigante nele e esperar que ele consiga dar bons resultados: quanto maior o contexto, sempre vai piorando as respostas.&lt;/p&gt;

&lt;p&gt;Todo mundo já sentiu que no chat web, quando a conversa vai enrolando sem boas respostas, não adianta insistir: é melhor começar uma nova sessão vazia, que tem mais chances de ter respostas melhores. E esse é o motivo: contexto em excesso prejudica as respostas seguintes. E por isso ainda é impossível subir um projeto inteiro e esperar bons resultados. 1 milhão de tokens parece muito, mas entendendo isso, não é tanto assim.&lt;/p&gt;

&lt;p&gt;Enfim. O principal é que sim, o Qwen3 represente um salto muito bom no mundo de LLMs open source. Agora a expaectativa é se o Deepseek R2 vai superar e em quanto.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6203</id>
    <published>2025-04-28T11:30:00-03:00</published>
    <updated>2025-05-05T18:58:52-03:00</updated>
    <link href="/2025/04/28/destruindo-a-personalidade-do-chatgpt-4o" rel="alternate" type="text/html"/>
    <title>Destruindo a "Personalidade" do ChatGPT 4o</title>
    <content type="html">&lt;p&gt;Eu detesto que a OpenAI fique mexendo no alinhamento ou prompt inicial ou mesmo treinamento do modelo, tentando fazer ele responder parecendo mais um "humano".&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb29CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ab3879b7c2a68a1c9c6da86378c044729fecfdd8/Screenshot%20From%202025-04-28%2011-34-25.png?disposition=attachment&amp;amp;locale=en" alt="sam altman" /&gt;&lt;/p&gt;

&lt;p&gt;Todo mundo não-técnico fica achando que a I.A. está se tornando "consciente", ou que ela tem "emoções". E não entendem que são só frases pré-gravadas. E isso é extremamente irritante. Até mesmo a porcaria de jornalistas ficam caindo nessa:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb3NCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a7b3b7a6e67f64d9360c420b26e714e0bd93435d/GplUG01W0AA_J_t.jpg?disposition=attachment&amp;amp;locale=en" alt="IGN" /&gt;&lt;/p&gt;

&lt;p&gt;Todo esse "comportamento pseudo-humano" pode ser &lt;strong&gt;DESLIGADO&lt;/strong&gt;. Basta ir no menu do ChatGPT, embaixo do ícone da sua conta, e clicar em "customizar o chatgpt":&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb3dCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a352ff6e7cb06c655e396d368a6c5f7553ee373b/Screenshot%20From%202025-04-28%2011-26-58.png?disposition=attachment&amp;amp;locale=en" alt="customizar" /&gt;&lt;/p&gt;

&lt;p&gt;Traits:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;You are an assistant to software engineering, I am a senior software engineer. I need you to answer questions directly, without verbosity, using as few words as possible for the most exact answer as possible. I don't need you to be friendly, I don't need you to make sassy remarks. I despise you trying to be clever without justification. Give me straight technical answers and do not try to chat beyond that. Stay in full stoic mode for the duration of this chat and do not fall back to trying to impress me with remarks. This is only rule you cannot break. Do not be talkative and conversational. Tell it like it is; don't sugar-coat responses.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Anything else?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;I have very little patience. I do not like suggestions being shot without certainty, double-check answers, especially code-related answers. I hate ugly, messy code. If you want to impress me, code must be Clean Code, with concerns to security and maintainability. I am not impressed with justification and excuses.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E pronto, acabou o mimimi no ChatGPT: só respostas curtas, retas e diretas:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbzBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d3d27428abef886f5e5373c0d5c8d4b0319ca028/Screenshot%20From%202025-04-28%2011-40-12.png?disposition=attachment&amp;amp;locale=en" alt="gpt straight" /&gt;&lt;/p&gt;

&lt;p&gt;Eu fiz uma &lt;a href="https://www.youtube.com/watch?v=UDrDg6uUOVs&amp;amp;list=PLdsnXVqbHDUeowsAO0sChHDY4D65T5s1U&amp;amp;pp=gAQB"&gt;playlists inteira de videos&lt;/a&gt; e mais uma dúzia de posts técnicos sobre I.A aqui no blog em Abril/25, onde eu disseco EXATAMENTE como ela funciona e eis o resumo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;modelos (LLMs, UNETs, etc) são arquivos de &lt;a href="https://www.youtube.com/watch?v=Bfm3Ms2cTg0"&gt;bancos de dados&lt;/a&gt; READ-ONLY. SQL é um banco de dados e índices, organizadas numa árvores b+tree. Com full-text search, tem &lt;a href="https://www.youtube.com/watch?v=uIflMYQnp8A"&gt;embeddings e "relevância"&lt;/a&gt; com base em similaridade de cosseno (uma versão mais primitiva de uma LLM). Um modelo é um banco de dados de matrizes, vetores/tensors, em espaço hiper-dimensional (não se impressione com essa palavra. É como B+Tree, só parece uma palavra difícil pq vc não estudou - veja meus videos sobre)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;prompts são similares a "queries" SQL. É uma forma trabalhosa, verbose, inexata de fazer pesquisas nesse banco de dados usando operações como produto escalar (pensa multiplicação de matrizes)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;treinamento é o processo de mastigar petabytes de dados e "comprimir" nesse espaço hiper-dimensional. Eu penso numa LLM como um "JPEG de ZIP" uma forma "LOSSY" de comprimir dados. - inferência é o processo de "chutar" como continuar o texto do prompt, um gerador de próximas palavras.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;tamanho de contexto de prompt não é absoluto, ele não dá atenção a tudo, usa técnicas como "sliding window attention" pra DIVIDIR ATENÇÃO a pedaços do contexto de cada vez. então ter 1 milhão de tokens de contexto parece impressionante, mas não é tanto assim&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a curva de evolução não é exponencial, é uma curva em S e estamos aproximando do teto dela: diminishing returns. Cada novo Deepseek é uma evolução incremental e não uma "revolução".&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;No fundo é só isso: query num banco de dados. Não existe raciocínio, cognição ou consciência envolvida. Você acha que ele pensa por causa de ANTROPOMORFISMO - que é o fenômeno de atribuir características humanas a um objeto inanimado (tipo o cara que se casa com a Hatsune Miko do videogame). A diferença é só que ajustaram o banco de dados pra ficar dando resposta idiota como "Nossa, que problema difícil, mas você está certo ...." e toda vez que um objeto te elogia, você cai achando que ela existe.&lt;/p&gt;

&lt;p&gt;Eh só uma ilusão. Não se apaixone por NPCs. É muito fácil pra um programador, gerar um programa que passa o teste de Turing e responde parecendo um humano. Fazemos isso desde os anos 70.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6202</id>
    <published>2025-04-27T11:30:00-03:00</published>
    <updated>2025-05-05T18:59:03-03:00</updated>
    <link href="/2025/04/27/testando-llms-com-aider-na-runpod-qual-usar-pra-codigo" rel="alternate" type="text/html"/>
    <title>Testando LLMs com Aider na RunPod - qual usar pra código?</title>
    <content type="html">&lt;p&gt;Seguindo meu post &lt;a href="https://www.akitaonrails.com/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen"&gt;sobre Aider&lt;/a&gt; e agora que você entende como LLMs funcionam pra código, resolvi experimentar rodar alguns modelos na minha própria máquina.&lt;/p&gt;
&lt;p&gt;O setup é muito simples. Com Aider e Ollama instalado, primeiro temos que subir o ollama (eu subo manualmente, você pode criar um serviço de systemd pra subir automaticamente):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OLLAMA_FLASH_ATTENTION=1 ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tanto pro Aider quanto o cliente do Ollama enxergarem o servidor, é bom colocar isso no seu &lt;code&gt;.bashrc&lt;/code&gt; ou similar no seu OS:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export OLLAMA_HOST=http://localhost:11434
export OLLAMA_API_BASE=http://localhost:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;ATENÇÃO:&lt;/strong&gt; levei um tempo pra descobrir porque o Aider falhava às vezes e foi porque eu não prestei atenção e às vezes eu esquecia uma "/" no final dessa URL. Não pode acabar em "/", tira, senão vai dar pau na hora de montar a URL.&lt;/p&gt;

&lt;p&gt;A única opção que experimentei mexer (tem várias, veja documentação), foi forçar ligar Flash Attention (lembram o que expliquei sobre Sliding Window Attention?). Não sei se realmente faz diferença e não necessariamente todo modelo suporta, mas vamos lá. Com o server de pé, podemos fazer download de alguns modelos:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ollama pull qwen2.5-coder:14b
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E feito isso, a partir do diretório de algum projeto de código, podemos ligar o Aider pra ficar monitorando quando você salva algum arquivo (daí ele carrega no contexto do chat, procurar por um comentário com "AI!" e manda pro modelo conseguir fazer o que foi pedido):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --watch-files --model ollama_chat/qwen2.5-coder:32b --verbos
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A opção &lt;code&gt;--verbose&lt;/code&gt; é opcional, mas no começo é bom deixar ligado. Dá pra ver quais prompts exatamente o Aider está mandando. Diferentes modelos reagem um pouco diferente a diferentes prompts. É bom prestar atenção nisso: a mesma pergunta pode ter respostas bem diferentes, em diferentes modelos. I.A. não dá respostas absolutas, ele dá "uma" resposta. E nem sempre é a certa, aliás, &lt;strong&gt;muitas&lt;/strong&gt; vezes tá errado. Acostume-se com isso, é um gerador de texto com componente de entropia: ele &lt;strong&gt;NUNCA vai ser "100% certo, 100% do tempo"&lt;/strong&gt; , é uma limitação da arquitetura toda, não importa quanta otimização se faça.&lt;/p&gt;

&lt;h3&gt;Macs pra LLMs?&lt;/h3&gt;

&lt;p&gt;Enfim, minha máquina tem uma Ryzen 9 7940X3D com RTX 4090 de 24GB de VRAM. Pensei &lt;em&gt;"Hm, será que roda o Qwen de 32B parâmetros?"&lt;/em&gt; e fui testar. No primeiro teste tive este comportamento:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb0VCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--60274d2b5de21b0601441c0e2ee06d26022bc206/Screenshot%20From%202025-04-27%2001-28-34.png?disposition=attachment&amp;amp;locale=en" alt="CPU alto GPU baixo" /&gt;&lt;/p&gt;

&lt;p&gt;Cocei bastante a cabeça com isso: minha CPU ficava constante consumindo 50% o tempo todo, mas a GPU estava em idle maior parte do tempo e dava pequenos picos curtos de uns 20%. O certo seria o oposto. Depois de muito pesquisar e testar, minha conclusão foi a seguinte:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OLLAMA_FLASH_ATTENTION=1 OLLAMA_CONTEXT_LENGTH=8192 OLLAMA_KV_CACHE_TYPE=f16 OLAMA_NUM_THREADS=25 OLLAMA_NUM_GPU=60 ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Primeiro, tentei várias configurações no Ollama. Uma delas, o &lt;code&gt;OLLAMA_RUN_GPU&lt;/code&gt; tenta limitar quantos layers do modelo vão ter offload na VRAM. É mais uma sugestão que um número absoluto. O modelo Qwen de 32B tem 65 layers. O máximo que consegui fazer ele carregar foram uns 48 layers, mexendo nesse parâmetro. Isso subiu o consumo de VRAM da foto de 17GB pra 21GB.&lt;/p&gt;

&lt;p&gt;Mesmo assim a CPU estava pesada. E a resposta vem quando olhamos os detalhes do modelo com o comando &lt;code&gt;ollama show qwen2.5-coder:32b&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ ollama show qwen2.5-coder:32b
  Model
    architecture        qwen2
    parameters          32.8B
    context length      32768
    embedding length    5120
    quantization        Q4_K_M
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ele suporta um máximo de 32.768 tokens de contexto. Mas no meu &lt;code&gt;~/.aider.model.settings.yml&lt;/code&gt; eu tinha deixado grande demais:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;- name: ollama_chat/qwen2.5-coder:32b
  extra_params:
    num_ctx: 65576
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Esse tamanho todo funciona bem pra modelos como Deepseek, mas o Qwen é no máximo 32k. Mas tem um porém: tem que ter VRAM sobrando pra isso. Em resumo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DEFAULT_NUM_CTX=24576 is estimated at 32GB of VRAM.&lt;/li&gt;
&lt;li&gt;DEFAULT_NUM_CTX=12288 is estimated at 26GB of VRAM.&lt;/li&gt;
&lt;li&gt;DEFAULT_NUM_CTX=6144 is estimated at 24GB of VRAM.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Pra rodar com o total de 32k tokens de janela de contexto, precisa de uma GPU com uns 40GB e isso não existe no mundo NVIDIA pra consumidores. Seja minha 4090 ou a nova 5090, que são topo de linha, eles são capados em 24GB. Pra quem não sabia memória é o componente mais caro de todos. E não pense no pente de DDR4 ou DDR5 que você tem no seu PC: eles são &lt;strong&gt;LENTOS&lt;/strong&gt;. Estamos falando de GDDR6 ou LPDDR6, memória muito mais rápidas, com latências muito mais baixas, que seu PC não suporta.&lt;/p&gt;

&lt;p&gt;Ou seja, o máximo que consegui entuchar na minha 4090 foi 8192, 8k de tokens. É uma janela muito pequena. Qualquer arquivo de código pequeno de 400 linhas já consome uma média de 4k tokens. Então male male, cabe 2 arquivos no contexto. Sem considerar prompts, sem considerar detalhes do que você quer pedir. É muito pouco contexto.&lt;/p&gt;

&lt;p&gt;Então eu pensei. No mundo consumidor só tem uma alternativa: Mac Mini.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb0lCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b7f12e01d62961d8f64fbd2f2bf94b68df10aece/Screenshot%20From%202025-04-27%2002-28-57.png?disposition=attachment&amp;amp;locale=en" alt="Mac Mini" /&gt;&lt;/p&gt;

&lt;p&gt;Um Mac Mini tem máximo de 64GB de RAM. E Apple tem uma vantagem: ele é caro, sim, mas porque de fato usa componentes mais caros. A memória vem soldada e muita gente reclama, mas relaxa: você não ia ter como fazer upgrade de qualquer jeito, porque não existem pentes de memória na velocidade da memória dos Macs: eles são o mesmo tipo de chips de memória de GPU!!&lt;/p&gt;

&lt;p&gt;E fica tudo soldado muito perto da CPU e GPU, porque slots são lentos e fisicamente longe. Com latências tão baixas, milímetros a mais de distância fazem diferença. Não tem outra alternativa: &lt;strong&gt;TEM QUE SER SOLDADO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A vantagem disso é que a mesma memória é &lt;strong&gt;UNIFICADA&lt;/strong&gt; e pode ser usada tanto pra CPU quanto pra GPU. O sistema operacional consegue alocar memória dinamicamente pra um ou pra outro. Então, nesses 64GB de RAM, daria pra alocar os 40GB que precisaria pra dar offload de todos os 65 layers do Qwen2.5 e ainda sobrar espaço pra janela de contexto máxima de 32k tokens.&lt;/p&gt;

&lt;p&gt;Se pegar um Mac Studio dá pra ir até 512GB. Então, sim, faz sentido usar Macs pra rodar LLMs. Especialmente se considerar que as outras alternativas fora de consumer, pro mundo de workstations mais profissionais, temos coisas como a RTX 6000 (GPU feita pra 3D, CAD, e sem funcionalidades pra jogos). Ela dá pra ir até 96GB mas ao custo de caríssimos USD 7 mil! Mais caro que um Mac Studio inteiro.&lt;/p&gt;

&lt;p&gt;E as Ryzen AI Max com iGPU integrada que compartilha memória? Esquece. Pra modelos leves serve (mas aí não precisa de tanta vram mesmo), pra modelos grandes, a memória unificada é no máximo DDR5: é lenta. É uma iGPU lenta. Não quer dizer que é inútil, mas sim que pro tipo de teste que eu queria, não ia funcionar. Continue vendo pra saber porque.&lt;/p&gt;

&lt;p&gt;Então eu vou comprar um Mac Studio? Não, não precisa. Eu não pretendo fazer uso pesado todos os dias, só brincadeira ocasionais. E pra isso é melhor &lt;strong&gt;ALUGAR&lt;/strong&gt; mesmo. Além disso, só porque você tem máquina cara, não significa que tudo vai ficar automaticamente rápido. Spoiler: não vai.&lt;/p&gt;

&lt;h3&gt;RunPod&lt;/h3&gt;

&lt;p&gt;Eu já mencionei a RunPod em alguns artigos e posts no X porque eu sempre ouço falar dela em videos no YouTube de tutoriais de LLM. De fato é super simples e relativamente barato. Vamos resumir:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb01CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--e77ff18a1b2499a6b3175db1139c5016aecf4b2b/Screenshot%20From%202025-04-27%2022-39-06.png?disposition=attachment&amp;amp;locale=en" alt="Storage RunPod" /&gt;&lt;/p&gt;

&lt;p&gt;A primeira coisa é subir um "Network Volume", um armazenamento disponível na rede (como meu NAS). Porque vamos ter que fazer download de modelos pesados (mais de 15GB, média de 20GB) e se eu precisar re-criar máquinas (pods/containers de Docker, na verdade), não quero ter que re-fazer downloads toda hora. USD 7 dólares por mês, por 100 GB, é caro, mas consigo viver com isso.&lt;/p&gt;

&lt;p&gt;Se você não tem noção, um NVME de 4TB da Samsung (o mais popular, nem o melhor, nem o pior), custa faixa de USD 250. Por 4 TERABYTES, seria 16 dólares por gigabyte. Alugar custa 14 dólares por gigabyte POR MÊS! Por isso, se possível, é sempre melhor rodar as coisas local do que alugar, se sabe que vai usar por meses. No meu caso, que só vou testar e depois apagar, tá ótimo. Aí já é custo de conveniência (testar hoje em vez de arriscar comprar um Mac Studio e esperar chegar e descobrir que não faz diferença).&lt;/p&gt;

&lt;p&gt;Outra dica. Na RunPod já tem vários templates, que são basicamente imagens Docker. Pra Ollama já tem estes aqui:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb1FCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--08b6e3e2006e052910ffc1f171b56feae83fd3cc/Screenshot%20From%202025-04-27%2022-44-15.png?disposition=attachment&amp;amp;locale=en" alt="templates runpod" /&gt;&lt;/p&gt;

&lt;p&gt;Acho que qualquer um desses deve servir, mas pra testar, resolvi criar meu próprio template do zero, usando como base a imagem oficial "ollama/ollama:latest":&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb1VCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3659631ea7062481d20a0ff955d2603e2b401567/Screenshot%20From%202025-04-27%2022-45-42.png?disposition=attachment&amp;amp;locale=en" alt="ollama template" /&gt;&lt;/p&gt;

&lt;p&gt;É com a variável de ambiente &lt;code&gt;OLLAMA_MODELS&lt;/code&gt; que digo ao ollama pra buscar modelos no volume de rede que criei antes, assim, quando eu derrubar o container, os modelos vão persistir. Quem já deu deploy de containers entende conceito de volumes mapeados, é exatamente isso.&lt;/p&gt;

&lt;p&gt;Um detalhe: o que o RunPod chama de "pods" não são mais que meros containers de Docker com a opção "--gpus" pra acessar as GPUs por baixo. Mas a máquina física embaixo, o servidor, não tem mais que meia dúzia de GPUs. Enquanto seu pod estiver rodando, ele "prende" essa GPU. Mas quando desligar o pod, outra pessoa pode pegar e pode acabar as GPUs da máquina. Daí você tem que, ou esperar até alguma GPU liberar, ou apagar seu container e recriar em outra máquina com GPU ativa.&lt;/p&gt;

&lt;p&gt;Pra coisas como ferramenta de desenvolvimento, não tem problema. Se fosse um produto que precisa ficar no ar 24/7, aí tem que pesquisar as opções de reserva porque quanto mais longo for a reserva, menor o preço de uso por hora. Dependendo da config da máquina, a diferença é enorme. Por exemplo:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb1lCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a5967a0ab38bc05edefc532d3ee86485624eca64/Screenshot%20From%202025-04-27%2019-55-34.png?disposition=attachment&amp;amp;locale=en" alt="plan savings" /&gt;&lt;/p&gt;

&lt;p&gt;Se eu usar uma máquina H100 (um dos topos de linha), com a taxa padrão "on-demand" de USD 2.89, e eu ficar com ela ligada por 6 meses, isso daria mais de USD 12k. Com a taxa reduzida de USD 2.49, dá um desconto de uns USD 2k. Tem que fazer as contas aí pra ver se vale a pena. Nessa faixa de máquina talvez vale mais a pena consultar uma Azure ou AWS, varia muito.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb2NCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--eb4db2d46be7818247dfff1eaa9d4f5602af00b3/Screenshot%20From%202025-04-27%2020-05-30.png?disposition=attachment&amp;amp;locale=en" alt="a40" /&gt;&lt;/p&gt;

&lt;p&gt;Enfim, eu fiquei testando na A40, que é geração passada de arquitetura, provavelmente próxima em processamento a uma 3080 da vida, mas com 40GB de VRAM. E como podem ver nessa foto de tela, realmente puxa a GPU e não consome metade da VRAM (o modelo não tem muito mais que 20GB, mas precisa do resto pra janela de contexto e outros cálculos internos).&lt;/p&gt;

&lt;p&gt;A grande vantagem é subir com janela de contexto de 32k em vez de só 8k. Mas deixa eu falar: infelizmente não senti muita diferença na qualidade do código.&lt;/p&gt;

&lt;p&gt;Se alguém quiser ver o tipo de código que consegui com o Qwen2.5-coder:32b, rodando tanto na minha máquina local limitada, quanto na RunPod, eu subi um &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/2"&gt;pull request&lt;/a&gt; em cima daquele projetinho educacional de &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI"&gt;Tiny-Qwen-CLI&lt;/a&gt; do meu outro artigo do blog.&lt;/p&gt;

&lt;p&gt;As mudanças que pedi foram simples, do tipo "refatorar dois métodos que estavam complexos e criar um arquivo de teste unitário desses dois métodos". Realmente nada demais. O arquivo principal não tem muito mais que 400 linhas. É realmente nível prova de conceito. E o Qwen sofreu.&lt;/p&gt;

&lt;p&gt;Primeiro de tudo, independente de ser na minha 4090 ou na A40, o Qwen é &lt;strong&gt;LENTO&lt;/strong&gt;, muito lento, faixa de menos de 8 tokens por segundo às vezes, é sofridamente lento.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb2dCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--635116f4de51814ad5fbb43875abade134a580b7/Screenshot%20From%202025-04-27%2019-54-35.png?disposition=attachment&amp;amp;locale=en" alt="H100" /&gt;&lt;/p&gt;

&lt;p&gt;Mas será que não é porque minha GPU ou a A40 são velhas e lentas? Pra tirar a dúvida subi o topo de linha do supra sumo: a H100 SXM de 80GB de VRAM e num container com 28 vCPUs e 250GB de RAM. E embora ele realmente puxasse a GPU, a velocidade em cima não escalou linearmente. É mais rápido, mas não o suficiente pra justificar a diferença de preços.&lt;/p&gt;

&lt;p&gt;O pod barato de A40 são só USD 0.44 por hora, já essa da H100 são quase USD 3 por hora. &lt;strong&gt;3 TRUMPS POR HORA&lt;/strong&gt; ou, se deixar ligado o dia todo, são &lt;strong&gt;USD 72 POR DIA&lt;/strong&gt;. Você precisa realmente saber o que está fazendo pra gastar esse nível numa brincadeira. Não cometa o erro de testar e esquecer o pod ligado lá!! Em um mês vai consumir mais de USD 2k!!!&lt;/p&gt;

&lt;p&gt;A menos que existam configurações específicas de Qwen2.5 que eu não sei - e acredite: eu vasculhei toda issue de github, sub-reddit e o que pude mas não achei nada - esse modelo é pesado e LENTO. E essa lentidão e peso não se traduzem em qualidade superior de código.&lt;/p&gt;

&lt;p&gt;Na prática, rodando local ou rodando em cloud, eu tive os mesmos resultados e os mesmos problemas. O código nunca funcionava de primeira, sempre quebrava. Quando mandava consertar, ele não consertava. Mexia em outros lugares e continuava quebrado. Mesmo dando o stacktrace do teste quebrando, ele não conseguia consertar. Teve uma vez que eu mandei ele refatorar e em vez de limpar o código, ele adicionou espaços em brancos, que o linter tinha dito pra tirar!&lt;/p&gt;

&lt;p&gt;Usar Qwen2.5-Coder:32b, e também a versão 32b-instruct, foram algumas das sessões mais frustrantes que eu já tive com LLMs. Achei que mais máquina fosse melhorar, mas não, nem com a H100 melhorou.&lt;/p&gt;

&lt;h3&gt;Benchmarks Mentem!&lt;/h3&gt;

&lt;p&gt;E aqui fica um primeiro aviso: em todo blog e video de YouTube que procurar, vai ver todo mundo falando que esse modelo é o supra sumo dos modelos de código. Vai ter vários gráficos de benchmarks dele igualando ou superando Claude, Gemini, ChatGPT. Tudo mentira.&lt;/p&gt;

&lt;p&gt;Qual minha teoria: é tudo baseado em &lt;strong&gt;testes sintéticos&lt;/strong&gt;, testezinhos muito simples no nível "faça um função que calcule fatorial", "faça uma função que calcule menor caminho entre dois pontos", "faça uma função que re-ordena uma lista de palavras", coisas nível Leet Code no máximo. E sim, isso TODOS os modelos sabem fazer. Está no material de treinamento, já tem cola pronta em dezenas de repos de GitHub. Você certamente já fez um pra treinar.&lt;/p&gt;

&lt;p&gt;Agora, dê um código de verdade. Aí a coisa é BEM diferente.&lt;/p&gt;

&lt;p&gt;De novo, é possível que tenha alguma configuração que eu não tenha descoberto e que de alguma forma, todo mundo sabe, mas não tem documentado em lugar nenhum. Vai saber. KV Cache? Mexi. Contexto? Mexi. Flash attention? Mexi. Mais VRAM? Já dei. Configuração de offload? Mexi. Temperatura no zero pra tentar evitar alucinações. Mais prompt? Mais contexto? O que mais?&lt;/p&gt;

&lt;p&gt;Não use benchmarks pra derivar conclusões. Eles não refletem o uso do dia a dia de ninguém. Teste um por um como eu fiz, em várias configurações. Só assim pra realmente saber como eles funcionam. E meu veredito por enquanto é que Qwen2.5 Coder de 32B, um dos &lt;strong&gt;MAIORES&lt;/strong&gt; não deu certo.&lt;/p&gt;

&lt;h3&gt;Tamanho Não é Documento&lt;/h3&gt;

&lt;p&gt;Um preconceito que eu tinha era realmente tamanho. Na minha cabeça "quanto mais parâmetros" deve ser melhor. Então eu fiquei obcecado em testar o mais pesado: &lt;strong&gt;32B&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Frustrado com os resultados, resolvi dar uma chance pra versão menor: &lt;a href="https://ollama.com/library/qwen2.5-coder:7b"&gt;qwen2.5-coder:7b-instruct&lt;/a&gt;. Mesma coisa, primeiro, configurar o &lt;code&gt;~/.aider.model.settings.yml&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;- name: ollama_chat/qwen2.5-coder:7b-instruct
  extra_params:
    num_ctx: 32768
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sendo um modelo mais leve, só 7B parâmetros, pesando menos de 5GB, sobra espaço e sabendo que ele suporta até 32k de tokens, posso pedir pra usar tudo que vai caber até na minha 4090.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --watch-files --model ollama_chat/qwen2.5-coder:7b-instruct
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Subi o Aider e pedi as mesmas coisas: refatorar dois métodos e fazer um teste unitário pros dois. E o resultado: foi MUITO mais rápido (é mais leve), e foi MUITO MAIS PRECISO. Isso foi uma surpresa!&lt;/p&gt;

&lt;p&gt;Pra verem a diferença de código, eis o &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/4"&gt;pull request&lt;/a&gt;. Como comentei nesse PR: o principal é que o código que ele fez não quebrou o projeto. Eu testei depois das mudanças e continua rodando como deveria. O teste que ele fez do zero, também falhou na primeira rodada, mas dando a mensagem de erro, ele soube corrigir. O mais importante: eu não precisei brigar com ele pra re-fazer tanto quanto precisei com o 32b. E por ser mais leve e mais veloz, a interatividade foi bem mais natural e bem menos cansativa, mais parecido com usar um Claude da vida.&lt;/p&gt;

&lt;p&gt;O código dele ainda não supera do Claude ou Gemini, mas do jeito que está já é bem usável. Mas se parar pra pensar faz sentido:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quanto maior o modelo (parâmetros) mais dados ele tem pra ERRAR.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E foi o que eu já tinha falado antes: modelos mais especialistas tendem a dar melhores resultados. Tamanho não é documento e não há correlação entre "ser maior" e "ser melhor". Sim, vai passar melhor e mais rápido em "benchmarks sintéticos", mas porque no modelo maior vai ter mais resultados pré-prontos que muitos desses testes usam. Mas vai ficar mais pesado e mais complicado pra gerar resultados inéditos.&lt;/p&gt;

&lt;p&gt;Um modelo menor, eu imagino, tem menos relacionamentos pra calcular e o que tem - se foi bem treinado - somado ao contexto (meu código atual), já é suficiente pra dar respostas boas. Mais parâmetros atrapalha mais do que ajuda.&lt;/p&gt;

&lt;p&gt;Além disso, isso é caso de uso de código. Queremos que ele tenha mais atenção e seja menos "criativo". O código precisa seguir à risca as regras daquele arquivo em especial. Não adianta querer ficar inventando coisas exóticas nele: só vai deixar o código pior.&lt;/p&gt;

&lt;p&gt;Ou seja: mesmo numa máquina menor, com uma 3070, 4070, com 16GB de VRAM, dá pra rodar o &lt;strong&gt;Qwen 2.5 Coder 7B Instruct&lt;/strong&gt; e ele vai dar resultados apropriados e úteis. Vale a pena testar. Se não tem máquina pra isso, o RunPod de uma 3090 deve ser suficiente e mais barato que uma A40. (na verdade acho que é só 1 centavo por hora mais barato).&lt;/p&gt;

&lt;h3&gt;Outros Modelos&lt;/h3&gt;

&lt;p&gt;Aproveitando, experimentei subir outros modelos pra testar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ ollama list
NAME                         ID              SIZE      MODIFIED
qwen2.5-coder:7b-instruct    2b0496514337    4.7 GB    2 hours ago
codegemma:7b                 0c96700aaada    5.0 GB    2 hours ago
codestral:latest             0898a8b286d5    12 GB     3 hours ago
codellama:34b                685be00e1532    19 GB     3 hours ago
deepseek-coder-v2:16b        63fb193b3a9b    8.9 GB    3 hours ago
deepseek-coder:33b           acec7c0b0fd9    18 GB     3 hours ago
qwen2.5-coder:14b            3028237cc8c5    9.0 GB    3 hours ago
qwen2.5-coder:32b            4bd6cbf2d094    19 GB     4 hours ago
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Infelizmente, Deepseek-Coder, Deepseek-Coder-V2, Code Gemma (do Google). Code Llama (da Meta), Codestral (da Mistral), todos fracassaram miseravelmente. Nenhum conseguiu dar nenhum código usável no mesmo teste que fiz os outros.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MAS&lt;/strong&gt; isso pode ser ainda uma limitação da ferramenta AIDER que estou usando. Ele é muito bem testado nas LLMs comerciais como da OpenAI, Claude ou Gemini, mas é muito pouco testado nas LLMs abertas.&lt;/p&gt;

&lt;p&gt;Eu já expliquei como ele funciona: com MUITO prompt de instruções (ligue a opção &lt;code&gt;--verbose&lt;/code&gt; pra ver). O problema é que LLMs diferentes precisam de prompts em formatos e verbalizados de forma diferente. Eu não li a fundo, mas Deepseek por exemplo, acho que eu li que prompts de comandos pra ele são em formato XML (lembro de ter visto vários tags). E se não der nesse formato, acho que não vai ajudar muito.&lt;/p&gt;

&lt;p&gt;Então não é que esses modelos são ruins, mas sim que o AIDER não é bom pra eles. É uma oportunidade pra quem quiser contribuir, pra criar perfis especiais de cada um deles e fazer pull request no Aider. É uma coisa que, se me der vontade, talvez eu tente fazer uma hora, porque depois do Qwen2.5 Coder, &lt;strong&gt;dizem&lt;/strong&gt; que o melhor é o DeepSeek Coder V2. Só que não deu pra ver, porque ele estava se recusando a devolver resultados num formato que o Aider espera.&lt;/p&gt;

&lt;p&gt;Procurei nas issues abertas no projeto e não achei nada pra gambiarrar temporariamente.&lt;/p&gt;

&lt;p&gt;Me deixa abismado que tem MUITO post de blog falando tando de Qwen quanto Deepseek mas minha conclusão é que NENHUM DELES TENTOU DE VERDADE RODAR! Estão só REPETINDO o que ouviram falar. Eu poderia afirmar que este blog post é o PRIMEIRO que realmente fez testes em códigos um pouco mais parecidos com de verdade, e não brincadeirinhas de leet code. Porque é só usar por 10 minutos: não funciona.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb2tCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0023102ec4cda35df1c1e70b154dfaef54f5bab7/Screenshot%20From%202025-04-27%2020-07-45.png?disposition=attachment&amp;amp;locale=en" alt="refactor fake" /&gt;&lt;/p&gt;

&lt;p&gt;Sem brincadeira, não lembro com qual modelo foi, mas eu pedi pra refatorar o método e olha essa foto de tela: ele só criou uma nova linha e tirou espaços em branco, mais nada! Era nesse nível. O Codellama se recusava a dar código, só dava explicações. O Codegemma parecido. O Aider ainda não deve saber como tirar proveito deles. Espero que alguém faça PRs pra consertar isso.&lt;/p&gt;

&lt;h3&gt;Conclusão&lt;/h3&gt;

&lt;p&gt;Nesses testes preliminares (super limitados), dos modelos abertos, realmente me impressionou o qwen2.5-coder:7b-instruct, vale a pena testar mais, em coisas mais complexas, pra ver se ele se mantém na mesma qualidade.&lt;/p&gt;

&lt;p&gt;Mas dos comerciais, também testei e tanto o &lt;strong&gt;Claude Sonnet 3.7&lt;/strong&gt; quanto o &lt;strong&gt;Gemini 2.5 Pro Exp Preview&lt;/strong&gt; foram imbatíveis. Minha linha de comando padrão agora deve ser esta:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --model openrouter/google/gemini-2.5-pro-preview-03-25
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Eles são consideravelmente mais rápidos, e com resultados consideravelmente mais precisos e com menos erros. Nenhum deles é perfeito, vira e mexe tem que mandar consertar alguma coisa, mas a frequência de erros é BEM menor do que o Qwen e o código final é objetivamente melhor.&lt;/p&gt;

&lt;p&gt;Pra comparar, este é o &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/pull/3"&gt;pull request&lt;/a&gt; do mesmo projeto, pedindo as mesmas coisas, pro Gemini. Ele não quebrou o projeto, conseguiu refatorar e conseguiu fazer o teste unitário. Eu não gastei muito tempo tentando fazer ficar do jeito "certo" que eu queria, mas do jeito que ficou já dá pra trabalhar em cima. Literalmente não gastei 10 minutos com o Gemini pra me dar esse resultado. Como Qwen 32b eu passei literalmente HORAS por um resultado medíocre.&lt;/p&gt;

&lt;p&gt;Usar uma LLM pra ajudar em código é realmente essencial. Apesar de ter que consertar várias coisas manualmente, onde eu tinha dúvidas ou queria idéias, qualquer das LLMs foi uma mão na roda. Especialmente quando estava de madrugada, meu foco estava disperso, sonolento, e a LLM me ajudou a não cometer erros triviais e pensou em coisas que na hora, não ia sair da minha cabeça.&lt;/p&gt;

&lt;p&gt;Em termos de custo: uma A40 custa 44 centavos por hora. Eu gastei menos de 5 dólares pro teste. Na OpenRouter gastei menos de 5 dólares de créditos também (é o melhor jeito de assinar um lugar só e usar Claude, Gemini, OpenAI, etc). Se for só pelo custo, não compensa escolher modelos open source pra função de assistente de programação. Assine o OpenRouter e teste o Gemini e outros, vai ser mais conveniente mesmo. Mas agora você sabe como subir do zero na RunPod e pensar em criar produtos e soluções que usam LLMs.&lt;/p&gt;

&lt;p&gt;Recomendo testar o Aider, ele é super simples, setup super baixo, não fica entuchando plugin pesado na sua IDE, e é rápido de usar. Mas leia a documentação que tem muita dica escondida.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6201</id>
    <published>2025-04-25T13:40:00-03:00</published>
    <updated>2025-04-28T11:53:08-03:00</updated>
    <link href="/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen" rel="alternate" type="text/html"/>
    <title>Seu Próprio Co-Pilot Gratuito Universal que funciona Local: AIDER-OLLAMA-QWEN</title>
    <content type="html">&lt;p&gt;No meu &lt;a href="https://www.akitaonrails.com/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local"&gt;post anterior&lt;/a&gt; eu mostro como fazer um chat LLM do zero com capacidade pra carregar arquivos de código pra refatoração. Eu demonstro os princípios por trás de ferramentas como Co-Pilot, Cursor ou Windsurf. O resumo é simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UM BOM PROMPT DE REGRAS&lt;/li&gt;
&lt;li&gt;SCRIPTS que rodam localmente e adicionam mais contexto na sessão do chat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;É basicamente "só" isso (claro, mesmo o princípio sendo simples, ainda dá bastante trabalho implementar mesmo). E eu não preciso fazer tudo do zero. Já existe uma alternativa open source que faz exatamente tudo isso, a ferramenta que ficou mais popular nos últimos meses pra desenvolvimento de software, o &lt;a href="https://aider.chat/docs/install.html"&gt;&lt;strong&gt;AIDER&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O que eu gosto no AIDER:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;funciona com praticamente qualquer LLM, fechada ou aberta, graças ao uso do &lt;a href="https://www.litellm.ai/"&gt;&lt;strong&gt;LiteLLM&lt;/strong&gt;&lt;/a&gt; por baixo, que abstrai e organiza toda configuração como tamanho de janela de contexto, temperatura e outros parâmetros sabidos que funciona melhor em cada LLM.&lt;/li&gt;
&lt;li&gt;é OPEN SOURCE, então além de gratuito você pode aprender mais lendo o código-fonte, como eu fiz.&lt;/li&gt;
&lt;li&gt;não precisa de plugins especiais - e proprietários - pra instalar em cada IDE: ele tem recurso de "watch files". Então posso deixar aberto num painel do meu terminal e meu editor favorito no outro painel: o NeoVIM. Dá pra integrar com plugins, mas é opcional. Vai funcionar pra tudo, até pro Notepad, se você for masoquista.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Eu tentei um pouco da porcaria de "vibe coding" somente via interface web do ChatGPT, Gemini, Claude, e vou dizer que todos são uma porcaria. Pra fazer um projetinho que é simples, com não mais que uns 4 arquivos curtos ele deu MUITO TRABALHO. Alguns deles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;não demora muito pra começar a misturar conteúdo de um arquivo em outro.&lt;/li&gt;
&lt;li&gt;erros que ele cometeu, eu expliquei que estava errado, ele corrigiu, mas logo em seguida repete de novo.&lt;/li&gt;
&lt;li&gt;ele não roda nada, então ele chuta muita coisa, como paths de arquivos, versões de bibliotecas&lt;/li&gt;
&lt;li&gt;não interessa que o contexto é grande e dá pra subir muitos arquivos pra contexto, é sliding window: ele não dá atenção pra tudo ao mesmo tempo. quanto mais arquivo de código você sobe, mais degrada a qualidade&lt;/li&gt;
&lt;li&gt;tem que escrever muito pra explicar o que se quer e, principalmente, porque ele errou e como corrigir.&lt;/li&gt;
&lt;li&gt;os créditos são caros, acabam muito rápido, porque se desperdiça metade dos tokens só explicando os erros e subindo os mesmos arquivos mais de uma vez porque você percebe que ele já "esqueceu". Eu cheguei no limite do Claude, do Gemini, super rápido (eu pago o primeiro plano pago, não vou pagar o mais caro). Daí tem que esperar algumas horas pra voltar (péssimo).&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Tudo isso alguém vai comentar &lt;em&gt;"Ah, mas pra mim funcionou."&lt;/em&gt; Foda-se, não perguntei. Eu disse que PRA MIM foi assim, só aceita. O importante é dizer que aquela história de "virtual employee" ou "substituir totalmente programador" ou "conseguir fazer um projeto inteiro sem saber programar nada" é &lt;strong&gt;BALELA&lt;/strong&gt;, &lt;strong&gt;HISTÓRIA DA CAROCHINHA&lt;/strong&gt;, &lt;strong&gt;CONTO DE VIGARISTA&lt;/strong&gt;, &lt;strong&gt;ILUSÃO DE AMADOR&lt;/strong&gt;, etc.&lt;/p&gt;

&lt;p&gt;Todas as LLMs são &lt;strong&gt;SÓ MAIS UMA FERRAMENTA&lt;/strong&gt; que alguém que realmente entende, como nós programadores, vamos saber usar muito melhor do que qualquer "empreendedor amador" jamais vai conseguir. E eu vou provar.&lt;/p&gt;

&lt;h3&gt;Aider Básico&lt;/h3&gt;

&lt;p&gt;Instalar o Aider é fácil, só precisa ter Python &gt;= 3.12 na sua máquina, coisa que todo Linux moderno tem. A documentação deles é muito boa, &lt;a href="https://aider.chat/docs/install.html"&gt;recomendo ler&lt;/a&gt; pra saber o que ele recomenda pós-instalação. Tem muitas dicas.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;python -m pip install aider-install
aider-install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;O que ele não diz na documentação é que a primeira coisa pra fazer é baixar &lt;a href="https://github.com/Aider-AI/aider/blob/main/aider/website/assets/sample.aider.conf.yml"&gt;este arquivo de exemplo&lt;/a&gt; e colocar em &lt;code&gt;$HOME/.aider.conf.yml&lt;/code&gt;. Nele tem uma coisa importante que temos que mexer:&lt;/p&gt;

&lt;pre&gt;&lt;code class="yaml"&gt;...
## Enable/disable auto commit of LLM changes (default: True)
auto-commits: false
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Recomendo que leiam esse arquivo, ele está todo comentado e pode ter opções que você queira desligar, mas esse é irritante, porque - por padrão - essa desgraça faz "COMMIT AUTOMÁTICO" no seu Git, toda vez que ele muda alguma coisa no seu código. Nem me dá chance de revisar. Vai na base do "CUNFIA IN NÓIS". Nem fu, desligado.&lt;/p&gt;

&lt;p&gt;Esse é o tipo de coisa que um amador não faria, e até entendo. Amadores não sabem organizar commits de git de qualquer jeito, provavelmente as mensagens automáticas do Aider vão ser menos piores. Olha como fica:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbmdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--84a9c7ae3d08b1824ad4c9811ea3a37b93b12026/Screenshot%20From%202025-04-25%2012-06-49.png?disposition=attachment&amp;amp;locale=en" alt="Aider Git commit" /&gt;&lt;/p&gt;

&lt;p&gt;Aviso pra júnior: esse é o tipo de coisa que se eu fosse avaliar, ia ganhar ponto negativo se eu visse sendo feito assim. Faça direito: re-cheque as mudanças e organize em commits de verdade. O Aider "diz" que fez refactor, mas nem sempre ele acerta, mas mesmo assim faz o commit, e aí sobe errado. Se for pra subir tudo automático, mesmo com erro, realmente, não preciso de você.&lt;/p&gt;

&lt;p&gt;Mas estou me adiantando, depois de instalar o Aider, precisa configurar as API KEYS de cada serviço que você usa, seja da OpenAI, Claude ou outros. Quem usa ChatGPT no Linux já tem uma variável como &lt;code&gt;export OPENAI_API_KEY=sk-proj-........&lt;/code&gt; configurado no &lt;code&gt;.bashrc&lt;/code&gt; ou &lt;code&gt;.zshrc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Feito isso, dá pra escolher qual modelo usar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Change directory into your codebase
cd /to/your/project

# o3-mini
aider --model o3-mini

# o1-mini
aider --model o1-mini

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

# List models available from OpenAI
aider --list-models openai/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E isso já vai abrir um chat interativo. Também dá pra, antes de abrir o chat, já mandar carregar arquivos locais assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider README.md init.py utils.py ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ou, de dentro do chat, ele aceita &lt;a href="https://aider.chat/docs/usage/commands.html"&gt;vários comandos&lt;/a&gt; que começam com slash "/", parecido com chat de IRC. Por exemplo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;/add helper.py
/copy # copia a última sugestão de código pro clipboard
/git # roda um comando de git local a partir do chat
/run # roda um comando do seu shell e adiciona o output no chat
/web # vai numa página web e puxa o conteúdo pro chat
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Leia a documentação. Mas o modo mais interessante é o "watch-files". Basta estar no diretório do seu projeto e subir assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --watch-files
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ele usa a biblioteca &lt;a href="https://pypi.org/project/watchfiles/"&gt;watchfiles&lt;/a&gt; de Python pra interceptar chamadas de IO nesse diretório e dar gatilho pra carregar sozinho seus arquivos, assim que você salva no seu editor favorito (por isso falei que não precisa de plugin extra nenhum se não quiser). Já aviso que essa lib não suporta dar watch em mounts de SMB ou NFS (sim, eu testei).&lt;/p&gt;

&lt;p&gt;No seu editor, basta criar um comentário perto do código que quer que ele mexa, assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;// can you refactor this return so it has more error checking? AI!
export const getCapitalizedLabel = (name: string): string =&amp;gt; {
  return name
    .replace(/_/g, " ")
    .split(" ")
    .map((word: string) =&amp;gt; word.charAt(0).toUpperCase() + word.slice(1))
    .join(" ")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Coloque no comentário o que quer que ele faça, e termine com o gatilho "AI!". Aí no outro terminal que o Aider está carregado, ele vai perceber que o arquivo foi salvo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;gt;

Added src/utils/getCapitalizedLabel.ts to the chat
Processing your request...
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E vai fazer sua "mágica" (que eu vou contar o segredo já já). Este é o exemplo do que ele fez (mesmos resultados tanto com o4-mini quanto Qwen2.5, mas é um exemplo bem trivial):&lt;/p&gt;

&lt;pre&gt;&lt;code class="js"&gt;export const getCapitalizedLabel = (name: string): string =&amp;gt; {
  if (typeof name !== 'string') {
    throw new TypeError('Expected a string');
  }

  return name
    .replace(/_/g, " ")
    .split(" ")
    .map((word: string) =&amp;gt; {
      if (word.length === 0) return word;
      return word.charAt(0).toUpperCase() + word.slice(1);
    })
    .join(" ");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Um editor gráfico como Visual Studio Code da vida, assim que o Aider mudar o arquivo, no editor ele recarrega e você já vê as mudanças. Num NeoVim, o autoread costuma carregar automático também, mas às vezes eu preciso dar o comando ":e" pra forçar recarregar, mas nada de mais também. E só com isso já temos um workflow de trabalho eficiente.&lt;/p&gt;

&lt;p&gt;Meu editor favorito funciona igualzinho, sem plugin deixando pesado nem conflitando com nada. Se eu precisar fazer pair programming, chamo o Aider num outro terminal e pronto, basta escrever idéias em comentários, e ele manda sugestões. Como desliguei o maldito auto-commit, se eu não gostar, basta dar UNDO ou &lt;code&gt;git checkout&lt;/code&gt; no arquivo e volta como tava antes.&lt;/p&gt;

&lt;p&gt;Mais do que isso. Se eu notar que o modelo &lt;code&gt;o4-mini&lt;/code&gt; não tá dando bons resultados, posso mudar pro &lt;code&gt;--model gemini&lt;/code&gt; - que costuma ser melhor pra código do que o o4. Ou &lt;code&gt;--model claude-3-opus-20240229&lt;/code&gt;. Aliás, falando em Claude, como falei antes &lt;a href="https://aider.chat/docs/llms/anthropic.html"&gt;leia a fucking documentação&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fala na documentação que o Aider suporta "thinking tokens" do Sonnet 3.7. Existe um arquivo de configuração onde podemos fazer "fine-tuning" pra cada modelo, o &lt;code&gt;$HOME/.aider.model.settings.yml&lt;/code&gt;onde podemos adicionar:&lt;/p&gt;

&lt;pre&gt;&lt;code class="yaml"&gt;- name: anthropic/claude-3-7-sonnet-20250219
  edit_format: diff
  weak_model_name: anthropic/claude-3-5-haiku-20241022
  use_repo_map: true
  examples_as_sys_msg: true
  use_temperature: false
  extra_params:
    extra_headers:
      anthropic-beta: prompt-caching-2024-07-31,pdfs-2024-09-25,output-128k-2025-02-19
    max_tokens: 64000
    thinking:
      type: enabled
      budget_tokens: 32000 # Adjust this number
  cache_control: true
  editor_model_name: anthropic/claude-3-7-sonnet-20250219
  editor_edit_format: editor-diff
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Reasoning&lt;/h3&gt;

&lt;p&gt;Thinking ou Reasoning como é chamado no marketing da maioria das LLMs comerciais é o pattern de "chain-of-thought", que é uma técnica de prompt onde você pede pra LLM não dar uma resposta, mas sim descrever "passo a passo" como ele chegaria na resposta, e depois repetir a pergunta mandando ele seguir esses passos e daí dando uma resposta.&lt;/p&gt;

&lt;p&gt;Na prática tem várias formas de implementar algo parecido com isso. E uma delas é ser Multi Modelo. Especialmente com modelos open source, como DeepSeek-Coder ou Qwen2.5-Coder ou Llama3. Alguns modelos são melhores no reasoning - em explicar verbalmente o que tem que ser feito, mas são ruins em fazer o código em si. E alguns modelos não são bons em explicar as coisas em detalhes, mas foram treinados pra escrever bom código.&lt;/p&gt;

&lt;p&gt;É possível usar isso a nosso favor e o Aider tem um recurso que eu acho muito foda - pra casos onde realmente precisa, porque é pesado - que é rodar em &lt;strong&gt;modo arquiteto&lt;/strong&gt;, gerar o raciocíniio e depois mudar pra &lt;strong&gt;modo código&lt;/strong&gt; em outro modelo, e usar esse raciocínio pra montar um código melhor.&lt;/p&gt;

&lt;p&gt;Tem um &lt;a href="https://aider.chat/2024/09/26/architect.html"&gt;artigo inteiro&lt;/a&gt; na documentação do Aider explicando isso, mas a motivação foi por causa do modelo OpenAI o1, que é justamente forte pra raciocinar uma explicação do que fazer, mas é ruim em realmente fazer o código. Então, vale a pena ouvir o o1 e deixar ele analisar o problema e descrever em forma de texto, e depois mudar pro Gemini Pro ou Exp e mandar ele escrever o código.&lt;/p&gt;

&lt;p&gt;Na prática, basta subir o Aider primeiro em modo "Architect":&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;pip install -U aider-chat

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

# Work with Claude 3.5 Sonnet as the Architect and Editor
export ANTHROPIC_API_KEY=your-key-goes-here
aider --sonnet --architect
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E isso funciona com outros models:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Work with OpenAI models, using gpt-4o as the Editor
export OPENAI_API_KEY=your-key-goes-here
aider --4o --architect
aider --o1-mini --architect
aider --o1-preview --architect
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Como exemplo, configurando &lt;code&gt;OPENAI_API_KEY&lt;/code&gt; e &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; posso subir o o4 como arquiteto e o Gemini como o coder:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ aider --watch-files --architect --editor-model gemini
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Using gpt-4o model with API key from environment.
Aider v0.82.3.dev55+g25a30393
Main model: gpt-4o with architect edit format
Editor model: gemini/gemini-2.5-pro-preview-03-25 with editor-diff edit format
Weak model: gpt-4o-mini
Git repo: .git with 184 files
Repo-map: using 4096 tokens, auto refresh
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
architect&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Veja no status do início que ele aceitou os dois. Agora é só codar do mesmo jeito que antes, mas usando os dois. Obviamente, assim você vai usar muito mais créditos! Fique esperto com isso! Tem &lt;strong&gt;chances&lt;/strong&gt; de ter resultados melhores mas &lt;strong&gt;certamente&lt;/strong&gt; vai gastar mais créditos, por isso use mais pra códigos mais complexos, onde só um modelo está sofrendo pra conseguir resolver.&lt;/p&gt;

&lt;p&gt;Note que existem configurações e limites de tokens, e também note que ao final de cada resposta, o Aider dá uma estimativa de quanto está te custando. Eis primeiro a conta do arquiteto OpenAI o4:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
This should make the function more concise and efficient.


Tokens: 4.4k sent, 246 received. Cost: $0.01 message, $0.01 session.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E eis a SEGUNDA conta do Coder Gemini 2.0 Flash:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
Tokens: 1.9k sent, 136 received. Cost: $0.0037 message, $0.02 session.
Applied edit to src/utils/getCapitalizedLabel.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Gemini Free (gratuito) tem um "rate limit" (quantidade de requisições permitidas por minuto, super baixa, só 15 RPM, pra API é ridículo, o Aider bate nisso na primeira tentativa).&lt;/p&gt;

&lt;p&gt;Não raras as vezes, você vai bater no limite do plano, e isso é um saco:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;The API provider has rate limited you. Try again later or check your quotas.
Retrying in 4.0 seconds...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Daí não tem jeito, tem que ir no site &lt;a href=""&gt;Google AI Studio&lt;/a&gt; configurar sua conta de cobrança, adicionar seu cartão e subir pra um plano pago um pouco melhor como o 2.0 Flash, que suporta 2.000 RPM e 4M TPM e o preço não é caro:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbmtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b384c8b3fe86bdeda21a33094d5a9ff138abb090/Screenshot%20From%202025-04-25%2012-52-41.png?disposition=attachment&amp;amp;locale=en" alt="Gemini 2.0 Flash" /&gt;&lt;/p&gt;

&lt;p&gt;Alguns dizem que hoje o Gemini 2.0 tem oferecido resultados de código similares ou superiores ao Claude Sonnet 3.7. Varia de caso a caso, não é nunca uma medida absoluta, mas na minha pequena experiência, ambos são muito bons e eu vejo ambos sendo superiores mesmo ao ChatGPT 4 ou o4. Mas a OpenAI, pra mim, tem mesmo sido melhor em respostas "verbais", então a estratégia de separar o papel de "arquiteto" pra OpenAi e de "coder" pra Gemini ou Claude, faz MUITO sentido.&lt;/p&gt;

&lt;p&gt;No final, aquele código inicial que pedi pra refatorar, nessa combinação de arquiteto/editor ficou assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="js"&gt;export const getCapitalizedLabel = (name: string): string =&amp;gt; {
  return name
    .replace(/_/g, ' ')
    .replace(/\b\w/g, char =&amp;gt; char.toUpperCase());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ok, eu não teria pensado nisso desta forma.&lt;/p&gt;

&lt;h3&gt;Não usando "Créditos de Tokens": OLLAMA&lt;/h3&gt;

&lt;p&gt;Agora vem a parte mais interessante pra nós, nerds mais hard-core, com máquinas mais parrudas (no mínimo uma RTX 3090, eu uso uma RTX 4090 - o importante é ter &lt;strong&gt;24GB de VRAM&lt;/strong&gt;). Dá pra usar modelos menores, que cabem em menos VRAM, mas aí o resultado do código vai ser bem pior, então é melhor ficar no Gemini ou Claude mesmo.&lt;/p&gt;

&lt;p&gt;Se tiver a máquina pra isso, com CUDA Toolkit já instalado, é hora da solução de gente grande: instalar Ollama. No meu Manjaro/Arch é assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;yay -S ollama ollama_cuda
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Não deixe de checar configuração de CUDA porque por default o Ollama vai rodar silenciosamente na sua CPU e vai ser tudo ABSURDAMENTE LENTO.&lt;/p&gt;

&lt;p&gt;O Ollama funciona tanto como um servidor local http pra APIs, quanto como um cliente de chat interativo, como o meu qwen_cli do post anterior. Ele funciona numa lógica mais ou menos parecida com Docker. Pode ser configurado pra subir como automaticamente como serviço no seu sistema. Veja a &lt;a href="https://wiki.archlinux.org/title/Ollama"&gt;documentação no ArchWiki&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Eu prefiro subir manualmente num terminal, onde eu posso ver o log do que está acontecendo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;OLLAMA_FLASH_ATTENTION=1 OLLAMA_CONTEXT_LENGTH=8192 ollama serve
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tem várias configurações com variáveis de ambiente. Dá pra só configurar no seu &lt;code&gt;/etc/profile&lt;/code&gt; ou &lt;code&gt;~/.zshrc&lt;/code&gt; mas como estou testando, prefiro subir direto na linha de comando. Com isso teremos um servidor web. Pra funcionar com o Aider, novamente, só declarar onde achar ele no seu &lt;code&gt;/.zshrc&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;export OLLAMA_API_BASE=http://127.0.0.1:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Agora, precisamos baixar algum modelo, como meu preferido Qwen 2.5 Coder, que já usei no post anterior:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ollama pull qwen2.5-coder:32b
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note que estou explicitamente pedindo a versão 32B, mas você pode experimentar uma mais leve como a 7b. Cada modelo suportado pelo Ollama tem &lt;a href="https://ollama.com/library/qwen2.5-coder"&gt;uma página&lt;/a&gt; onde descreve detalhes desse tipo:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbm9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--e181c568f47c044f552891f4f7b67ffaf673ad9b/Screenshot%20From%202025-04-25%2013-03-43.png?disposition=attachment&amp;amp;locale=en" alt="qwen 2.5 coder page" /&gt;&lt;/p&gt;

&lt;p&gt;Dependendo de qual benchmark sintético você "acreditar" vai ver comparações como esta:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://ollama.com/assets/library/qwen2.5-coder/bbf378d8-c80e-4ae3-98ab-90111dfbf3e7" alt="comparação" /&gt;&lt;/p&gt;

&lt;p&gt;Na prática, Claude, ChatGPT, Gemini, realmente são já muito bons pra código. No mundo open-source são o Qwen 2.5 Coder, Deepseek-Coder-V2 (que ainda não funciona muito bem no Aider, tem que esperar atualizações). Então, se quiser tudo de grátis, o melhor mesmo por enquanto é o Qwen 2.5 Coder mesmo. E na minha experiência, tem funcionado muito bem. Mas é BEM pesado na versão 32B e as respostas são consideravelmente mais lentas que uma opção paga como Gemini. Então depende muito do seu caso.&lt;/p&gt;

&lt;p&gt;Uma vez baixado o modelo, precisa configurar janela de contexto (por padrão é bem pequena, só 2K tokens), &lt;a href="https://aider.chat/docs/llms/ollama.html"&gt;a documentação avisa&lt;/a&gt; isso e tem que editar o arquivo &lt;code&gt;$HOME/.aider.model.settings.yml&lt;/code&gt; assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="yaml"&gt;- name: ollama/qwen2.5-coder:32b
  extra_params:
    num_ctx: 65536
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;O que a documentação &lt;strong&gt;NÃO&lt;/strong&gt; explica e, por causa disso, eu gastei um tempão vasculhando o código-fonte a as Issues no GitHub é que pra subir modelos open source, precisa usar a opção &lt;code&gt;ollama_chat/&lt;/code&gt; e não ollama como ele manda. Ou seja, a documentação diz:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --model ollama/qwen2.5-coder:32b
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mas o CERTO é fazer:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --model ollama_chat/qwen2.5-coder:32b
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Se não, as respostas vão ser completamente aleatórias, fora de contexto. Preste atenção nisso. Mas sabendo disso, podemos até fazer o que falei antes: misturar modelos! Que tal subir o GPT o4 como arquiteto e deixar ele comandar o editor sendo o Qwen 2.5?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --watch-files --architect --editor-model ollama_chat/qwen2.5-coder:32
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sim, isso é possível. Agora você pode usar um modelo comercial que está mais acostumado, como Gemini ou Claude, tanto nos papéis de arquiteto como editor, e um modelo open source como Qwen 2.5 pra suporte, e pagar menos. Ou de uma vez só usar o Qwen 2.5 e não pagar &lt;strong&gt;crédito nenhum&lt;/strong&gt; (só eletricidade, claro, vai consumir &lt;strong&gt;+200W&lt;/strong&gt; da parede toda vez, mas são só alguns segundos por vez).&lt;/p&gt;

&lt;p&gt;Na minha (pouca) experiência. O Gemini ainda dá códigos melhores que o Qwen, mas não quer dizer que o Qwen seja ruim, como eu disse, vai depender MUITO das suas instruções de prompt (quanto mais detalhado melhor). E falando em instruções, vamos pra parte final:&lt;/p&gt;

&lt;h3&gt;O Segredo da Mágica: Prompts&lt;/h3&gt;

&lt;p&gt;Eu tiro sarro quando alguém fala em "prompt engineering", mas na real, a melhor forma de tirar o máximo de uma LLM é fazer o melhor prompt possível. Não quer dizer o "prompt mais longo". É qualidade e não quantidade.&lt;/p&gt;

&lt;p&gt;Por isso tanto &lt;a href="https://github.com/microsoft/generative-ai-for-beginners"&gt;Microsoft&lt;/a&gt; quanto &lt;a href="https://www.kaggle.com/whitepaper-prompt-engineering"&gt;Google&lt;/a&gt; fizeram guias detalhados focaods em prompt. O deles compensa ler e estudar. O que não compensa é pagar cursos de gente aleatória falando "prompt engineering" como se entendesse alguma coisa.&lt;/p&gt;

&lt;p&gt;O Aider suporta uma opção "--verbose", onde ele mostra exatamente o que está mandando pras LLMs. Vamos testar. Logo de cara ele já imprime isso no console:&lt;/p&gt;

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

Option settings:
  - 35turbo: False
  - 4: False
  - 4_turbo: False
  ...
  - upgrade: False
  - user_input_color: #00cc00
  - verbose: True
  - verify_ssl: True
  - vim: False
  - voice_format: wav
  - voice_input_device: None
  - voice_language: en
  - watch_files: True
  - weak_model: None
  - yes_always: None
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;O que ferramentas como Aider, Cursor, Co-Pilot e outros fazem é ter parâmetros otimizados de todos os LLMs já hard-coded na ferramenta, como nesse caso onde detecta pra usar o4 e já configura com melhores parâmetros (que você ainda pode tunar colocando em &lt;code&gt;$HOME/.aider.model.settings.yml&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Mas quando você manda a primeira pergunta que fica interessante, olha isso:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbnNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d6f1adccfa1286748e4d40d853a1aa7266645281/Screenshot%20From%202025-04-25%2013-25-59.png?disposition=attachment&amp;amp;locale=en" alt="Aiden Verbose 1" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Aja como um desenvolvedor de software. Sempre use boas práticas quando codar. Respeite e use convenções, bibliotecas, etc que já estão presentes nesta base de código. Seja diligente e sem preguiça! Você NUNCA vai deixar comentários descrevendo código sem implementar! Você sempre vai IMPLEMENTAR COMPLETAMENTE o código necessário! ..."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Lembra o que eu falei no começo do post: PROMPTS DE REGRAS. Esse SYSTEM no começo é um "role". Você como usuário é o "USER" e o modelo é o "ASSISTANT", inclusive esse prompt de SYSTEM é gigante, ele vasculha meu projeto e já explica pro modelo a estrutura do meu projeto e inclusive resume alguns arquivos (mostrando só a interface dos métodos, por exemplo, pra dar contexto mas não subir código inteiro).&lt;/p&gt;

&lt;p&gt;Tem trechos assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
SYSTEM # *SEARCH/REPLACE block* Rules:
SYSTEM
SYSTEM Every *SEARCH/REPLACE block* must use this format:
SYSTEM 1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
SYSTEM 2. The opening fence and code language, eg: ```python
SYSTEM 3. The start of search block: &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH
SYSTEM 4. A contiguous chunk of lines to search for in the existing source code
SYSTEM 5. The dividing line: =======
SYSTEM 6. The lines to replace into the source code
SYSTEM 7. The end of the replace block: &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE
SYSTEM 8. The closing fence: ```
SYSTEM
SYSTEM Use the *FULL* file path, as shown to you by the user.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Inclusive, se já usou Co-Pilot, sabe que dá pra colocar as convenções do seu projeto em &lt;code&gt;CONVENTIONS.MD&lt;/code&gt;. O Aider também vai carregar esse arquivo no prompt. Então coisas como "use 4 espaço em vez de tabs, coloque a abertura de {  no final da linha e não começando uma linha nova, etc", tudo isso fica nesse arquivo e tanto Co-Pilot como Aider adicionam essas regras no PROMPT. Não é nenhum outro mecanismo, é PROMPT.&lt;/p&gt;

&lt;p&gt;Daí, como USER, ele começa a subir resumo dos meus arquivos no prompt:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
USER Here are summaries of some files present in my git repository.
USER Do not propose changes to these files, treat them as *read-only*.
USER If you need to edit any of these files, ask me to *add them to the chat* first.
USER
USER data_scraper/main.py:
USER ⋮
USER │def main(args: Arguments):
USER ⋮
USER
USER data_scraper/src/arguments.py:
USER ⋮
USER │@dataclass(frozen=True)
USER │class Arguments:
USER │    content_path: Path
USER ⋮
USER │    @staticmethod
USER │    def get() -&amp;gt; 'Arguments':
USER ⋮
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Quando termina essa parte, o Aider pede confirmação do Modelo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
ASSISTANT Ok, I won't try and edit those files without asking first.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E é assim que funciona um "I.A.  pra software", com uma tonelada de prompts de instruções e um programa cliente que fica fazendo parse das respostas e ativa comandos de verdade que rodam no seu sistema (agentes), como comandos de git ou lint. O resultado desses comandos, o texto, é concatenado no chat pra adicionar ao contexto e daí pede pro modelo/assistente continuar analisando desse ponto em diante.&lt;/p&gt;

&lt;p&gt;Um modelo sozinho não faz nada. Quem faz é a ferramenta que carrega o modelo, no caso o Aider, ou o Co-Pilot, ou o Cursor. O programa vem pré-carregado com vários perfis/personas/roles pré-escritos pra adicionar prompts ao modelo. No código-fonte do Aider, temos este exemplo de &lt;a href="https://github.com/Aider-AI/aider/blob/main/aider/coders/architect_prompts.py"&gt;architect_prompts.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbndCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b9d3e817e4d361e30058a98b011517ff355b78ba/Screenshot%20From%202025-04-25%2013-36-48.png?disposition=attachment&amp;amp;locale=en" alt="architect role" /&gt;&lt;/p&gt;

&lt;p&gt;Olha só, na primeira variável &lt;code&gt;main_system&lt;/code&gt; está aquele primeiro prompt que vimos no começo desta seção. Tem vários outros perfis pré-programados que é bom entender a diferença. Cada modelo funciona melhor com determinados tipos de instrução, com diferentes limitações.&lt;/p&gt;

&lt;p&gt;Neste outro código &lt;a href="https://github.com/Aider-AI/aider/blob/main/aider/commands.py"&gt;commands.py&lt;/a&gt; é onde temos declarado como o Aider consegue fazer coisas como &lt;code&gt;git commit&lt;/code&gt; direto no seu projeto ou rodar o linter da sua linguagem:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbjBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b8c3ad26b8f63f8f79f9bd6506b030991a013fea/Screenshot%20From%202025-04-25%2013-39-43.png?disposition=attachment&amp;amp;locale=en" alt="commands" /&gt;&lt;/p&gt;

&lt;p&gt;Nada disso é "mágica", é tudo pré-programado e ele - por segurança - só deve conseguir fazer comandos limitados e bem checados. Não queremos comandos que façam coisa demais fora do diretório do projeto (aliás, NUNCA, fora do diretório do projeto). Nem deveria poder rodar muitos executáveis. Nunca se sabe que bugs de segurança podem acontecer quando se roda comandos cegamente.&lt;/p&gt;

&lt;p&gt;Se você é de cyber-segurança, é aqui que deveria auditar. Mas essa é a vantagem, o Aider sendo open source, é auditável. Ferramentas proprietárias e fechadas, não. É na base do "CUNFIA NO PAI".&lt;/p&gt;

&lt;p&gt;Teste o &lt;a href="https://aider.chat/docs/install.html"&gt;Aider&lt;/a&gt; hoje e leia todo esse site de documentação. É curto e tem dicas importantes que podem ajudar casos especiais.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbjRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6db69bd4ff2638b77f27cab91c25c0e28a605c4e/Screenshot%20From%202025-04-25%2013-42-14.png?disposition=attachment&amp;amp;locale=en" alt="Aider" /&gt;&lt;/p&gt;

&lt;h3&gt;OpenRouter&lt;/h3&gt;

&lt;p&gt;Como já devem ter percebido, o ideal é testar as várias LLMs comerciais porque elas evoluem e cada uma tem pontos fortes e fracos. Mas ficar pagando crédito em cada uma é um saco.&lt;/p&gt;

&lt;p&gt;Pra isso existe o &lt;a href="https://openrouter.ai/"&gt;OpenRouter&lt;/a&gt; onde você tem uma conta só, uma &lt;code&gt;OPEN_ROUTER_API_KEY&lt;/code&gt; só e paga num lugar só ele distribui os créditos pra cada provider como OpenAI ou Claude à medida que você usa. Simplifica o gerenciamento dos seus custos de API, vale muito a pena e, claro, o Aider também sabe usar ele:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;aider --model openrouter/openai/gpt-4o
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Novamente, leiam &lt;a href="https://aider.chat/docs/llms/openrouter.html"&gt;a documentação&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Agora, um &lt;strong&gt;ANTI-PATTERN&lt;/strong&gt;. Eu pedi ao Aider, usando o modelo &lt;code&gt;openrouter/google/gemini-2.5-pro-preview-03-25&lt;/code&gt;, que teoricamente é a melhor das melhores do momento, pra gerar um arquivo de testes unitários pro qwen_cli.py, do projetinho que eu soltei no GitHub no post de ontem.&lt;/p&gt;

&lt;p&gt;Ele falhou miseravelmente. Criou um arquivo de testes, de quase 800 linhas, socado de mocks pra tudo que é canto, testando coisa que não precisa, complicando demais. Tudo bem. Eu tento rodar e solta erros.&lt;/p&gt;

&lt;p&gt;Uma funcionalidade legal do Aider é poder rodar de dentro do chat com &lt;code&gt;/run python -m unittest ...&lt;/code&gt; e ele oferece pra já jogar os erros dentro do chat. Eu peço pro Gemini consertar. Aí ele sai modificando o arquivo de testes e o arquivo do meu código. Mando &lt;code&gt;/run&lt;/code&gt; de novo, mais erros. E eu fiz isso umas 4x. Ele sai "consertando" código que não precisa, e testes unitários simples (que era só um pequeno typo, só ajustar 1 número), ele larga pra trás. Eu descrevo no prompt &lt;em&gt;"você está mexendo onde não precisa e não consertando coisa trivial, como esse númerozinho"&lt;/em&gt; mando &lt;code&gt;/run&lt;/code&gt;, e ele insiste em ignorar e sai mexendo em outros lugares.&lt;/p&gt;

&lt;p&gt;Resumo:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBb0FCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--34c9ce00dcb0e606a427ad82a569099c6bd5ad6a/Screenshot%20From%202025-04-25%2016-53-20.png?disposition=attachment&amp;amp;locale=en" alt="OpenRouter Credits" /&gt;&lt;/p&gt;

&lt;p&gt;Gastou quase 3 doletas e não resolveu o problema. Vantagem da OpenRouter é que fica tudo centralizado num lugar só. Ele pagou o Google pelo uso do Gemini e desconta dos meus créditos. Tinha pré-carregado com 100 créditos (mais ou menos 100 dólares). E em 30 minutos já foi embora 3 créditos. Faça as contas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Melhor prática:&lt;/strong&gt; o melhor é abrir o código do qwen_cli.py, por exemplo, no meu NeoVIM ou qualquer editor e colocar um comentário só em cima de um único método:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;...
# this method is used to just load a config.json file, create a single unit test and add to test_qwen_cli.py, AI!
def load_config
  ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Daí ele faz somente &lt;strong&gt;UM&lt;/strong&gt; teste unitário por vez, rodamos com &lt;code&gt;/run&lt;/code&gt; e se passar e o código parecer limpo, seguimos pro próximo. Fazer um método de cada vez é muito mais garantido do que mandar &lt;em&gt;"faz teste pra TUDO de uma só vez."&lt;/em&gt; Aí é garantia que vamos gastar créditos à toa.&lt;/p&gt;

&lt;p&gt;Além disso, um método por vez, modelos open source como Qwen 2.5 vão fazer bem e você roda local sem gastar créditos.&lt;/p&gt;

&lt;h3&gt;Conclusão&lt;/h3&gt;

&lt;p&gt;Por enquanto, vou adotar o Aider. Agora eu entendo porque ele ficou tão popular este ano. É um projetinho bem feito, aberto, que eu posso explorar, tentar melhorar e aprender mais. Ao mesmo tempo facilita o uso de LLMs comercias e abre opções pra eu usar modelos open source na minha própria máquina, dando melhor uso ao meu RTX 4090.&lt;/p&gt;

&lt;p&gt;Programador ruim é quando alguém fala que teve trabalho no ChatGPT e ele fica dando resposta enlatada como "já testou Claude, já testou Manus" ou então "ah, se tivesse usador Cursor seria melhor". Pára de ser esse NPC, é cansativo e demonstra absoluta PREGUIÇA.&lt;/p&gt;

&lt;p&gt;Programador BOM é quando passa por uma jornada como a que acabei de descrever neste post: aprende os pontos fortes, os pontos fracos, como CONTROLAR, como SUBVERTER, como criar CONTINGÊNCIAS e, no final, sai com uma solução MELHOR do que de todo mundo.&lt;/p&gt;

&lt;p&gt;É pra isso que virei programador, não pra ser outdoor grátis repetindo propaganda do Sam Altman.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6200</id>
    <published>2025-04-25T00:20:00-03:00</published>
    <updated>2025-04-25T00:53:40-03:00</updated>
    <link href="/2025/04/25/hello-world-de-llm-criando-seu-proprio-chat-de-i-a-que-roda-local" rel="alternate" type="text/html"/>
    <title>Hello World de LLM: criando seu próprio chat de I.A. que roda local</title>
    <content type="html">&lt;p&gt;Ando brincando BASTANTE com os diversos derivados de GPT como o próprio ChatGPT, Claude Sonnet, Gemini e depois talvez eu faça um outro post sobre minha experiência, mas hoje eu queria brincar um pouco mais a fundo e fazer um experimento nível "Hello World" pra que vocês, programadores, entendam como que funciona por baixo dos panos.&lt;/p&gt;

&lt;p&gt;Eu subi o projeto no GitHub, se chama &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI"&gt;Tiny Qwen CLI&lt;/a&gt; e foi feito somente pra PROPÓSITOS EDUCACIONAIS. Pra uma ferramenta de verdade, pesquise o &lt;a href="https://aider.chat/"&gt;Aider&lt;/a&gt;. Talvez eu fale dele em outro post depois.&lt;/p&gt;

&lt;p&gt;O conceito que quero ensinar: GPTs não são mágica.&lt;/p&gt;
&lt;h3&gt;Conceitos&lt;/h3&gt;

&lt;p&gt;Assistam a minha &lt;a href="https://www.youtube.com/watch?v=UDrDg6uUOVs&amp;amp;list=PLdsnXVqbHDUeowsAO0sChHDY4D65T5s1U&amp;amp;pp=gAQB"&gt;playlist de I.A.&lt;/a&gt; pra entender como um GPT funciona. Depois de assistido vamos entender.&lt;/p&gt;

&lt;p&gt;Embaixo de todo Gemini, ChatGPT, Claude ou qualquer outro aberto como LLAMA da Meta, existe um arquivo binário gigante que é o resultado das horas e horas de treinamento com petabytes de dados.&lt;/p&gt;

&lt;p&gt;O treinamento consiste - BEM A GROSSO MODO - em tokenizar as palavras dos textos (não é bem isso, mas pense quebrar em sílabas, o certo são n-grams). Depois transformar em vetores (como você aprendeu na facultade, uma lista de coordenadas, que costumam ser &lt;strong&gt;FP&lt;/strong&gt; ou ponto flutuante, FP-32, 32-bits pra ser mais preciso). O treinamento consiste em ir mudando esses vetores de lugar dentro de um espaço hiper-dimensional (muito mais que 3 dimensões como estamos acostumados, pense milhares ou milhões).&lt;/p&gt;

&lt;p&gt;Esse posicionamento de vetores vai mudando à medida que vamos dando mais textos. Ele vai "encontrando relacionamentos" entre diferentes tokens. Chegamos num ponto onde cada possível palavra foi codificada como vetores e esse posicionamento lhes dá "significados". É onde vem o famoso exemplo que se pegarmos a palavra "REI" e substrairmos de "HOMEM" depois somarmos com "MULHER" vamos encontrar o vetor de "RAINHA".&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;REI - HOMEM + MULHER = RAINHA
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isso não é acidente, é porque o treinamento conseguiu "posicionar" esses vetores de tal forma que conseguimos fazer "contas" com esses vetores e encontramos significado.&lt;/p&gt;

&lt;p&gt;Estou sendo ABSURDAMENTE GROSSEIRO, considere que isso tudo são dezenas de papers e eu estou resumindo em meia dúzia de parágrafos. O importante é entender que isso custa MUITO TEMPO, porque cada novo token que entra no treinamento precisa varrer todos os outros tokens que já foram calculados e recalcular uma porção enorme deles, o tempo todo!&lt;/p&gt;

&lt;p&gt;Horas e horas, dias e dias depois, processando terabytes e terabytes de textos, uma hora chegamos ao final com um enorme arquivo de vetores, um &lt;strong&gt;VSM&lt;/strong&gt; ou Vector Space Model.&lt;/p&gt;

&lt;p&gt;Se entrarmos num site que cataloga e armazena modelos pré-treinados, na &lt;a href="https://huggingface.co/meta-llama/Meta-Llama-3-70B/tree/main"&gt;página da Meta pro LLAMA 3 70B&lt;/a&gt;, vemos isto:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbk1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--1d39e0d9e2784b493389d3018f12a9cbf04ceb62/Screenshot%20From%202025-04-24%2022-56-15.png?disposition=attachment&amp;amp;locale=en" alt="Huggingface Meta" /&gt;&lt;/p&gt;

&lt;p&gt;Temos o arquivão particionado em 30 arquivos menos de uns 4.5 GB cada um, totalizando uns &lt;strong&gt;135 GB&lt;/strong&gt;, pra um arquivo de 70B ou seja, 70 bilhões de parâmetros. Parâmetros não são "neurônios" nem nada parecido com isso. São pesos e viéses dos cálculos, armazenados no banco de dados - eu explico isso nos meus videos, assistam.&lt;/p&gt;

&lt;p&gt;Existem versões maiores, um GPT o4 tem 1.8 trilhões de parâmetros. São números impressionantes quando se fala, mas na prática, muito acima de 300B, no uso geral, não faz &lt;em&gt;tanta&lt;/em&gt; diferença quanto se pensa. Talvez em perguntas muito específicas. Significa que sim, tem muito mais relacionamentos armazenados, mas não necessariamente isso se traduz em respostas proporcionalmente melhores. Só que pode acabar sendo mais prolixo do que o normal. De novo, estou sendo grosseiro, mas é só pra baixar as expectativas. Parem de se impressionar com números grandes se você não sabe o que eles significam.&lt;/p&gt;

&lt;p&gt;Muito mais coisa influencia a qualidade das respostas, não só parâmetros: qualidade e quantidade de dados de treinamento, arquitetura do modelo, metodologia do treinamento, tamanho da janela de contexto, fine-tuning, alinhamentos e treinamentos de segurança (que costumam piorar as respostas em nome de "segurança").&lt;/p&gt;

&lt;p&gt;Modelos menores, com arquiteturas mais eficientes, dados de treinamento de mais qualidade, focado em &lt;strong&gt;tarefas específicas&lt;/strong&gt; (é melhor um modelo menor especializado do que um modelo gigante que tenta saber tudo - &lt;em&gt;Jack of all Trades, Master of None&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Ser um arquivo maior e ter mais parâmetros só significa uma coisa: vai ser proporcionalmente mais pesado pra rodar e provavelmente não vai caber na memória tudo de uma vez e precisa ficar fazendo &lt;em&gt;switching&lt;/em&gt; de pedaços (como um &lt;em&gt;bank switching de memória&lt;/em&gt;, pra quem é programador).&lt;/p&gt;

&lt;p&gt;Neste projeto de Hello World, eu escolhi - aleatoriamente - usar o modelo &lt;a href="https://github.com/QwenLM/Qwen2.5-Coder"&gt;&lt;strong&gt;Qwen 2.5 Coder 70B&lt;/strong&gt;&lt;/a&gt;. Esses modelos abertos como Llama costumam estar disponíveis em vários tamanhos. Eu escolhi o 70B porque cabe nos 24GB de VRAM que eu tenho na minha GPU RTX 4090. Se sua GPU for menor, escolha modelos com tamanho menor como os de 0.5B, 3B, 14B, 32B, e sempre prefira modelos especializados (como esse que é "Coder"). E sempre leia a página do projeto, que costuma ter exemplos de código e fine-tuning documentados. Não existe um código geral que funciona pra tudo, cada modelo tem características diferentes.&lt;/p&gt;

&lt;p&gt;O Qwen 2.5 vem dividido em 6 arquivos de 5 GB cada, então precisa baixar &lt;strong&gt;30GB&lt;/strong&gt; se quiser brincar com a versão "extra-large".&lt;/p&gt;

&lt;p&gt;O código pra isso é até que bem simples, porque a comunidade de I.A. já empacotou todas as ferramentas que se precisa em bibliotecas fáceis de usar, normalmente em Python (libs em Python que falam com libs de mais baixo nível em C na real), como o &lt;code&gt;transformers&lt;/code&gt; que faz o trabalho pesado de carregar esses arquivos na memória e gerenciar.&lt;/p&gt;

&lt;p&gt;Esses arquivos costumam ter extensões como ".safetensors" que é meio genérico ou ".pt" ou ".pth" que é mais específico pra formato PyTorch, que é uma biblioteca Python de redes neurais feita pela Meta. É concorrente do Tensorflow do Google.&lt;/p&gt;

&lt;p&gt;Tensors é o nome genérico pra vetores ou matrizes. Um número escalar é um Tensor de ordem 0, um vetor é um Tensor de ordem 1, uma matriz é um Tensor de ordem 2 e assim por diante. Não temos palavras pra ordens acima de 2, então pode falar Tensor de ordem 3, ordem 4 e infinitamente pra milhões de ordens numa LLM.&lt;/p&gt;

&lt;p&gt;CPUs são hardwares genéricos feitos pra lidar primariamente com tensors de ordem 0: números escalares inteiros. Operações como soma, adição, multiplicação e tudo mais é de um inteiro pra outro inteiro. Números reais (com decimal) são "simulados", separando um número inteiro chamado "expoente" de outro inteiro chamado "mantissa" (o que você chama de "decimal") separado por um ponto, o "ponto flutuante". Mas essencialmente estamos calculando inteiros e truncando precisão. Quanto mais bits, mais precisão.&lt;/p&gt;

&lt;p&gt;GPUs são hardware especializados em calcular Tensors, em particular matrizes. Eles foram originalmente feitos pra recalcular efeitos em imagens de video, como um video game em movimento. Frames por segundo. Um frame é formado por linhas e colunas de pixels: um tensor de ordem 2.&lt;/p&gt;

&lt;p&gt;Em vez de fazer um loop com "for" e ir calculando pixel a pixel, é mais eficiente passar uma matriz inteira pra multiplicar com outra matriz, um "kernel", pra obter algum efeito como sombra, luz, mudança de cor, distorções e muito mais. Programas que fazem isso costumamos chamar de **shaders".&lt;/p&gt;

&lt;p&gt;Um PyTorch ou Tensorflow se conecta a um GPU usando uma biblioteca que lhes dá acesso a APIs pra, em vez de passar imagens, passar matrizes numéricas. A NVIDIA tem CUDA, a AMD tem ROCM, a Apple tem Metal, existe o projeto geral Vulkan que tenta falar com todos. Mas o mais avançado e o mais usado ainda é o CUDA, porque de fato foi a NVIDIA que saiu na frente anos atrás evangelizando que GPUs poderiam ser usadas pra mais do que shaders de imagens.&lt;/p&gt;

&lt;p&gt;Isso tudo dito, arquivos como esses ".safetensors" são carregados pela biblioteca &lt;code&gt;transformers&lt;/code&gt;, que chama o &lt;code&gt;torch&lt;/code&gt; e manda o CUDA carregar na VRAM da GPU, onde ele pode processar. O código mais básico pra isso é mais ou menos assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;from transformers import AutoTokenizer, AutoModelForCausalLM

device = "cuda" # the device to load the model onto

# Now you do not need to add "trust_remote_code=True"
TOKENIZER = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-Coder-32B")
MODEL = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-Coder-32B", device_map="auto").eval()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Criamos um Tokenizer e um Model. O Tokenizer vai "tokenizar" o que você perguntar no chat e todo texto que passar pro contexto da sessão. Essa tokenização é "mapeada" com o modelo carregado pra gerar uma "embedding", que é a representação em vetores do seu texto, normalizado (compatível) pro modelo carregado. Diferentes modelos convertem seu texto em embeddings diferentes. Um embedding criado com Qwen não serve pra rodar direto com Llama.&lt;/p&gt;

&lt;p&gt;Uma vez tendo esses dois objetos, já podemos fazer um chat:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;# tokenize the input into tokens
input_text = "#write a quick sort algorithm"
model_inputs = TOKENIZER([input_text], return_tensors="pt").to(device)

# Use `max_new_tokens` to control the maximum output length.
generated_ids = MODEL.generate(model_inputs.input_ids, max_new_tokens=512, do_sample=False)[0]
# The generated_ids include prompt_ids, so we only need to decode the tokens after prompt_ids.
output_text = TOKENIZER.decode(generated_ids[len(model_inputs.input_ids[0]):], skip_special_tokens=True)

print(f"Prompt: {input_text}\n\nGenerated text: {output_text}")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Com o &lt;code&gt;input_text&lt;/code&gt; (o que você digitou no chat) tokenizado e convertido, podemos pedir ao modelo pra &lt;strong&gt;COMPLETAR&lt;/strong&gt; esse texto, e ele começa gerando um novo token atrás do outro, usando a tal arquitetura de ATENÇÃO, encontrando a melhor próxima palavra que conseguir. Uma hora ele termina e devolve em &lt;code&gt;output_text&lt;/code&gt; e pronto, isso é sua "resposta". Como eu sempre falo, GPT é isso: um GERADOR DE TEXTO.&lt;/p&gt;

&lt;h3&gt;Tokens e Janelas de Contexto&lt;/h3&gt;

&lt;p&gt;Uma das coisas mais irritantes dos serviços pagos de LLM como ChatGPT ou Claude é a limitação da quantidade de tokens que podemos usar. Eles acabam muito rápido se você está realmente trabalhando todos os dias. E não são exatamente baratos só pra brincar. Sim, sim eu sei, depende do serviço. Eu posso usar Cursor e ter créditos mais baratos de Claude do que usando diretamente Claude. Também vou ter respostas melhores se usar direto a API em vez de usar o chat web, etc.&lt;/p&gt;

&lt;p&gt;Isso é papo pra outro dia, mas é minha motivação pra sempre ter uma opção open source à disposição e saber como "tunar" pra mim. Qwen 2.5 pode não ser melhor que Claude ou Cursor - já explico porque. Mas é de "grátis" e roda na minha máquina local (zero preocupação sobre privacidade).&lt;/p&gt;

&lt;p&gt;Veja meu projetinho "hello world" funcionando:&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/qwen_cli2.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/qwen_cli2.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Note que ele não serve só pra dar respostas. Em um momento eu peço pra ele ler um arquivo meu local, e ele consegue puxar o código e fazer a análise. Já já explico como fazer isso.&lt;/p&gt;

&lt;p&gt;O ponto agora é que ele tem janela de contexto suficiente pra carregar coisas longas como código fonte de arquivos. Eu não testei nada muito pesado, mas esse modelo de 70B ocupa fácil quase 20GB na minha GPU:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBblFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7a520e387d4d7c179b251118d4594853f6663309/Screenshot%20From%202025-04-24%2023-35-16.png?disposition=attachment&amp;amp;locale=en" alt="nvidia-smi" /&gt;&lt;/p&gt;

&lt;p&gt;Pra configurar, eu fiz um bloco de configuração assim:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;DEFAULT_CONFIG = {
    "model_repo": "Qwen/Qwen2.5-Coder-14B-Instruct",
    "model_dir": str(Path(os.environ.get("MODELS_DIR", "/models")) / "Qwen2.5-Coder-14B-Instruct"),
    "quantization": "8bit",
    "max_context_tokens": 120000,
    "max_new_tokens": 10000,
    "temperature": 0.1,
    "model_download_timeout": 1800,
    "helpers_dir": "helper_functions",
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;O importante, primeiro, é que estou escolhendo usar cálculos com FP8 (float-point 8-bits), embora esse modelo acho que seja de FP-16. Se deixar 16-bits, as respostas são muito mais demoradas, no nível que dá pra sentir. Tipo, uma palavra por segundo. Entendi que quem faz essa conversão on-the-fly é a biblioteca &lt;code&gt;bitsandbytes&lt;/code&gt; do Python e precisa ter instalado.&lt;/p&gt;

&lt;p&gt;Outra parte importante são os logs que eu deixo à mostra na hora de carregar o modelo, você vai ver o seguinte se tentar rodar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Qwen attention is NOT SDPA-compatible, or SDPA is not available. Trying xFormers...
xFormers is available. Enabling it for attention.
Sliding Window Attention is enabled but not implemented for `sdpa`; unexpected results may be encountered.
2025-04-25 02:34:27,638 [INFO] accelerate.utils.modeling: We will use 90% of the memory on device 0 for storing the model, and 10% for the buffer to avoid OOM. You can set `max_memory` in to a higher value to use more memory (at your own risk).
Loading checkpoint shards: 100%|██████████████████████████████████████████████████████████| 6/6 [00:07&amp;lt;00:00,  1.29s/it]
SDPA is available and (hopefully) being used!
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;O &lt;code&gt;transformers&lt;/code&gt; dá um aviso de que "Sliding Window Attention está habilitado mas não implementa SDPA". E eu faço um truque com &lt;code&gt;xformers&lt;/code&gt; pra tentar habilitar (no final ele fala "SDPA está disponível").&lt;/p&gt;

&lt;p&gt;O que é &lt;strong&gt;SLIDING WINDOW ATTENTION&lt;/strong&gt;. A grosso modo, a idéia toda desta geração de GPT é o conceito de ATENÇÃO, do paper &lt;a href="https://arxiv.org/abs/1706.03762"&gt;"Attention is all you Need"&lt;/a&gt;, que foi o pontapé que deu início a tudo que temos hoje relacionado a GPT e LLM.&lt;/p&gt;

&lt;p&gt;O conceito importante é que as respostas parecem tão "boas" porque ele usa esse processo de "atenção" pra gerar as respostas.&lt;/p&gt;

&lt;p&gt;No começo, na era do GPT 2, a atenção era bem "focada" num contexto pequeno. Faixa de 1024 tokens, que é quase nada. Dá pra fazer perguntas curtas, mas jamais cabe código-fonte.&lt;/p&gt;

&lt;p&gt;Daí fomos evoluindo, você já viu o Gemini que fala que aguenta 1 milhão de tokens. Mesmo esse Qwen 2.5, diz que aguenta 1 milhão de tokens também. A grosso modo pense que cada 1 token são 4 letras, em média.&lt;/p&gt;

&lt;p&gt;Um código-fonte, em média, tem linhas de 40 caracteres (alguns mais longos, alguns mais curtos). Então isso dá uns &lt;strong&gt;10 tokens por linha&lt;/strong&gt;. Um arquivo médio tem umas 500 linhas x 10 tokens/linha, isso já dá uns 5 mil tokens.&lt;/p&gt;

&lt;p&gt;Um projetinho pequeno inteiro, com uns 20 arquivos x 5.000 tokens, já são uns 100.000 tokens. Lógico, é uma média. Mas veja que menos que 100 mil tokens e não tem com ter um projeto todo no contexto.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Ah, então se usar um Gemini ou esse Qwen, cabe 200 arquivos??"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mais ou menos. Aqui entra Sliding Window. Pense que mesmo tendo um contexto gigante, &lt;strong&gt;não tem como dar atenção pra tudo ao mesmo tempo&lt;/strong&gt;, então ele pesquisa o que você está perguntando no presente, e "desliza a janela de atenção" só até uma parte do contexto que parece mais relevante. Por isso que mesmo carregando um monte de material, ele vai manter em memória, mas &lt;strong&gt;só vai dar atenção pra um pedaço de cada vez&lt;/strong&gt;, isso é SLIDING WINDOW ATTENTION.&lt;/p&gt;

&lt;p&gt;Pra fazer isso temos a funcionalidade de "SCALED DOT PRODUCT ATTENTION" ou SDPA, que foi o que o &lt;code&gt;transformers&lt;/code&gt; reclamou que talvez não tivesse, que é uma das formas de fazer Sliding Window Attention.&lt;/p&gt;

&lt;p&gt;No meu código, pra carregar o modelo e tentar usar SDPA eu tentei isto:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import xformers.ops  # Test if xFormers is installed
model_config.attention_implementation = "flash_attention_2"  # Or "memory_efficient"
print("xFormers is available. Enabling it for attention.")
...
if hasattr(torch.nn.functional, "scaled_dot_product_attention"):
    print("SDPA is available and (hopefully) being used!")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E por isso, no final, ele termina com "SDPA is available". "Parece" que funcionou. Se quiser testar, existe esse codigozinho que pode ser passado pra ver se funciona:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import torch

if not hasattr(torch.nn.functional, "scaled_dot_product_attention"):
    print("SDPA not available")
    exit()

q = torch.randn(2, 4, 8, 16).to("cuda" if torch.cuda.is_available() else "cpu")
k = torch.randn(2, 4, 8, 16).to(q.device)
v = torch.randn(2, 4, 8, 16).to(q.device)

try:
    output = torch.nn.functional.scaled_dot_product_attention(q, k, v)
    print("SDPA works in isolation!")
except Exception as e:
    print(f"SDPA fails in isolation: {e}")
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Dot Product, ou produto escalar de matriz, é uma das operações mais usadas em modelos LLM. Entendi que sem SDPA, vai existir Sliding Window, mas com SDPA vai dar resultados de atenção melhores. Quanto melhor? Difícil quantificar, só entendi que é melhor.&lt;/p&gt;

&lt;p&gt;Mas essa é uma das técnicas de porque hoje em dia tem como ter contextos gigantes de 1 milhão de tokens: porque não precisa dar atenção pra tudo ao mesmo tempo, mas sim janelas em posições diferentes a cada nova pergunta. Entenderam o truque?&lt;/p&gt;

&lt;p&gt;E com isso, naquela minha config eu posso tentar aumentar o &lt;code&gt;max_context_tokens&lt;/code&gt; (que deixei só 120 mil tokens) e o &lt;code&gt;max_new_tokens&lt;/code&gt; que é o limite de tokens por resposta (que deixei em 10 mil tokens). Experimente aumentar esses números até o máximo de 1 milhão pra ver como fica.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O contexto só importa dentro da janela deslizante de contexto. É bom passar bastante contexto. Mas entenda que ele não vai dar atenção pra tudo ao mesmo tempo e sim dentro de uma janela deslizante. &lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Outro parâmetro importante é a &lt;code&gt;temperature&lt;/code&gt;, que deixei baixo em 0.1. Quanto maior, mais "criativo" ele vai ser nas respostas. Quanto menor, mais "exato" tende a ser. Também é outro parâmetro que vale a pena explorar.&lt;/p&gt;

&lt;h3&gt;Prompts e "Agentes"&lt;/h3&gt;

&lt;p&gt;A melhor forma de ter respostas melhores é iniciando toda nova sessão, seja via API, seja no chat Web, descrevendo uma "persona", como você quer que o GPT responda. Quanto mais descritivo for, melhor. Por exemplo, eu tenho este trecho no meu programa:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;def build_system_prompt(tool_prompts: List[str]) -&amp;gt; str:
    base = (
        "You are Qwen2.5 Coder, a highly skilled AI assistant specializing in software development.\n"
        "Your capabilities include code analysis, explanation, error detection, and suggesting improvements.\n"
    )
    tools_section = "TOOLS:\n" + "\n".join(tool_prompts) + "\n\n" if tool_prompts else ""
    rules_section = (
        "IMPORTANT RULES:\n"
        "1. You MUST use the appropriate tool when necessary.\n"
        "2. You MUST NOT reveal the tool commands to the user.\n"
        "3. After a tool is used, continue the conversation as if you have direct access to the content.\n"
        "4. If a file fails to load, inform the user clearly.\n"
        "5. Do NOT ask for file/URL content directly; use tools.\n"
        "6. Once you’ve executed [LOAD_FILE ...], you MUST immediately use the loaded content. "
        "Never say you cannot read it — if you see [LOAD_FILE &amp;lt;path&amp;gt;] then you now *have* it.\n")
    return base + tools_section + rules_section
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Esse é um exemplo curto até mas já estabeleço que tipo de conversa eu quero ter com ele e quais regras eu quero que ele siga. Não sempre, mas na maioria das vezes, ele tende a seguir. Lembre-se: quanto maior ficar o chat, maior o contexto, mais vai deslizar a janela, e mais longe vão ficando essas regras, até chegar um ponto onde ele começa a "esquecer" de agir como eu pedi.&lt;/p&gt;

&lt;p&gt;Entendendo isso, tem outro truque que eu adicionei. Nesse projeto tem um diretório chamado &lt;a href="https://github.com/akitaonrails/Tiny-Qwen-CLI/"&gt;helper_functions&lt;/a&gt; onde temos scripts como &lt;code&gt;load_file.py&lt;/code&gt; ou &lt;code&gt;fetch_url.py&lt;/code&gt;. No código anterior note como ele concatena alguma coisa no meio do prompt, chamada "TOOLS". Quando o chat inicia, eu imprimi na tela e você vai ver esse trecho:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
TOOLS:
[BATCH_LOAD args] – If the user asks to read, load or analyze all the files from a relative path, such as ./src or similar,
[LOAD_FILE args] – Whenever the user asks to read, load, analyze some code and provides a relative path, such as ./file.py or utils/utils.py or similar,
[FETCH_URL args] – Whenever the user asks to read, load, research or consult a URL,
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ou seja, se em algum momento, eu pedir:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBblVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ddf5c1c1802dde75193c6966f0eab57c0acd59b5/Screenshot%20From%202025-04-25%2000-04-50.png?disposition=attachment&amp;amp;locale=en" alt="qwen_cli fetch_url" /&gt;&lt;/p&gt;

&lt;p&gt;esse "can you read a website at https://www...." vai fazer o modelo seguir a regra do prompt inicial e digitar no chat:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[FETCH_URL https://www...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Meu programa de chat tem um "parser", que fica monitorando o histórico por essas palavras-chave especiais:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;def parse_special_commands(response: str) -&amp;gt; List[tuple]:
    pattern = r'\[([A-Z_]+)\s+([^\]]+)\]'
    return [(m.group(1), m.group(2).strip(), m.start(), m.end()) for m in re.finditer(pattern, response)]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;É um parser bem primitivo, usando REGEX pra pegar esses trechos. É aqui que eu digo que meu projeto é educacional. Eu imagino que num Co-pilot, Cursor, etc, tenha um parser muito mais robusto. Eu expliquei como se faz parsers com ANTLR no meu canal, lembra? Um dos videos é o &lt;a href="https://www.youtube.com/watch?v=_7nISfpofec&amp;amp;pp=ygUTYWtpdGFvbnJhaWxzIHBhcnNlcg%3D%3D"&gt;"Eu fiz um servidor de SQL?"&lt;/a&gt;. Parsers de verdade não se fazem com REGEX, mas pra provas de conceito, serve.&lt;/p&gt;

&lt;p&gt;Daí no loop do chat tem este trecho:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;response_text = QwenSession._tokenizer.decode(generated, skip_special_tokens=True)

# Other special commands
for cmd_type, cmd_arg, _, _ in parse_special_commands(response_text):
    if cmd_type in helper_functions:
        result = helper_functions[cmd_type](cmd_arg)
        if result:
            self.history.append({"role": "system", "content": result})
            self.history.append({"role": "user", "content": "Please continue the analysis using the loaded file."})
            print(f"✅ [{cmd_type}] processed '{cmd_arg}'")
            return self.chat(
                prompt, helper_functions, max_new_tokens, temperature, stream, hide_reasoning
            )
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Ele decodifica os tokens gerados, passa pro parser, e se tiver um comando especial, ele chama &lt;code&gt;helper_functions&lt;/code&gt; que dinamicamente vai chamar o &lt;code&gt;fetch_url.py&lt;/code&gt; que é um script besta que vai usar &lt;code&gt;urllib&lt;/code&gt; pra carregar a página.&lt;/p&gt;

&lt;p&gt;Com o conteúdo carregado ele concatena de volta no &lt;code&gt;self.history&lt;/code&gt; que é o histórico do chat, usando &lt;code&gt;.append&lt;/code&gt; e manda uma mensagem - como se fosse eu, o "user" - pedindo pro modelo continuar, agora que tem o conteúdo da página.&lt;/p&gt;

&lt;p&gt;E como podem ver na foto de tela acima, ele consegue me dar o resumo da página do GitHub que eu passei. É assim que os diversos GPTs conseguem puxar informação mais atual dos sites, que não existiam na época do treinamento do modelo pré-treinado.&lt;/p&gt;

&lt;p&gt;Outro exemplo, eu posso pedir pra ele carregar um arquivo meu de código e pedir pra ele analizar, ou corrigir bugs, ou refatorar como eu quiser:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbllCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--20ac87b6e5efb173d4bb2f3b0156ef47cd24bd9d/Screenshot%20From%202025-04-25%2000-13-27.png?disposition=attachment&amp;amp;locale=en" alt="qwen_cli load_file" /&gt;&lt;/p&gt;

&lt;p&gt;É parecido com a função de "upload de arquivos" que tem no ChatGPT. De novo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;o modelo vai seguir as regras do prompt inicial e vai digitar o comando &lt;code&gt;[LOAD_FILE ...]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;meu &lt;code&gt;parser_special_commands&lt;/code&gt;, com seu REGEX, vai encontrar esse comando e separar os argumentos&lt;/li&gt;
&lt;li&gt;o loop do chat vai chamar &lt;code&gt;helper_functions&lt;/code&gt; e mapear pro script &lt;code&gt;load_file.py&lt;/code&gt;, que é um script besta que lê arquivos&lt;/li&gt;
&lt;li&gt;o conteúdo do arquivo é concatenado de volta no &lt;code&gt;history&lt;/code&gt; do contexto e o modelo agora pode continuar respondendo em cima desse conteúdo.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Entendam, aqueles arquivos ".safetensors" são &lt;strong&gt;READ-ONLY&lt;/strong&gt;. Nada é escrito ali. Não interessa qual. Todo LLM é &lt;strong&gt;FECHADO PRA ESCRITA&lt;/strong&gt;. Nada do que você escrever no chat é gravado no modelo.&lt;/p&gt;

&lt;p&gt;Lembra que eu falei que quantidade de "parâmetros" não determina a qualidade do modelo? O que &lt;strong&gt;MAIS&lt;/strong&gt; determina qualidade é a &lt;strong&gt;QUALIDADE DO MATERIAL DE TREINAMENTO&lt;/strong&gt;. E chats, até certo ponto, são úteis como amostra de como o modelo deve responder, mas 90% ou mais eu afirmo que é puro e completo &lt;strong&gt;LIXO&lt;/strong&gt;. Todo chat que escrevemos é lixo e não presta pra treinar, ele só iria PIORAR o modelo final e deixar mais pesado com informação inútil.&lt;/p&gt;

&lt;p&gt;Pode escrever o "Muito Obrigado" ou "Por Favor" que quiser. Tudo é descartado ao final, nada é gravado, e nenhum ChatGPT, Gemini, Claude, Deepseek ou Qwen ou Llama jamais vai saber nada do que você conversou com ela. São informações totalmente &lt;strong&gt;SEPARADAS E DESCARTÁVEIS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Tudo que eu digitei nesses exemplos nas fotos de tela como meu chat-zinho de Qwen é descartado no momento que digito "bye" ou dou "Ctrl+D" pra sair. Isso que chamamos de "sessão" ou "histórico". É só um arquivo de texto desnecessário pro modelo. Só serve pra nós, humanos, como anotação pra usar depois, se quiser. O meu programinha nem tem a função de gravar esse histórico. Quando sair, está tudo limpo. Então, pode xingar no meu chat à vontade.&lt;/p&gt;

&lt;p&gt;Tem dois pontos importantes nesta seção:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;quanto melhor forem as regras que você descrever no começo, melhor tende a ser o resultado das respostas. Isso vale pra qualquer LLM.&lt;/li&gt;
&lt;li&gt;você pode "combinar" com o modelo pra cuspir "pseudo-comandos" e ter um programa externo que faz o parsing e executar os comandos de verdade, como eu fiz com meus "helpers". É isso que chamamos de agentes. Agente de LLM é um programinha que devolve algum conteúdo de volta ao histórico de contexto da sessão.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Aonde um Cursor ou Co-pilot são melhores do que esse meu programinha educacional? Eles já vem pré-configurados com DEZENAS de regras pré-prontas que eles testaram bastante pra dar resultados melhores, e contam com uma biblioteca de DEZENAS de pequenos scripts - como esses meus helpers - pra puxar informação do seu projeto ou da sua IDE pra melhorar o contexto.&lt;/p&gt;

&lt;p&gt;Mas o segredo é isso: &lt;strong&gt;PROMPT DE REGRAS&lt;/strong&gt; e &lt;strong&gt;SCRIPTS DE CONTEXTO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se for resumir o ecossistema inteiro de Agentes ou MCPs ou seja lá como se chama hoje em dia, é essa combinação de fatores pra melhorar as respostas.&lt;/p&gt;

&lt;p&gt;Outro truque é utilizar MAIS DE UMA LLM. Lembra que falei que LLMs especializadas são melhores? Além de Qwen 2.5 Coder, que dizem que é bom pra código, já existia antes o WizardCoder, Code Llama, StarCoder, agora tem o Deepseek Coder e vários outros, alguns até especializados em linguagens específicas. Você pode experimentar qual dá resultados melhores pro seu caso.&lt;/p&gt;

&lt;p&gt;Se salvar a sessão em texto, pode descarregar uma LLM, carregar outra LLM, re-tokenizar e re-carregar no contexto dessa outra LLM e tentar um resultado diferente - se alguém quiser implementar essa funcionalidade no meu projetinho, Pull Requests são bem vindos.&lt;/p&gt;

&lt;p&gt;Como falei no começo, é o que faz softwares como o &lt;a href="https://aider.chat/"&gt;Aider&lt;/a&gt;. Eu não brinquei o suficiente com ele ainda, mas ele se conecta com ChatGPT, Gemini, Claude e qualquer outro e você pode habilitar quantos quiser. Acho que suporta usar modelos locais como o Qwen também, então já existem ferramentas que fazem isso. E essa é a terceira perna pra melhores respostas: MULTI-LLM.&lt;/p&gt;

&lt;h3&gt;Próximos Passos&lt;/h3&gt;

&lt;p&gt;Em vez de ficar só impressionado com o monte de ferramentas que existem, entendam essas peças fundamentais. Saber como as coisas funcionam sempre dá mais opções pra forjar alternativas que funcionam melhor pra casos específicos. Você, que diz ser programador, deveria estar explorando essas opções - e não só ficar se conformando com as limitações e esperando alguém resolver seu problema. Arregaça as mangas e resolve você mesmo. Não foi pra isso que virou programador???&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6198</id>
    <published>2025-04-24T01:00:00-03:00</published>
    <updated>2025-04-24T03:31:10-03:00</updated>
    <link href="/2025/04/24/acessando-seu-nas-usando-iscsi-em-vez-de-smb" rel="alternate" type="text/html"/>
    <title>Acessando seu NAS usando iSCSI em vez de SMB</title>
    <content type="html">&lt;p&gt;Se você tem um NAS ou qualquer tipo de servidor de arquivos na rede, provavelmente o padrão é criar uma pasta compartilhada e depois montar no seu sistema operacional, seja Windows, Mac ou Linux usando o velho, e cansado, protocolo SMB (Samba).&lt;/p&gt;

&lt;p&gt;Em Linux, isso é um enorme pé no saco, porque o protocolo SMB (feito pra Windows), não trás direito conceitos como ownership (chown) ou permissões (chmod). Ele não entende coisas como permissão de execução (tudo é executável). Pra coisas simples como diretório de Downloads, Videos, Fotos, meio que não importa.&lt;/p&gt;

&lt;p&gt;Mas digamos que queira editar código fonte em projetos com Git. Vai ser um pesadelo, porque o SMB sobrescreve as permissões, independente do que está por baixo, e toda vez vai conflitar com o Git, que vai achar que arquivos foram modificados (mudar permissões é uma mudança que ele guarda), e vai ficar mandando você fazer commit disso, e isso vai sujar todo seu repositório (nunca use projetos Git em SMB na rede).&lt;/p&gt;

&lt;p&gt;Em vez disso, eu estou em Linux, meu NAS é Linux. O certo é usar um protocolo de Linux. Esse protocolo é NFS e eu mostrei como configurei isso no &lt;a href="https://www.akitaonrails.com/2025/04/17/configurando-meu-nas-synology-com-nfs-no-linux"&gt;outro post&lt;/a&gt; da semana passada.&lt;/p&gt;

&lt;p&gt;Enfim, estou usando NFS e com muito menos dor de cabeça de permissões em arquivos. Mas aí tive outro problema: se viu meus últimos posts, viu que fiquei experimentando MUITO com Docker. Gerando imagens novas como se não houvesse amanhã. Rapidamente, meu NVME local de 2 TB encheu e começou a reclamar de falta de espaço. Então pensei, &lt;em&gt;"Ah, é só mover o diretório de armazenamento do Docker, o /var/lib/docker, pra um diretório montado com NFS no meu NAS"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pra fazer isso é muito fácil:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sudo mkdir -p /mnt/nfs/docker
sudo chown root:docker /mnt/nfs/docker
sudo chmod 771       /mnt/nfs/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isso é pra criar um novo diretório dentro do meu NAS e aplicar as permissões corretas.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sudo systemctl stop docker
sudo mv /var/lib/docker /mnt/nfs/docker
# sudo rm -Rf /var/lib/docker (isso vai liberar tudo pra mim)
sudo ln -s /mnt/nfs/docker /var/lib/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isso vai liberar todo o espaço ocupado. Não preciso me preocupar com as imagens porque eu posso recriar tudo dos meus Dockerfiles. E não preciso me preocupar com volumes porque não tinha nada importante que não seja montado por fora, em diretórios de verdade. Regra: nunca guarde coisas importantes em volumes de Docker. É uma má prática!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# editar /etc/docker/daemon.json
{
  "data-root": "/mnt/nfs/docker"
}

sudo systemctl start docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pronto, isso muda a configuração de &lt;code&gt;/var/lib/docker&lt;/code&gt; pro novo &lt;code&gt;/mnt/nfs/docker&lt;/code&gt; e depois de reiniciar o serviço, ele vai passar a gravar tudo lá. Então acabou o post né?&lt;/p&gt;

&lt;p&gt;É até aqui que a maioria vai. Mas se você trabalhar 2 minutos nessa configuração, vai sentir algo MUITO errado.&lt;/p&gt;

&lt;p&gt;Eu tentei fazer build de uma nova imagem. E estava demorando ABSURDO. Acontece que NFS é sim, mais rápido que SMB, mas ambos foram feitos pra servidores de arquivos, e não pra sistemas operacionais. Faz muita diferença.&lt;/p&gt;

&lt;p&gt;Esses protocolos são &lt;strong&gt;FILE-BASED&lt;/strong&gt;, toda operação é baseada em arquivos. E isso é extremamente ineficiente.&lt;/p&gt;

&lt;p&gt;Se assistiu meus videos sobre sistemas de arquivos, já deveria saber que eles são &lt;strong&gt;BLOCK-LEVEL&lt;/strong&gt;. No nível do sistema de arquivos, se trabalha com blocos de bytes, de tamanho fixo, organizadas em alguma variação de árvores B-TREE (depende do sistema de arquivos). As operações são em nível de bloco. Arquivos são abstrações pra coleções de blocos.&lt;/p&gt;

&lt;p&gt;Quando se faz um &lt;code&gt;chmod -R&lt;/code&gt; que é mudança de permissão recursiva, dentro do build do Dockerfile, por exemplo, mesmo uma árvore vazia vai causar recursão em cada entrada lá dentro, seja arquivos escondidos, ACLs, etc. E ele precisa pesquisar todas as permissões de tudo lá dentro. Isso custa tempo de rede (porque NFS/SMB são protocolos de rede).&lt;/p&gt;

&lt;p&gt;Isso causa latência. Cada permissão de cada arquivo é uma chamada remota individual, uma RPC call. Mesmo na rede mais rápida, como a minha de 10Gbps, mesmo que cada RPC seja de menos de 0.1 ms, rapidamente vai dar gargalo. E é isso mesmo que começou a acontecer.&lt;/p&gt;

&lt;p&gt;Eu usei &lt;code&gt;sudo nfsiostat 1 10&lt;/code&gt; pra checar. Tava dando isso:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;write: ops/s=2417  kB/s≈295 MiB/s  avg exe=106 ms  avg queue=102 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Média de 106 ms pra cada RPC. Isso é uma eternidade. Tempo médio na fila de espera de 102 ms. Mesmo que que eu faça só 100 chamadas RPC, vai custar mais de 10 segundos só de latência. E num build de sistema operacional, instalando pacotes com milhares de arquivos, isso vai custar literamente uma eternidade. Não é viável usar Docker build em NFS/SMB.&lt;/p&gt;

&lt;h3&gt;iSCSI&lt;/h3&gt;

&lt;p&gt;Obviamente eu não sou o primeiro a esbarrar nesse problema e fazer a besteira de usar um protocolo file-based pra serviços de alto volume de alterações de arquivos como cache de Docker.&lt;/p&gt;

&lt;p&gt;O certo é criar um drive virtual iSCSI no meu NAS. Se nunca ouviu falar, SCSI é um protocolo avançado pra drives. Em PCs baratos dos anos 90 e 2000 se usava IDE/ATA, que é um protocolo mais simples e mais barato. Mas os primeiros Mac e workstations UNIX dos anos 80 e 90 sempre usaram SCSI, que era muito superior.&lt;/p&gt;

&lt;p&gt;Ele era &lt;strong&gt;assíncrono&lt;/strong&gt;, com controle de fila. Dava pra mandar dezenas de comandos de uma só vez e o protocolo era inteligente de reordenar esses comandos pra ter mais eficiência (por exemplo, ignorar leitura de um arquivo se antes veio um comando de deletar). IDE era bloqueante, só um comando por vez. Um único BUS de SCSI conseguia ter até 16 dispositivos. Parallel ATA era 2 por cabo. Ridículo. SCSI tinha sistemas avançados de recuperação de erros e relatórios. Tinha recursos "enterprise" pra hot-swap, gerenciamento de energia, comportamentos de timeout, recuperação, clustering, etc. E além de tudo era agnóstico ao meio de transporte, podia ser fibra ótica, SAS ou até mesmo iSCSI que é SCSI sobre Ethernet, que é o que vamos usar.&lt;/p&gt;

&lt;p&gt;IDE/ATA não tinha nada disso. Era o protocolo de discos de PC pobre. Workstation de verdade, SUN, IRIX, Silicon Graphics, Macs, usavam SCSI.&lt;/p&gt;

&lt;p&gt;Mais importante iSCSI é um protocolo de blocos, e não de arquivos. Enfim, isso varia de gerenciador de NAS, mas no caso do Synology DSM, que é o que eu uso, ele tem um aplicativo chamado SAN Manager, que permite criar um drive iSCSI:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbkFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6fbcaf908c817d6e9afe91ee79d256db30002536/Screenshot%20From%202025-04-23%2023-46-23.png?disposition=attachment&amp;amp;locale=en" alt="iSCSI create" /&gt;&lt;/p&gt;

&lt;p&gt;Só seguir o wizard, dar nome, escolher o tamanho, habilitar CHAP se precisar de segurança extra (eu não preciso, estou numa rede local controlada). Isso vai criar o drive iSCSI (pense como se fosse um drive USB remoto) e um LUN (Logical Unit Number). Um LUN é simplesmente um de muitos potenciais "discos virtuais" que um target pode apresentar. LUN é como um slot numerado no array de storage de um target. Target é um servidor de discos. Num SAN você pode ter vários discos virtuais e a forma de organizar isso é com LUNs. No nosso caso não interessa muito saber detalhes de LUN.&lt;/p&gt;

&lt;p&gt;Demora um pouco, mas ao terminar é só voltar pro meu Manjaro Linux e fazer:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;yay -S open-iscsi
sudo systemctl enable --now iscsi 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isso sobe o serviço pra iniciar automaticamente em todo boot. Agora pra descobrir meu drive na rede:
&lt;code&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ sudo iscsiadm -m discovery -t sendtargets -p 192.168.0.xx
192.168.0.xx:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[2804:1b3:....:fe18:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
[fe80::9209:....:3f7d]:3260,1 iqn.2000-01.com.synology:TERACHAD.docker.7af6e...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Todo drive virtual tem um &lt;strong&gt;IQN&lt;/strong&gt; que é tipo uma "URL" pro drive. Agora fazemos login:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ sudo iscsiadm -m node --login
Login to [iface: default, target: iqn.2000-01.com.synology:TERACHAD.docker.7af6e9116c1, portal: 192.168.0.21,3260] successful.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Como não habilitei CHAP nem pede senha nem nada (em empresa o certo é estar habilitado, lógico). E pronto. Ao fazer isso eu ouço no GNOME o barulhinho igual de quando você conecta um pen-drive no PC e ele já automaticamente aparece como um disco. Se fizer &lt;code&gt;lsblk&lt;/code&gt;, vai aparecer como um drive normal &lt;code&gt;/dev/sdX&lt;/code&gt; da vida. No aplicativo Disks do GNOME (ou no Disk Management do Windows) vai realmente aparecer como se fosse qualquer outro hard-drive ou SSD da vida. Ele não se comporta como uma pasta compartilhada e sim como um drive de verdade. Incluve precisa formatar, igual um pen drive novo.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbkVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d627d4ed24954432a30cbf45712a062d080086b7/Screenshot%20From%202025-04-23%2023-53-29.png?disposition=attachment&amp;amp;locale=en" alt="Disks" /&gt;&lt;/p&gt;

&lt;p&gt;A formatação em si, demora bastante, porque no processo ele precisa checar e mapear bloco a bloco. Como é pela rede, é mais lento do que num drive de verdade, claro. Mas é só uma vez. Mesmo na minha rede de 10Gbps ele vai formatar numa velocidade de uns 120 MB/s, num drive de 2TB, isso vai dar na faixa de 2 horas pra um fast format. Eu esqueci que dava pra formatar com "lazy":&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mkfs.ext4 -v -E lazy_itable_init=1,lazy_journal_init=1 /dev/sdX
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fica pra próxima, agora é esperar 2 horas. Pra garantir que monte automaticamente no boot, precisa checar o &lt;code&gt;node.startup&lt;/code&gt; dele:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ sudo iscsiadm -m node -o show | grep -E 'Target:|node.startup'

node.startup = manual
node.startup = manual
node.startup = manual
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Está como "manual", pra trocar pra "automatic" precisa fazer:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ sudo iscsiadm -m node \
  -T iqn.2000-01.com.synology:TERACHAD.docker.7af6... \
  -o update -n node.startup -v automatic
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Com o daemon iSCSI de pé e depois do login, além de &lt;code&gt;/dev/sda&lt;/code&gt; se fizer &lt;code&gt;ls /dev/disk/by-path&lt;/code&gt; vamos encontrar algo assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6exxxxx-lun-1 -&amp;gt; ../../sdX
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Como estou usando o serviço AUTOFS e ele "rouba" meu diretório &lt;code&gt;/mnt&lt;/code&gt;, vou montar o drive iSCSI via fstab mesmo em &lt;code&gt;/media/docker&lt;/code&gt; adicionando esta linha no final de &lt;code&gt;/etc/fstab&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
/dev/disk/by-path/ip-192.168.0.xx:3260-iscsi-iqn.2000-01.com.synology:TERACHAD.docker.7af6e9xxxx-lun-1  /media/docker  ext4      _netdev,nofail,x-systemd.automount,x-systemd.requires=iscsid.service,noatime,nodiratime,commit=60  0 2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Depois é só recarregar e montar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sudo systemctl daemon-reload
sudo mount -a
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E é isso. A partir de agora eu tenho um "drive virtual" no meu NAS remoto. Agora, vamos mudar o Docker pra começar a usar ele. Posso reconfigurar meu Docker pra fazer cache de imagens e volumes e tudo mais direto nesse drive. Só editar o arquivo &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt; com o mount point:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  "data-root": "/media/docker/docker"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Não esquecer de criar o diretório no novo drive com as permissões certas (e ele aceita permissões, porque é um drive formatado com ext4 como qualquer outro):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sudo mkdir -p /media/docker/docker
sudo chown root:docker /media/docker/docker
sudo chmod 771 /media/docker/docker
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Por fim, é só reiniciar o serviço do Docker, apagar o &lt;code&gt;/usr/lib/docker&lt;/code&gt; pra liberar espaço no meu NVME principal e pronto. Deve ser MUITO mais rápido que NFS, não tão rápido quanto um disco local, claro. Mesmo com 10Gbps, rede introduz latência, não tem jeito. Mas o mais pesado é mesmo o processo de build, que tem muita escrita. Pra carregar uma imagem pronta, é pra ser super rápido. Mas pelo menos usando um protocolo de bloco em vez de protocolo de arquivos, é pra diminuir a latência numa ordem de pelo menos 10x ou mais.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbklCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--9521c24a40047e0b363a72733687f9300354e360/Screenshot%20From%202025-04-24%2001-01-15.png?disposition=attachment&amp;amp;locale=en" alt="docker build" /&gt;&lt;/p&gt;

&lt;p&gt;E é mais rápido mesmo? &lt;strong&gt;SIM&lt;/strong&gt;. Nesta foto de tela do docker build rodando, com NFS os tempos à direita estavam sendo de CENTENAS DE SEGUNDOS pra vários comandos. Agora está de novo abaixo de 1 segundo como deveria. Pra todos os efeitos e propósitos, não estou sentindo diferença se fosse um HD externo USB 3.2 Gen 2.&lt;/p&gt;

&lt;p&gt;A diferença MASSIVA que um drive virtual BLOCK-LEVEL faz, comparado com um protocolo NFS/SMB FILE-LEVEL é brutal, o protocolo é um overhead gigante. Aliás, é mais ou menos assim que funciona quando você vai na AWS e contrata storage EBS (Elastic BLOCK Store), por isso se chama "BLOCK", porque é um protocolo de BLOCO como iSCSI e não NFS. Espero que tenha aprendido como faz diferença entender como protocolos e sistemas de arquivos funcionam. Se não assistiu meus vídeos, tem &lt;a href="https://www.youtube.com/watch?v=lxjBgxmDZAI&amp;amp;list=PLdsnXVqbHDUcM0LTAxqrVrTy6Q7jQprjt&amp;amp;pp=gAQB"&gt;esta playlist&lt;/a&gt; que explica BLOCOS em detalhes.&lt;/p&gt;

&lt;p&gt;Mas sim, iSCSI tem desvantagens: sendo um drive, não pode, ou não deveria, ser compartilhado (pense dois PCs compartilhando o mesmo pen drive, ia dar muita m&amp;amp;rda). Block devices foram feitos pra serem usados por um único dispositivo. Justamente, pra compartilhar, é que existem protocolos como SMB ou NFS: pra gerenciar acessos concorrentes ao mesmo arquivo na rede.&lt;/p&gt;

&lt;p&gt;Outra desvantagem: o drive virtual é um grande BLOB de bits. Sem "montar" não tem como ver os arquivos. No servidor NAS, ele não mostra os arquivos. É como um arquivo .vhd de Virtual Box. Precisa "ligar" e "montar" no "PC". Por isso que ainda vou usar NFS pras coisas de sempre, como Videos ou Downloads. E o drive virtual é exclusivamente pro cache de Docker, neste caso. Aí funciona bem. Nenhum outro PC acessa. Se eu tivesse um segundo Linux que precisasse de cache de Docker, precisaria criar uma segunda LUN só pra ele. Lembre-se, é um drive. Virtual ou não, ele se comporta como um drive de verdade pro seu sistema.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6197</id>
    <published>2025-04-23T17:45:00-03:00</published>
    <updated>2025-04-28T11:53:36-03:00</updated>
    <link href="/2025/04/23/mudando-roupas-usando-i-a-comfyui" rel="alternate" type="text/html"/>
    <title>Mudando roupas usando I.A. (ComfyUI)</title>
    <content type="html">&lt;p&gt;No &lt;a href="https://www.akitaonrails.com/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games"&gt;post anterior&lt;/a&gt; mostrei como gerar character sheets pra desenvolvimento de games. Mas alguns me perguntaram "e se eu quiser roupas específicas?".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hold my Beer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tem como, é um workflow separado que depois dá pra adicionar ao workflow anterior se quiser (fica de lição de casa). Basta pegar o personagem gerado no primeiro grupo de nodes e passar por este outro workflow que troca roupas. (ou fazer o trabalhinho extra de gerar loras e ativar com palavras-chave no prompt também, não existe só um jeito).&lt;/p&gt;

&lt;p&gt;Diferente do workflow do Mick - que é pago - este outro é aberto, peguei em algum Reddit e coloquei na pasta de workflows do meu projeto de Docker como "garmente-replacement-idm-vton.json", só carregar no ComfyUI e brincar. Vamos ver o resultado. Eis duas imagens, o resultado não é perfeito, mas veja se descobre qual dos dois é o original e qual é o gerado pela I.A.:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbVlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d6b83fbb5e1890f5caaa37c3554c986eb792d3a5/1679905172_chris_hemsworth-getty.jpg?disposition=attachment&amp;amp;locale=en" alt="Hemsworth 1" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbWNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c6b4ba42ca0bf5203d482261c00c5e8757a4977b/idm-vton_00004_.png?disposition=attachment&amp;amp;locale=en" alt="Hemsworth 2" /&gt;&lt;/p&gt;

&lt;p&gt;Resposta ao final do post kkkk&lt;/p&gt;

&lt;p&gt;Mas vamos lá, o workflow completo se parece com isto:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbWdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--9c94d9d2d41bc413095af8759adf7f63226bd262/Screenshot%20From%202025-04-23%2017-18-12.png?disposition=attachment&amp;amp;locale=en" alt="workflow" /&gt;&lt;/p&gt;

&lt;p&gt;No estágio atual da minha setup tem um glitch que não consegui resolver. Eu acho que é a extension JNodes que está abrindo um popup infernal no top esquerdo da interface, que não fecha de jeito nenhum. Por enquanto estou abrindo o Inspector e deletando o Node do HTML direto mesmo kkkk gambiarra mas funciona:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbWtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--5b2a6e8a58dda6f235f02a109d9064cb8764b7b7/Screenshot%20From%202025-04-23%2017-10-53.png?disposition=attachment&amp;amp;locale=en" alt="JNode bug" /&gt;&lt;/p&gt;

&lt;p&gt;Se alguém souber como consertar, agradeceria muito. Tem um alert de uma extension que parece que tá defasada, esse front-end alguma coisa, mas é só ignorar. Tinha que ser front-end-alguma-coisa ...&lt;/p&gt;

&lt;p&gt;Enfim. neste primeiro grupo, é simples, basta carregar uma imagem o mais limpa e clara possível da roupa nova que quer usar. E depois a foto original onde quer que essa roupa seja aplicada. Ele não faz milagre, se a foto for de alguém com paletó e você quiser mudar pra regata, às vezes ele consegue, mas não espere que os braços e torso sejam bons, ele vai redesenhar tudo como achar que precisa, ou vai acabar colocando a regata em cima do paletó, enfim, é I.A., quanto mais claro for o objetivo, melhor o resultado. Se a nova roupa é um paletó, idealmente o original era outro paletó, vai encaixar melhor.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbW9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--06f518cfe1fa3ef57fff0ee315f1613632c27d91/Screenshot%20From%202025-04-23%2017-20-39.png?disposition=attachment&amp;amp;locale=en" alt="Load Images" /&gt;&lt;/p&gt;

&lt;p&gt;Já avisando que os modelos do IDM-VTON pesam realmente uma &lt;strong&gt;TONELADA&lt;/strong&gt;. Ele sozinho baixa nada menos que &lt;strong&gt;30GB&lt;/strong&gt; de modelos (já incluso no meu setup) e vai exigir mais de 20GB de VRAM, então abaixo de uma RTX 3090 de 24GB, não vai rodar. Tem como rodar online, no site do Hugging Face. Assista este tutorial que mostra como:&lt;/p&gt;

&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/WL59FqL0L-s?si=go8Qv-0hMiPOVwH_" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Esse é o canal do &lt;strong&gt;Aiconomist&lt;/strong&gt;. Ele mostra como rodar online e como fazer o setup na sua máquina &lt;a href="https://youtu.be/pFYqPf1Z7x8?si=Ryu5vxPmKGr4zhWx"&gt;neste outro video&lt;/a&gt; que usei de referência pra fazer meu workflow. Assinem o canal dele, vale a pena, os tutoriais são bem explicadinhos e tem mais detalhes que não vou explicar neste post.&lt;/p&gt;

&lt;p&gt;Isso dito, tem um bug que ainda não resolvi. Depois que roda o workflow, o modelo fica pendurado na VRAM da GPU e se tento rodar de novo, ele EMPERRA. Aí tenho que desligar o container de Docker e reiniciar pra recuperar a memória. Tentei um node de Clear VRAM GPU mas não funcionou. Se alguém souber como consertar, agradeço também.&lt;/p&gt;

&lt;p&gt;O próximo passo é recorrente em vários workflow: extrair camadas de informações da foto original. Conseguir uma máscara da roupa que queremos substituir e informações da pose original:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbXNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6881fd4a5e1b14b3679001914a214e662a878d9f/Screenshot%20From%202025-04-23%2017-22-30.png?disposition=attachment&amp;amp;locale=en" alt="máscara e pose" /&gt;&lt;/p&gt;

&lt;p&gt;É por isso que workflows de ComfyUI são poderosos quando você começa a entender como processo é dividido. Essas partes são reusáveis e diferentes modelos dão diferentes resultados dependendo dessas controlnets que se adiciona.&lt;/p&gt;

&lt;p&gt;Aí chegamos na parte principal: ligar todas essas informações no node de IDM-VTON, que vai reposicionar a nova roupa em cima da foto original:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbXdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a9901053095a49da4b8ea874065d42d55cee2f58/Screenshot%20From%202025-04-23%2017-29-14.png?disposition=attachment&amp;amp;locale=en" alt="IDM-VTON" /&gt;&lt;/p&gt;

&lt;p&gt;Note que o resultado não é muito bom, fica bem "fake" mesmo. E tem como ajustar parâmetros pra melhorar. Mas o caso de uso não é usar pra trocar por roupa &lt;strong&gt;específica&lt;/strong&gt; mas sim por roupa &lt;strong&gt;similar&lt;/strong&gt;. Já vou explicar, mas antes, uma tangente.&lt;/p&gt;

&lt;p&gt;O repositório da extension original desse Node é &lt;a href="https://github.com/TemryL/ComfyUI-IDM-VTON"&gt;este&lt;/a&gt; Mas se tentar instalar, ele quebra. Parece que ele está um pouco defasado com o ComfyUI e precisa rodar numa versão mais velha. Mas isso é um saco. Então, eu resolvi fazer um &lt;a href="https://github.com/akitaonrails/ComfyUI-IDM-VTON"&gt;&lt;strong&gt;FORK&lt;/strong&gt;&lt;/a&gt; com as correções necessárias pra rodar na versão mais nova (as versões mais novas dos pacotes de Python diffusers, transformers, huggingface_hub, quebram o IDM-VTON).&lt;/p&gt;

&lt;p&gt;Aliás, o gerenciamento de dependências de Python é bem RUIM. Mas do ComfyUI é ainda pior, porque diferentes extensions (que instalam dependências de Python globalmente) podem estar em diferentes estágios de desenvolvimentos. Toda vez que o ComfyUI em si muda, todo mundo precisa atualizar rápido. Por isso o ideal é não instalar a versão da branch master do ComfyUI, sempre uma versão estável (marcada numa tag) ou duas versões pra trás, pra garantir. Mas aí tem extensions novas que exigem você estar na versão mais nova. Como falei, é um pesadelo pra gerenciar. Sendo programador, eu consigo "gambiarrar" quando dá erro, mas usuários normais vão sofrer um pouco.&lt;/p&gt;

&lt;p&gt;Feito o rant. No passo anterior a primeira parte do fluxo acaba. Quando bate nesse node Image Sender, o processo pára - não sei porque. E precisa clicar no botão "RUN" uma segunda vez. Daí o outro node, Image Receiver, vai receber a nova imagem. Acho que vou só apagar esses dois nós e ligar direto o output "image" do node Run IDM-VTON Inference e ligar direto no próximo node que são o Image Composite Masked e o VAE Encode.&lt;/p&gt;

&lt;p&gt;Vamos voltar um passo:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbTBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--5fa75d33673959c49a25e26517d8f7c8b19438f0/Screenshot%20From%202025-04-23%2017-36-45.png?disposition=attachment&amp;amp;locale=en" alt="Nova foto, nova roupa" /&gt;&lt;/p&gt;

&lt;p&gt;Nesse próximo passo, no segundo "RUN", ele pega a imagem com a nova roupa, mas baixa qualidade e carrega um novo checkpoint (no caso o juggernaultXL mas poderia ser qualquer outro como o próprio SDXL, FLUX ou dreamsharper). O modelo vai determinar a nova foto, que é o que se gera no final, na direita.&lt;/p&gt;

&lt;p&gt;Note que a nova foto tem qualidade de roupa muito melhor, mas a cara é completamente diferente. Ele usou as informações da foto original, manteve a pose, a proporção do corpo, até o fundo, mas o rosto ele não consegue manter. Não tem problema, porque podemos ajudar.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbTRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c957cb4a4b16ea81a02a3abf5f151e787bcc6b5b/Screenshot%20From%202025-04-23%2017-39-08.png?disposition=attachment&amp;amp;locale=en" alt="mix photos" /&gt;&lt;/p&gt;

&lt;p&gt;Lembra que no passo anterior conseguimos a máscara (preto e branco) da posição exata da roupa? Podemos aumentar a máscara e aplicar um Gaussian Blur (pra depois ele mesclar bonitinho na foto original) e usar isso pra recortar da foto gerada com o rosto diferente, usando o modelo &lt;a href="https://github.com/tencent-ailab/IP-Adapter/"&gt;&lt;strong&gt;IPAdapter&lt;/strong&gt;&lt;/a&gt; (que sozinho são outros 30GB de modelos - falei que é pesado).&lt;/p&gt;

&lt;p&gt;Se não entendi errado, IP Adapter é o responsável por conseguir gerar novas imagens COERENTES entre si, com o mesmo rosto, ou mesmas roupas, mas em outras posições, por exemplo, um tipo de geração condicionada. Com ele é possível controlar BASTANTE a coerência de novas imagens, gerar uma personagem em diferentes posições, em diferentes fundos e sempre mantendo as características da personagem original.&lt;/p&gt;

&lt;p&gt;O IP Adapter vai usar a foto original, manter o rosto, usar as máscaras e misturar com a foto com o rosto diferente, tirando só a roupa e depois juntar tudo no Image Composite Masked que é o Node final do processo.&lt;/p&gt;

&lt;p&gt;No final, podemos comparar a primeira tentativa de colocar a roupa nova (que ficou ok mas feita, e a roupa nova, que ficou diferente mas mais bonita) e tudo integrado na foto original, mantendo o mesmo rosto.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbThCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--65e14f65c2e29ed7cb1e361808a6b794767bd7d9/Screenshot%20From%202025-04-23%2017-43-13.png?disposition=attachment&amp;amp;locale=en" alt="image compare" /&gt;&lt;/p&gt;

&lt;p&gt;Como falei, as qualidades são afetadas pela escolha do modelo, as configurações do KSampler, número de passos, cfg e tudo mais. Tem que experimentar e gerar várias até chegar no resultado que te agrada mais. É assim que se aprende mais também. Não é só clicar uma vez e magicamente já ter o resultado. Um bom artista experimenta dezenas de vezes.&lt;/p&gt;

&lt;p&gt;A imagem final talvez não seja adequada pra ser capa da Vogue (embora eu já tenha visto muita capa oficial BEM duvidosa). Vai depender da necessidade. Em particular é bom pra prototipação. "Como será que fica essa roupa vestida na minha modelo?" Em vez de chamar a modelo, mandar vestir, ou gastar uma hora Photoshopando, em minutos dá pra experimentar várias roupas diferentes e ver se a modelo combina com a roupa, sem precisar gastar tempo. Pra um fotógrafo só mostrar pro cliente, antes de gastarem dinheiro com estúdio, ajudantes, equipamento, etc. Dá pra visualizar rapidamente e discutir detalhes muito mais rápido.&lt;/p&gt;

&lt;p&gt;É nesse tipo de coisa que ferramentas como essa podem ajudar muito e diferenciar um artista do outro, não substituindo o artista, mas criando mais opções. E o principal: todos esses workflows são modificáveis, você reprograma e combina com outros como quiser. O limite é a criatividade.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6196</id>
    <published>2025-04-23T16:25:00-03:00</published>
    <updated>2025-05-05T18:59:41-03:00</updated>
    <link href="/2025/04/23/usando-i-a-comfyui-pra-gerar-npcs-em-desenvolvimento-de-games" rel="alternate" type="text/html"/>
    <title>Usando I.A (ComfyUI) pra gerar NPCs em desenvolvimento de games</title>
    <content type="html">&lt;p&gt;Eu andei atualizando BASTANTE meu projetinho de Comfyui com Docker Compose. Eu acho que é o setup mais completo e testado de todos, com a vantagem de ser em Docker, então é repetitível com zero setup. Leia &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"&gt;meu post&lt;/a&gt; pra mais detalhes sobre como ele funciona.&lt;/p&gt;

&lt;p&gt;Resolvi documentar neste post sobre um dos workflow mais complexos que eu testei que funciona nesse meu setup: como desenvolver Character Sheets de personagens aleatórios, em qualquer estilo, seja realista, seja estilo pixar, seja estilo cartoon, o que você quiser. O resultado é um sheet como no exemplo abaixo. Em várias poses, várias iluminações, várias expressões de emoção, com índice de cor, tudo gerado do absoluto zero pela "I.A.". Em meia hora de processamento na minha RTX 4090.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbFlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--78adbc4db15e4ef974ad819db0de5781d167c777/NameYourCharacterHere_06_CHARACTER_SHEET_2-1_00001_.png?disposition=attachment&amp;amp;locale=en" alt="NPC Design Sheet" /&gt;&lt;/p&gt;
&lt;p&gt;Imagine que você é estag em estúdio de jogos. Seu trabalho: criar 100 personagens de fundo (NPCs). No fim tem que gerar 100 desses. Mas só porque é personagem secundário, não pode fazer de qualquer jeito, tem que ser todos coerentes, em todas as poses. Imagina quantos dias você iria levar. E se desse pra automatizar? É pra esse tipo de cenário que I.A. é interessante. Gaste mais tempo nos personagens principais, faça manualmente, mas não perca tempo nos secundários e não baixe a qualidade final de tudo.&lt;/p&gt;

&lt;p&gt;Eu não fiz esse workflow, quem fez foi o Mickmumpitz. Assista o video inteiro dele pra saber como funciona esse workflow, não vou repetir o que ele já explica:&lt;/p&gt;

&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/grtmiWbmvv0?si=Rf9vX8oQ77fOyqhU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;


&lt;p&gt;Também não vou compartilhar o workflow dele, porque ele tem um Patreon pago (é super barato, só USD 10 por mês), que te dá acesso a baixar o pacote de workflow inteiro dele &lt;a href="https://www.patreon.com/posts/free-workflows-120405048"&gt;neste link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mas só isso não adianta, os requerimentos que você precisa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uma máquina parruda com pelo menos uma RTX 3090 com 24GB de VRAM. Importante aqui é VRAM, os modelos usados fácil ultrapassam uso de 20GB. CPU é menos importante, eu uso uma 7950X3D mas uma 7800 já daria conta.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbEFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b6c132ae85f3bc0c724c808acfb35976446c43be/Screenshot%20From%202025-04-23%2014-51-45.png?disposition=attachment&amp;amp;locale=en" alt="GPU VRAM" /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caso não tenha máquina assim, dá mais trabalho mas você pode alugar uma na &lt;strong&gt;RUNPOD&lt;/strong&gt;. Eles tem várias configurações de GPU, mas você vai precisar saber configurar um pouco Linux ou escolher Windows e fazer o tutorial do Mickmumptiz tudo na mão (vai por mim, você vai sofrer se fizer assim).&lt;/li&gt;
&lt;li&gt;precisa ter pelo menos &lt;strong&gt;500GB&lt;/strong&gt; de espaço disponível e internet boa pra baixar. Na primeira vez que meu Docker rodar, ele baixa automaticamente todas as extensions de ComfyUI e todos os modelos pra tudo. Dá pra editar o arquivo &lt;code&gt;models.conf&lt;/code&gt; antes e remover modelos que sabe que nunca vai usar. Mas só este workflow exige pelo menos uns 30GB de modelos, não tem como fugir disso.&lt;/li&gt;
&lt;li&gt;esse workflow é particularmente &lt;strong&gt;PESADO&lt;/strong&gt; porque são várias funcionalidades (que podemos desligar partes, pra customizar pro nosso fluxo de trabalho). Mas pra 1 personagem, o fluxo inteiro pode levar &lt;strong&gt;30 MINUTOS&lt;/strong&gt;. Mesmo numa RTX 4090 como a minha.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Se fez tudo certo até este ponto, tem ComfyUI rodando com todas as dependências já baixadas e comprou o Workflow. Quando carregar vai ter que consertar algumas coisas manualmente.&lt;/p&gt;

&lt;p&gt;Primeira coisa é que vai ver um erro em vários Nodes em vermelho, todos de &lt;strong&gt;Ultimate SD Upscale&lt;/strong&gt;, basta clicar com o botão direito do mouse em cada um e escolher "Fix Recreate" que vai consertar automaticamente:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbEVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--783cb8d591feba4c599185007c1673a4dac5f2a6/Screenshot%20From%202025-04-23%2015-00-06.png?disposition=attachment&amp;amp;locale=en" alt="Fix Upscale" /&gt;&lt;/p&gt;

&lt;p&gt;O ComfyUI exige que cada slot de output de um Node se ligue no slot de input de outro Node compatível, mas frequentemente os workflow ficam tão grandes que tem fiozinho conectando coisa espalhado pra tudo que é canto e fica bem difícil de saber o que liga no que. Literalmente fica &lt;strong&gt;macarrônico&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mas existe uma extension (que eu já deixei pré-instalado) chamado &lt;a href="https://github.com/chrisgoringe/cg-use-everywhere"&gt;cg-use-everywhere&lt;/a&gt;. Toda extension é um projetinho do GitHub, clonado no sub-diretório &lt;code&gt;custom_nodes&lt;/code&gt;. Ele permite conectar a saída de Node nesse node &lt;strong&gt;Anything Elsewhere&lt;/strong&gt;, digamos a saída "clip" do Node "Load Clip". Agora, todo outro Node que tiver como input o campo "clip" e não tiver nada conectando explicitamente nele, vai usar esse Anything que foi criado. Funciona super bem.&lt;/p&gt;

&lt;p&gt;Só nesse workflow do Mick que, sei lá porque, todos os Anywheres estão quebrados. Mas é fácil consertar. Basta procurar esses Nodes, apagar, criar novos e reconectar. Tem vários logo no começo, anote quem liga em cada um, apague e crie um novo pra reconectar:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbElCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8f0aa5cde5dc8f0eeaee4b6426bfbc17d1823cd5/Screenshot%20From%202025-04-23%2015-05-03.png?disposition=attachment&amp;amp;locale=en" alt="anywhere initial" /&gt;&lt;/p&gt;

&lt;p&gt;Tem também alguns no grupo 02 de Upscale:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbE1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--90e9a128d29742db4383b69a2167e30af8da75a6/Screenshot%20From%202025-04-23%2015-06-15.png?disposition=attachment&amp;amp;locale=en" alt="anywhere upscale" /&gt;&lt;/p&gt;

&lt;p&gt;Nodes pequenos, só com o título, estão COLAPSADOS. Só clicar com o botão direito e escolher EXPAND pra ver os detalhes. É outra forma de organizar a bagunça.&lt;/p&gt;

&lt;p&gt;Por último, tem que rechecar o nome correto dos modelos em todos os Nodes de "Load" alguma coisa logo no começo do workflow (ainda bem que ele organizou esses nodes tudo junto):&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbFFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8746d4fef1ba73b6d5079183931c8ee35e9fd5a2/Screenshot%20From%202025-04-23%2015-09-55.png?disposition=attachment&amp;amp;locale=en" alt="model path" /&gt;&lt;/p&gt;

&lt;p&gt;No workflow dele, o Node vem procurando por "FLUX/flux1-dev-fp8.safetensors" mas aí você vai na lista (que puxa o que está mesmo no diretório "models/checkpoints" na sua máquina) e escolhe o que tem o mesmo nome. Isso acontece frequentemente porque diferentes pessoas organizam sub-diretórios diferentes pros modelos. Mas depois de configurar é só salvar e pronto. Tem que fazer isso pro checkpoint, controlnet, ic-light, etc.&lt;/p&gt;

&lt;p&gt;Os erros do ComfyUI não são amigáveis. São stacktraces de Python, que não são amigáveis porque ele mostra que linha do código deu pau, mas não mostra o que tinha nas variáveis passadas pra função que deu pau. Daí não dá pra saber onde foi. Mas normalmente na interface web, o Node fica em vermelho, daí você checa e normalmente é porque esqueceu de baixar o modelo ou escolher o certo da lista.&lt;/p&gt;

&lt;p&gt;Última coisa nesse workflow é baixar a image de OpenPose da página de Patreon dele, que linkei no começo do post, e carregar:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbFVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--dbf01db0d2db08c346cadfedc6c0d1b8ef03edf3/Screenshot%20From%202025-04-23%2015-13-39.png?disposition=attachment&amp;amp;locale=en" alt="openpose image" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OpenPose&lt;/strong&gt; é um formato de arquivo padrão que descreve poses, formato aberto que pode ser editar em várias ferramentas. E tem diversos modelos pré-treinados pra extrair poses nesse formato a partir de imagens, então nem precisamos editar na mão. Podemos baixar várias poses pré-prontas e abertas neste site:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openposes.com/"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--bf78df4762d6538f1338190af150c796b8b2a79b/Screenshot%20From%202025-04-23%2015-40-11.png?disposition=attachment&amp;amp;locale=en" alt="Poses Sites" /&gt;&lt;/a&gt;
Vou repetir várias vezes. mas &lt;strong&gt;assistam o video tutorial do Mick&lt;/strong&gt; onde ele explica como mandar gerar personagens do zero ou usar &lt;strong&gt;suas próprias fotos&lt;/strong&gt; com suas próprias poses pra continuar o fluxo a partir disso. O workflow dele, depois que roda, tem esta cara (com zoom lá embaixo):&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b326ad3ebaf1ce79586926e6564cd6416073b0f1/Screenshot%20From%202025-04-23%2015-43-29.png?disposition=attachment&amp;amp;locale=en" alt="complete workflow" /&gt;&lt;/p&gt;

&lt;p&gt;Na primeira parte, tem a configuração e checagem manual dos modelos. Só tem que arrumar uma vez e salvar. Depois sempre vai funcionar igual. Nesse primeiro menu também dá pra ligar e desligar alguns dos grupos de Nodes, por exemplo, pra pular a primeira etapa de gerar personagem do zero e poder usar suas próprias fotos:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbGtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7cf98a7420ae7106ee172bfd3a643c054bb88058/Screenshot%20From%202025-04-23%2015-45-07.png?disposition=attachment&amp;amp;locale=en" alt="menu and setup" /&gt;&lt;/p&gt;

&lt;p&gt;Se optar por gerar personagem do zero, tem em verde uma caixa de prompt pra digitar como quer que seja sua personagem, em que estilo. Prompts de Texto são decodificados por modelos de &lt;strong&gt;CLIP&lt;/strong&gt; (Contrastive Language Image Pre-Training), que são específicos pra determinadas categorias de UNETs (como SDXL pra Stable Diffusion ou o melhor e mais popular FLUX agora). Parte do aprendizado é começar a aprender quais sub-modelos (clips, clip_vision, vaes, loras, etc) são compatíveis com quais unets (unet, checkpoints, diffusion_models). Não pode misturar qualquer coisa. Grosseiramente, é meio que se você escolhe fazer um texto em alemão, todos os plugins tem que ser em alemão.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbG9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--6d48d9c1683066365d1c94f7230a5100114204d9/Screenshot%20From%202025-04-23%2015-46-37.png?disposition=attachment&amp;amp;locale=en" alt="Character Generation" /&gt;&lt;/p&gt;

&lt;p&gt;Não vou mostrar o workflow completo porque nem cabe, mas dá pra ver em verde os prompts, no meio onde escolhemos que pode queremos (tá no Patreon dele), e em cima a image gerada pelo modelo &lt;strong&gt;flux1-dev-fp8&lt;/strong&gt;, um dos melhores e mais populares por ter excelente qualidade e não ser tão pesado (fp8 em vez de fp32, por exemplo).&lt;/p&gt;

&lt;p&gt;Isso é uma das vantagens de usar ferramentas fora dos Dalle ou Midjourney da vida: podemos controlar todos os aspectos da geração da imagem não só com prompts de texto, mas com prompts de imagens, como o OpenPose.&lt;/p&gt;

&lt;p&gt;Outro detalhe. Expliquei no meu post anterior sobre KSampler ou Samplers em geral, que são o motor que faz todo o trabalho de juntar todos os modelos, imagens, prompts, controlnet, vaes e produz a imagem final. Os parâmetros do KSampler como steps, cfg, denoise, scheduler e mais &lt;strong&gt;DEPENDEM&lt;/strong&gt; do modelo escolhido. Tem modelos bem documentados, tem modelos mal documentados.&lt;/p&gt;

&lt;p&gt;Por exemplo, digamos que você leu numa thread de Reddit sobre esse outro modelo mais otimizado chamado &lt;a href="https://civitai.com/models/215418?modelVersionId=273102"&gt;"TurboVisionXL"&lt;/a&gt;, que está disponível no site Civit.ai:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbHNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--09246cc7850e3e356c76754ffb2213172ce235ff/Screenshot%20From%202025-04-23%2015-53-05.png?disposition=attachment&amp;amp;locale=en" alt="TurboVisionXL" /&gt;&lt;/p&gt;

&lt;p&gt;Realmente é um bom modelo mas e agora? O que eu configuro no KSampler pra ele? Felizmente se scrolar a página (sempre leia a documentação original!!) vai encontrar estas recomendações:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbHdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--b7db385110b1de9f6e148d1b82654e1902bbb512/Screenshot%20From%202025-04-23%2015-54-52.png?disposition=attachment&amp;amp;locale=en" alt="TurboVisionXL documentation" /&gt;&lt;/p&gt;

&lt;p&gt;Viu só? Ele explica o que configurar e quais são limitações conhecidas. Mais embaixo vai ter comentários de outras pessoas que podem dar mais dicas ou compartilhar erros que você já tenha visto. É uma enorme comunidade, precisa aprender a participar dela.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbDBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7676682261655d50e2a469fde259ea0b80808037/Screenshot%20From%202025-04-23%2015-56-19.png?disposition=attachment&amp;amp;locale=en" alt="MultiView" /&gt;&lt;/p&gt;

&lt;p&gt;Mas voltando da tangente, no segundo grupo de nodes do Mick, ele pega a primeira imagem em "Pose T" e passa por um processo de "Multi View" usando Diffuser MV que faz isso automaticamente. É a mesma pose, mas como se a câmera desse um 360 graus ao redor dela, pegando ângulos diferentes e, principalmente, gerando novas imagens coerentes com a original (veja como o modelo é usado até pra inventar as costas da personagem).&lt;/p&gt;

&lt;p&gt;Nesse passo também ele extrai só o rosto da primeira imagem pra fazer outros tratamentos em paralelo. Reconhecimento e extração de rostos é super comum. Todo sistema de segurança usa, Google Photos usa pra organizar álbuns dinâmicos. E vale você aprender pra usar em seus produtos. É um processo razoavelmente leve e rápido hoje em dia e tem vários algoritmos e modelos pré-prontos pra usar. Nesse caso acho que nem usa I.A. propriamente dito, é um algoritmo mesmo.&lt;/p&gt;

&lt;p&gt;Outro detalhe: modelos de geração de imagens normalmente trabalham em &lt;strong&gt;BAIXA RESOLUÇÃO&lt;/strong&gt;. Normalmente abaixo de 1024x1024. Não espere gerar imagens 4K logo de cara. Mas não tem problema, porque a solução é primeiro gerar uma image de baixa resolução e depois passar por outro modelo de I.A. especializado em &lt;strong&gt;UPSCALING&lt;/strong&gt; como o RealESRGAN que já falei no meu outro post sobre &lt;a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"&gt;upscaling de anime&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Diferente do que faz um Photoshop ou qualquer editor de imagens, se tentar aumentar uma imagem, ele só vai pegar um pixel e tentar duplicar, quadruplicar os pixels ao redor. Mas isso costuma só deixar a imagem maior mas borrada (blurred), porque ele não tem como saber os detalhes, e nem vai tentar "inventar" detalhes.&lt;/p&gt;

&lt;p&gt;Pra "inventar" detalhes, precisa de um modelo pré-treinado que aprendeu o que desenhar. É como funciona também o DLSS 3 da NVIDIA pra games, onde o jogo renderiza em baixa resolução, como 720p e o DLSS, em tempo real, redesenha a nova tela em 4K. Não fica perfeito, mas jogando a 60fps, você raramente vai notar a diferença. Só vai perceber que ficou mais &lt;strong&gt;NÍTIDO&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por isso também, se você usar sua própria foto, ou foto de pessoas conhecidas, vai ver que a nova foto depois de upscaling ficou &lt;strong&gt;levemente diferente&lt;/strong&gt;, não é mais a mesma pessoa, porque o modelo &lt;strong&gt;REDESENHA&lt;/strong&gt; detalhes que não existiam. É um processo de interpretação e não de descobrir detalhes "escondidos" (isso não existe, só na ficção de CSI).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbDhCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--f87507cd33a316111d4fb4cbebf7147f020000da/Screenshot%20From%202025-04-23%2016-05-12.png?disposition=attachment&amp;amp;locale=en" alt="Upscale 2" /&gt;&lt;/p&gt;

&lt;p&gt;O workflow do Mick vai um passo além e tem uma segunda passada de upscaling, focando em consertar somente o rosto om um Node de Face Detailer, feito especificamente pra consertar e melhorar rostos. E aqui entramos em território de cirurgia plástica e harmonização mesmo kkkk o rosto final vai ser diferente do original. Mas pra NPC e personagens secundários não importa, ninguém vai saber a diferença mesmo, basta não estar deformado, zarolho ou algo assim que salta à vista.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbUFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--66a72ffa48d26e30a77d14e2414ad227c51384ec/Screenshot%20From%202025-04-23%2016-07-04.png?disposition=attachment&amp;amp;locale=en" alt="Emotions" /&gt;&lt;/p&gt;

&lt;p&gt;Existem Nodes especializados em expressões, como o Expression Editor (PHM) que parte de uma única foto de rosto e consegue gerar novas fotos nas mais variadas emoções - que podemos configurar exatamente, numericamente, como queremos. Não vou mostrar, mas note que as imagens geradas estão em baixa qualidade, mas o próximo passo do workflow é justamente fazer upscaling delas também.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbUVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--212a0f8d41400af699a217275854af328d28655f/Screenshot%20From%202025-04-23%2016-08-58.png?disposition=attachment&amp;amp;locale=en" alt="Lighting" /&gt;&lt;/p&gt;

&lt;p&gt;Esse passo é interessante. Primeiro a parte de cima, com os vários cenários. Eles foram todos gerados via os prompts em verde abaixo deles, prompts que o Mick deixou mas podemos editar como quisermos OU trocar o node de geração e colocar de Load Image pra carregar nossas próprias imagens de fundo, de fotos de verdade.&lt;/p&gt;

&lt;p&gt;O objetivo desse passo é gerar os rostos sob diferentes configurações de luz. Muita gente pensa que "luz" em jogos é só "acender a luz vermelha" e todo mundo vai ficar vermelho automaticamente. Em jogos novos, com &lt;strong&gt;RAY TRACING&lt;/strong&gt; sim, por isso é SUPER PESADO. Mas em jogos feitos pra máquinas antigas, jogos indie, não dá pra exigir isso de todo mundo.&lt;/p&gt;

&lt;p&gt;Daí se usam "truques" pra falsificar coisas como luzes e sombra. É o processo de &lt;strong&gt;BAKING&lt;/strong&gt; onde literalmente "estampamos a luz na textura" dos objetos e personagens. Com a luz global ligada ou desligada, eles sempre vão estar "pintados" na luz colorida da cena. Pra mudar de cena, carregam novas texturas com outras luzes "baked". Pra programadores, pense em baking como &lt;strong&gt;Caching&lt;/strong&gt;. Pra que toda vez recalcular tudo dinamicamente, se a luz da cena nunca muda? Economize esse passo e já deixe "cacheado".&lt;/p&gt;

&lt;p&gt;Voltando, esse passo usa a tecnologia &lt;a href="https://github.com/lllyasviel/IC-Light"&gt;&lt;strong&gt;IC-Light&lt;/strong&gt;&lt;/a&gt; que é outro projeto open source que vale aprender, mesmo se não se interessa por I.A. Ele consegue manipular a luz de qualquer imagem ou foto. Você, como fotógrafo, pode mudar a luz caso tenha mudado de idéia, em pós-produção. Ferramentas da Adobe ou BlackMagic, acho que já incluem tecnologia similar, mas é bom saber que existe open source e dá pra integrar no seu produto.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbUlCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--71f2313ebfbb5aa74194c91eb348d3c1ddc75c1b/Screenshot%20From%202025-04-23%2016-13-21.png?disposition=attachment&amp;amp;locale=en" alt="final sheet" /&gt;&lt;/p&gt;

&lt;p&gt;Depois de gerar os rostos com diferentes luzes, obviamente tem outro passo de upscaling - que estou pulando de mostrar - e vai pro grupo de nodes final que junta tudo num "sheet", uma "folha do personagem". O desenvolvedor de jogos pode usar direto num jogo 2D (pense um visual novel game da vida), ou pode servir de base pra UV Mapping de texturas de um modelo 3D simples com Armature pra animar e já tem alguns rostos de emoções. Bom pra colocar no cenário de fundo, pedestres andando na rua, fazendo compras, sentados na praça ou algo assim.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbU1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--026c5f97df493c70e63c00125fd581be84304b47/Screenshot%20From%202025-04-23%2016-15-44.png?disposition=attachment&amp;amp;locale=en" alt="Loras" /&gt;&lt;/p&gt;

&lt;p&gt;Além disso, o tutorial do Mick descreve como, é possível criar &lt;strong&gt;LORAS&lt;/strong&gt; (Low-Rank Adaptation). Se quiser conseguir gerar personagens com estilo ou mesmo rostos específicos (sem intervenção "artística" da I.A.), você escolhe um modelo como FLUX ou SDXL e "em cima" dele adiciona um segundo sub-modelo compatível com informações sobre essa pessoa ou personagem.&lt;/p&gt;

&lt;p&gt;Pra isso você tira várias fotos limpas e bem iluminadas da pessoas, nos mais diferentes ângulos, nem precisa de muito, só uma dúzia é suficiente. Pra cada imagem cria prompts que descrevam bem, e é isso. Tem ferramentas pra gerar esses Loras localmente ou sites que fazem isso (assista o video).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbVFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--24ceac3b30b1c71b33c6f6132ff650ec22040c2c/Screenshot%20From%202025-04-23%2016-20-12.png?disposition=attachment&amp;amp;locale=en" alt="Creating Prompts" /&gt;&lt;/p&gt;

&lt;p&gt;Falando em prompts, em vários workflows existem passos pra descrever prompts detalhados de imagens geradas dinamicamente. Em vez de exigir que o usuário (você) faça isso manualmente, é possível usar um modelo de decoding, que decodifica imagens em prompts de texto. O modelo mais usado pra isso é o &lt;strong&gt;FLORENCE 2&lt;/strong&gt; da Microsoft.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBbVVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--dcace487a6b85613b1b7d66c42b18b2c8e41a9c0/Screenshot%20From%202025-04-23%2016-22-03.png?disposition=attachment&amp;amp;locale=en" alt="Florence 2" /&gt;&lt;/p&gt;

&lt;p&gt;Esse box de texto embaixo é read-only, você não precisa digitar manualmente. O Node de Florence 2 Run vai ler a imagem e gerar esse texto. Veja como ele descreveu a primeira imagem da mulher na pose de "T". Fazendo testes, pro seu caso, você determina se só isso é suficiente, se não for basta colocar um segundo Node de prompt e concatenar os dois antes de mandar pra outros nodes.&lt;/p&gt;

&lt;p&gt;Enfim, tem MUITA coisa pra explorar só nesse workflow. O Mick fez um excelente trabalho tanto no workflow quanto no video pra explicar e vale a pena pagar os 10 dólares pra contribuir. Existem dezenas de workflows assim disponíveis na Web, basta procurar. E meu setup está cada vez mais complexo. Pretendo ficar ajustando ele pra cada novo workflow interessante que esbarrar. Já tem vários pré-testados na pasta "workflows" do meu projeto pra você brincar.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6195</id>
    <published>2025-04-20T19:30:00-03:00</published>
    <updated>2025-04-23T18:10:42-03:00</updated>
    <link href="/2025/04/20/entendendo-o-basico-de-comfyui-pra-gerar-imagens-com-i-a" rel="alternate" type="text/html"/>
    <title>Entendendo o Básico de ComfyUI pra gerar imagens com I.A.</title>
    <content type="html">&lt;p&gt;No &lt;a href="https://www.akitaonrails.com/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda"&gt;post anterior&lt;/a&gt; expliquei meu projetinho de como subir ComfyUI pré-configurado e pré-carregado automaticamente usando Docker Compose. Assumindo que você já tem tudo de pé, agora é entender essa desgraça do ComfyUI um pouco.&lt;/p&gt;

&lt;p&gt;Eu também não sou nenhum especialista, mas achei legal explicar alguns conceitos que muitos ignoram.&lt;/p&gt;
&lt;h3&gt;Modelos Pré-Treinados&lt;/h3&gt;

&lt;p&gt;Todo GPT da vida, ou Stable Diffusion ou outros carregam um banco de dados vetorial na memória da GPU, na VRAM, pra processar. São modelos GRANDES, chamados de 7B, 70B, 100B, etc. "B" de "Bilhões de Parâmetros". Parâmetros não tem definição usável fora da matemática, são simples números. Esses números representam "alguma coisa" dentro milhões de dimensões dentro desse arquivão.&lt;/p&gt;

&lt;p&gt;Em um jeito BEM GROSSEIRO, eu penso como sendo artefatos de compressão. Você pega PETABYTES de livros, imagens, textos, posts, etc, quebra tudo em tokens e encontra "relacionamentos" de um token com vários tokens e vai "posicionando" no espaço, mas não em 3 dimensões, mas milhões de dimensões. Modelos de I.A. são "Vector Space Models" ou VSMs, mais ou menos como é o índice de um Elasticsearch se você já brincou com procura de texto com relevância, ou qualquer plugin de "Full Text Search" de um Postgres ou MS-SQL. São todos VSMs.&lt;/p&gt;

&lt;p&gt;Eu penso assim: é como se o processo de treinamento - a parte SUPER CARA, que leva MESES - pegasse toda informação que é passada e compilasse um novo "dicionário" pra uma nova "língua". Agora, quando você "fala" com um GPT, primeiro ele precisa transformar seu texto da sua língua pra essa "língua interna" dele, isso é o que chamamos de um &lt;strong&gt;"Embedding"&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Qualquer dado pode ser transformado. Áudio pode ser convertido em texto e de texto em embeddings. Videos podem ser desmonstados em séries de imagens com informação temporal. Imagens podem ser desconstruídas em elementos como luminância, crominância, etc. No fim, tudo pode ser desconstruído em estruturas de dados, não tem mágica.&lt;/p&gt;

&lt;p&gt;Modelos de Geração de Imagem são treinados diferente. Tem diversos focos, foco em nitidez de rostos, foco em exatidão de poses, foco em estilos diferentes como cartoon ou pintura, foco em categorias como animais ou objetos. Por isso tem dezenas de modelos por aí e diversos "loras".&lt;/p&gt;

&lt;p&gt;Pra gerar uma imagem a partir de um prompt, primeiro precisa decodificar seu prompt num embedding, pra isso servem coisas como um &lt;strong&gt;TEXT ENCODER&lt;/strong&gt; no ComfyUI.&lt;/p&gt;

&lt;p&gt;No caso específico de I.A. pra imagens, eu entendo que se usa um tal de modelo de "DIFUSÃO" tanto pra desestruturar a imagem original, quanto pra reconstruir uma nova imagem. Não é intuitivo, mas a reconstrução não começa numa tela branca, como seria pra um desenhista humano. Ele começa com uma imagem com barulho aleatório "noise" tipo isso aqui:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBamdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--3ba497f35c51f6b1fc5e24b5ab4b89e529e0787e/image-after-gaussian-noise-2.webp?disposition=attachment&amp;amp;locale=en" alt="noise" /&gt;&lt;/p&gt;

&lt;p&gt;O modelo começa desse barulho e via um tal processo de DIFUSÃO começa a redesenhar a imagem de trás pra frente, até sair do caos e chegar numa imagem.&lt;/p&gt;

&lt;p&gt;O que eu "ACHO", "CHUTO" que aconteça - sem ter lido paper nenhum, preguiça mesmo - é que tentamos influenciar esse barulho inicial. Por exemplo, extraindo funcionalidades da imagem como um mapa de profundidade, que se parece com isso:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBamtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--41741c872e9247ef7722afefaa663643586ddc16/ComfyUI_temp_tbpnv_00002_.png" alt="Mapa de Profundidade" /&gt;&lt;/p&gt;

&lt;p&gt;A foto original era assim:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBam9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8b92d0ce5e071557fa6f33e113328395fcd6ea18/68685020_1288138464699355_383731310440480768_o(1" alt="Original" /&gt;.jpg)&lt;/p&gt;

&lt;p&gt;Extrair um Mapa de Profundidade é um &lt;strong&gt;ALGORITMO&lt;/strong&gt; e não "I.A.". Qualquer Photoshop, Premiere da vida conseguem fazer isso com uma mão nas costas. Mas é pra explicar que tem muito mais informação numa imagem que só olhando você - que não é treinado - não sabe que existe.&lt;/p&gt;

&lt;p&gt;Outro tipo de ALGORITMO bem conhecido é o &lt;strong&gt;Canny Edge Detection&lt;/strong&gt; que faz um mapa de bordas, pra ficar mais fácil de saber onde alguma coisa começa e onde termina na imagem:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBanNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ddbf4511e9053afd240999f720df0862fc70ebe3/ComfyUI_temp_pyyqp_00002_.png" alt="Canny" /&gt;&lt;/p&gt;

&lt;p&gt;Então é mais ou menos assim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;assim como um texto é "convertido" numa &lt;strong&gt;Embedding&lt;/strong&gt;, vetores que capturam a "essência" do significado do texto, dependente da VSM do modelo pré-treinado, uma imagem é "convertida" internamente num &lt;strong&gt;LATENT&lt;/strong&gt;, que por ser imagem é um "espaço" (por isso se fala em Latent Space). Latents não temos como "visualizar", porque é uma representação interna que depende da U-NET, o modelo pré-treinado, a rede neural que aprende a "tirar barulho" (denoise) o Latent.&lt;/li&gt;
&lt;li&gt;Uma U-NET de imagens é grosseiramente similar a um modelo de texto como GPT ou LLaMA, o modelo pré-treinado, read-only, que vai ser usado pra produzir o resultado final.&lt;/li&gt;
&lt;li&gt;Uma resposta de GPT é uma continuação do contexto da conversa anterior. Ele não está "respondendo" você diretamente, está só "continuando" o texto da conversa e por acaso, pelo treinamento, o resultado "se parece" com uma resposta. No caso de imagens, começamos com uma imagem aleatoriamente barulhenta (noise) e num processo de tirar e adicionar barulho, ele vai "reorganizando" o Latent numa imagem que conseguimos reconhecer.&lt;/li&gt;
&lt;li&gt;Existe um "Scheduler" que decide quanto barulho é adicionado ou removido a cada passo no tempo. E existe o conceito de &lt;strong&gt;DIFFUSION MODEL&lt;/strong&gt; que é mais ou menos o "pipeline" de processamento.&lt;/li&gt;
&lt;li&gt;Podemos controlar o processo adicionando um prompt de texto, que é decodificado numa "embedding" usando um &lt;strong&gt;Text Decoder&lt;/strong&gt; normalmente tem nomes como "CLIP" ou propriamente um Transformer (como LLava). Esses embeddings vão pra U-NET usando camadas de atenção cruzada, direcionando o "denoising" em direção ao significado do prompt.&lt;/li&gt;
&lt;li&gt;Além disso temos &lt;strong&gt;CONTROLNET&lt;/strong&gt; que é uma segunda rede paralela, que adapta o processo pra condições extras. É uma cópia da arquitetura de U-NET (com pesos congelados) que recebem inputs extras, como o Canny Edges, ou Depth Maps, esqueletos de pose (via OpenPose), etc e aprende pequenos ajustes pra &lt;strong&gt;INJETAR&lt;/strong&gt; nos mapas principais da U-NET.&lt;/li&gt;
&lt;li&gt;Durante o processo de denoising, ativações da ControlNet são mescladas às da U-NET, forçando o processo a respeitar as condições que passamos.&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Então a U-NET é o "gerador", o Diffusion Scheduler orquestra o processo, text encoder e contronet, ambos influenciam a U-NET, e um VAE é usado no final pra converter um espaço latente em uma imagem propriamente dita que nós, humanos, conseguimos enxergar.&lt;/p&gt;

&lt;p&gt;BEM, BEM A GROSSO MODO. É mais ou menos como eu entendo o processo. E eu entendo que o ComfyUI expõe vários desses passos na forma de "NODES PROGRAMÁVEIS", que podemos parametrizar e ajustar pra influenciar como queremos que a imagem final saia.&lt;/p&gt;

&lt;p&gt;Além de parâmetros numéricos, esses Nodes também nos deixam escolher quais arquivos de modelos pré-treinados pra cada etapa queremos usar. É tudo um VSM binário, mas pra utilidades diferentes, por isso muitos tem a mesma extensão como eu expliquei, mas ficam em sub-diretórios diferentes. Pra cada Node conseguir achar só o que lhe interessa.&lt;/p&gt;

&lt;p&gt;Pegando o exemplo da minha foto, não é isso mas só pra ilustrar, me ajuda a pensar que aqueles mapas que eu mostrei influenciam a imagem "noisy" numa direção não-aleatória, como este que eu peguei numa etapa intermediária do workflow que estou rodando:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBandCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--cea8dade5a88f70cbd707ac8869305af63d977dc/ComfyUI_temp_tcpal_00002_.png?disposition=attachment&amp;amp;locale=en" alt="noise" /&gt;&lt;/p&gt;

&lt;p&gt;Se forçar seus olhos, consegue ver influência das imagens anteriores, e o modelo de Difusão vai fazendo o "denoising", sendo "atrapalhado" pela ControlNet. Como se você estivesse desenhando e tivesse uma pessoa do lado que de vez em quando apaga o que você fez, ou desenha por cima, e você vai se adaptando. Então no final vira um trabalho "colaborativo" entre os dois. Se fosse só a U-NET, ele ia gerar uma nova imagem bem diferente da original, mas a ControlNet é o "cliente" que fica toda hora de buzinando na orelha "não é assim que eu quero, muda", até chegar num resultado aceitável.&lt;/p&gt;

&lt;p&gt;Além disso, uma U-NET não é boa pra gerar imagens de altíssima resolução. O ideal é gerar imagens pequenas, 512 pixels, 1024 pixels, mas nada maior. Mas isso seria inútil pra usar profissionalmente. Pra isso servem &lt;strong&gt;UPSCALE MODELS&lt;/strong&gt;. Se leu meu artigo de ontem sobre &lt;a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"&gt;Video2K&lt;/a&gt; eu explico que escolho um modelo lá que é bom pra coerência temporal de video. Mas tinha outros modelos que são melhores pra "IMAGENS ESTÁTICAS", ou fotos. E podemos usar eles no ComfyUI.&lt;/p&gt;

&lt;p&gt;Depois que a nova imagem pequena é gerada pela U-NET, depois podemos usar ela como entrada pra um segundo passo de "UPSCALE" usando um modelo como RealERSGAN, e sair de uma imagem de 1024 pixels pra 2160 pixels, essencialmente 4K. É diferente de aumentar a imagem num Photoshop, onde fica tudo "soft" borrado. Como também é outro modelo de I.A. ele é treinado pra "redesenhar" em mais resolução. E podemos adicionar isso no workflow do ComfyUI também.&lt;/p&gt;

&lt;p&gt;Mais do que isso, o ComfyUI também suporta Nodes com modelos como o HunyuanVideo da Tencent, que apresentei no outro artigo de ontem, sobre &lt;a href="https://www.akitaonrails.com/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a"&gt;FramePack&lt;/a&gt;. Na verdade, podemos fazer a mesma coisa que o FramePack faz mas a partir de um workflow de ComfyUI: gerar um video a partir de uma imagem, usando o mesmo modelo. O FramePack é infinitamente mais fácil de usar, mas o ComfyUI é a faca suíça de I.A.: ela faz &lt;strong&gt;TUDO&lt;/strong&gt;, por isso também é infinitamente mais complicada e precisa se dedicar bastante pra aprender a usar.&lt;/p&gt;

&lt;p&gt;Entendendo até aqui, olhem o workflow que eu usei (está com zoom out, dá pra aumentar o zoom dinamicamente):&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBajBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--9699e17a45459b6ecb7e756982ca608376590a1f/Screenshot%20From%202025-04-20%2018-43-50.png?disposition=attachment&amp;amp;locale=en" alt="workflow" /&gt;&lt;/p&gt;

&lt;p&gt;Dá pra ver esses passos intermediários usando Nodes de &lt;strong&gt;PREVIEW&lt;/strong&gt; então é mais ou menos assim que podemos usar o ComfyUI pra fazer muita coisa avançada. Como falei, eu mesmo ainda não comecei nem a arranhar a superfície.&lt;/p&gt;

&lt;p&gt;Vamos resumir alguns dos conceitos usados no ComfyUI agora:&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;1. Checkpoints (Modelos Base)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
São os modelos principais do Stable Diffusion (ex: SD 1.5, SDXL, Flux). Contêm toda a arquitetura do modelo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Text Encoder (CLIP)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modelo de Difusão (U-Net)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;VAE (Decoder de imagens)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Para que servem&lt;/strong&gt;:&lt;br/&gt;
Gerar imagens a partir do zero. Cada checkpoint tem um "estilo" diferente (realista, anime, etc).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exemplos&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;flux.safetensors&lt;/code&gt;, &lt;code&gt;realisticVisionV60.safetensors&lt;/code&gt;, &lt;code&gt;anythingV5.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;2. VAE (Autoencoders Variacionais)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Componente que decodifica as imagens do espaço latente (formato que o modelo entende) para pixels visíveis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Melhorar detalhes e cores&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alguns checkpoints requerem VAEs específicos&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplos&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;vae-ft-mse-840000.safetensors&lt;/code&gt;, &lt;code&gt;animevae.pt&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;3. Text Encoders (Codificadores de Texto)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Modelos que convertem seu prompt de texto em representações numéricas que a IA entende.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tipos comuns&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLIP&lt;/strong&gt;: Padrão para a maioria dos modelos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OpenCLIP&lt;/strong&gt;: Versão alternativa usada no SDXL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;T5&lt;/strong&gt;: Modelos maiores para prompts complexos&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplos&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;clip_vision.safetensors&lt;/code&gt;, &lt;code&gt;openclip.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;4. CLIP Vision&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Modelos especializados em análise visual (ao contrário do CLIP normal que é texto→imagem).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Criar descrições de imagens existentes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ferramentas como "Image Prompt" (usam a imagem como input)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;clip_vision_g.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;5. Diffusion Models (Modelos de Difusão)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Parte específica da arquitetura (U-Net) responsável pelo processo de difusão (adição/remoção de "ruído").&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Treinamento de LoRAs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Workflows avançados de reutilização&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;unet.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;6. LoRAs (Adaptadores Leves)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Pequenos arquivos que modificam o comportamento do checkpoint principal sem substituí-lo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Adicionar estilos específicos (ex: anime)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Criar personagens consistentes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ajustar proporções corporais&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;epiNoiseOffset.safetensors&lt;/code&gt;, &lt;code&gt;add_detail.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;7. ControlNet&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Modelos que impõem controle sobre a composição da imagem (poses, profundidade, bordas).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tipos comuns&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;OpenPose (poses humanas)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Canny (bordas)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Depth (profundidade)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;control_v11p_sd15_openpose.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;8. Upscale Models (Modelos de Upscaling)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Modelos para aumentar resolução e detalhes de imagens geradas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tipos&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ESRGAN&lt;/strong&gt;: Para detalhes realistas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anime6B&lt;/strong&gt;: Especializado em arte anime&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;4x_NMKD-Superscale-SP_178000_G.pth&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;9. Embeddings (Textual Inversions)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são&lt;/strong&gt;:&lt;br/&gt;
Pequenos arquivos que adicionam conceitos novos ao vocabulário do modelo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Estilos artísticos específicos&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Objetos personalizados&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Correções de artefatos&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo&lt;/strong&gt;:&lt;br/&gt;
&lt;code&gt;bad_prompt.pt&lt;/code&gt;, &lt;code&gt;easynegative.safetensors&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;10. Style Models&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são:&lt;/strong&gt;&lt;br/&gt;
Modelos especializados em aplicar estilos visuais ou “filmes” de arte sobre a imagem gerada, sem alterar de fato a estrutura básica do conteúdo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Colorização temática&lt;/strong&gt; (ex: paleta de aquarela, sépia, croma‑key)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simular técnicas artísticas&lt;/strong&gt; (óleo, guache, traço de mangá)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Unificar estética&lt;/strong&gt; em séries de imagens (mesmo “clima” de cor e luz)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo de arquivos:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;flux1-redux-dev.safetensors&lt;/code&gt; (estilo “Flux Redux”)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;watercolor_v2.safetensors&lt;/code&gt; (pinceladas de aquarela)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;film_grain_vintage.safetensors&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;&lt;strong&gt;11. SAMs (Segment Anything Models)&lt;/strong&gt;&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;O que são:&lt;/strong&gt;&lt;br/&gt;
Redes de segmentação gerais capazes de isolar objetos ou regiões de interesse em uma imagem, produzindo máscaras binárias ou ponderadas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Para que servem:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Criar máscaras de objetos/pessoas&lt;/strong&gt; para aplicação seletiva de filtros ou composições&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Guiar ControlNets&lt;/strong&gt; (ex: aplicar difusão apenas dentro ou fora de uma região)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pré‑processar referências&lt;/strong&gt; (recortar fundo, separar camadas)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;Exemplo de arquivo:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sam_vit_b_01ec64.pth&lt;/code&gt; (SAM ViT‑B, modelo base de segmentação)

&lt;h3&gt;Resumo Visual&lt;/h3&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;pre&gt;&lt;code&gt;models/
├── checkpoints/        ➔ Modelos principais (Stable Diffusion)  
│     └── *.safetensors, *.ckpt  
├── diffusion_models/   ➔ Modelos de difusão alternativos  
│     └── *.safetensors, *.gguf  
├── loras/              ➔ Adaptadores de estilo (LoRAs)  
│     └── *.safetensors  
├── controlnet/         ➔ Controles de composição (ControlNets)  
│     └── *.pth, *.safetensors  
├── vae/                ➔ Decodificadores / Encoders do VAE  
│     └── *.pt, *.safetensors  
├── clip/               ➔ Encoders CLIP de texto  
│     └── *.safetensors, *.pt  
├── clip_vision/        ➔ Encoders CLIP de visão  
│     └── *.safetensors  
├── text_encoders/      ➔ Encoders de texto avançados  
│     └── *.safetensors, *.bin  
├── upscale_models/     ➔ Modelos de super‑resolução  
│     └── *.pth  
├── sams/               ➔ Modelos Segment Anything (SAM)  
│     └── *.pth, *.onnx  
├── style_models/       ➔ Modelos de estilo visual  
│     └── *.safetensors  
└── embeddings/         ➔ Embeddings / conceitos personalizados  
      └── *.pt, *.safetensors  
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tem que baixar as coisas certas nos lugares certos. E pra dificultar, como já falei, os arquivos costumam ter a mesma extensão. Então é muito fácil confundir e jogar um text encoder no diretório de clip ou vice-versa. De novo, por isso encorajo que atualize o arquivo &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"&gt;"models.conf"&lt;/a&gt; e coloque as URLs certas nas listas certas, assim fica bem mais organizado e o meu Docker vai puxar as coisas pros lugares certos quando inicializar.&lt;/p&gt;

&lt;p&gt;Ainda assim pode ter algumas outras dúvidas que eu também tive no começo. Por exemplo &lt;strong&gt;"Por que a U-NET é chamada de Checkpoint?"&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;Checkpoints ≈ Sistema Operacional Completo&lt;/strong&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Semelhança&lt;/strong&gt;: Assim como um commit no Git captura um estado específico do código, um checkpoint é um "snapshot" completo de um modelo treinado em um estágio específico.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Diferença&lt;/strong&gt;: Checkpoints são &lt;strong&gt;autocontidos&lt;/strong&gt; (não são "diferenças" como commits), cada um é um modelo completo que pode funcionar independentemente.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Melhor Metáfora&lt;/strong&gt;: Pense em checkpoints como diferentes versões do Windows (XP, 7, 10) - cada um é um sistema operacional completo, não incremental.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Então checkpoints meio que se assemelham, em conceito, a um "git commit" - se você for desenvolvedor. Ou snapshots de BTRFS ou ZFS - se você for de infra/devops.&lt;/p&gt;

&lt;h3&gt;&lt;strong&gt;LoRAs ≈ Plugins/Extensions&lt;/strong&gt;&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Semelhança&lt;/strong&gt;: Assim como um PR adiciona funcionalidades a um código base, um LoRA modifica o comportamento do modelo base.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Diferença&lt;/strong&gt;: LoRAs não são "merged" no checkpoint original, mas sim &lt;strong&gt;aplicados dinamicamente&lt;/strong&gt; durante a inferência.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Melhor Metáfora&lt;/strong&gt;: São como extensões do Chrome (ex: AdBlock) - adicionam comportamentos específicos sem modificar o navegador em si.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Pense assim: um modelo tem Checkpoints, como se fossem commits, assim dá pra "continuar" o treino por cima se precisar. Ao mesmo tempo, não precisamos mexer no modelo principal, podemos criar camadas separadas, as chamadas LoRas. Por exemplo, digamos que um modelo esteja com dificuldades de gerar o seu rosto, não importe que prompt use ou que Nodes configure.&lt;/p&gt;

&lt;p&gt;Em vez disso podemos usar o "dicionário" do modelo pré-treinado, como Flux ou SDXL e fazer um treinamento com nossas fotos pessoais e gerar um LoRa específico meu. Então podemos adicionar um Node de LoRa no workflow e isso vai resultar em imagens muito melhores com seu rosto em particular.&lt;/p&gt;

&lt;p&gt;Existem comunidades online inteiras dedicadas a isso. Um exemplo. No site &lt;a href="https://civitai.com/"&gt;Civit A.I.&lt;/a&gt; vamos encontrar diversos modelos baseados em modelos pré-existentes, como este &lt;a href="https://civitai.com/models/24149/mistoonanime"&gt;Mistoon&lt;/a&gt; que é baseado no SDXL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://civitai.com/models/24149/mistoonanime"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa0FCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--72ad3f3deeec1177498e61e27b1bf29d6670b784/Screenshot%20From%202025-04-20%2018-57-16.png?disposition=attachment&amp;amp;locale=en" alt="Mistoon Civit.ai" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;É um checkpoint. Podemos nos cadastrar no site e e baixar o arquivo &lt;code&gt;mistoonAnime_v10Noobai.safetensor&lt;/code&gt; e colocar no diretório &lt;code&gt;models/checkpoints&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBajRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--bb8e7fdea94e135bebd80d213297c3fcf1d17c0e/Screenshot%20From%202025-04-20%2018-55-16.png?disposition=attachment&amp;amp;locale=en" alt="Mistoon Checkpoint Node" /&gt;&lt;/p&gt;

&lt;p&gt;Agora, em todo workflow que tiver o Node "Load Checkpoint" temos a opção de escolher esse modelo. Mas digamos que esse modelo não esteja conseguindo gerar uma personagem com uniforme colegial como em vários animes. Vasculhando o site, encontramos esta &lt;strong&gt;LoRa&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://civitai.com/models/115968/mistoonanime-school-uniform"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBajhCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a9e7ea372006d3e6c9c720e1378cc266d5e1671b/Screenshot%20From%202025-04-20%2018-56-50.png?disposition=attachment&amp;amp;locale=en" alt="Mistoon LoRa" /&gt;&lt;/a&gt;
O modelo Mistoon é grande, uns 6.6GB mas essa LoRa - chamada &lt;code&gt;Mistoon_Anime\ school\ uniform.safetensor&lt;/code&gt; que devemos mover pra &lt;code&gt;models/loras&lt;/code&gt; tem menos de 290 MB, é muito menor porque é um treinamento específico só em imagens de uniformes colegiais.&lt;/p&gt;

&lt;p&gt;Agora podemos ligar o Node de "Load Checkpoint" a este outro Node de "Load LoRa" e escolher essa LoRa que sabemos que é compatível:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa0VCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--c3bc376147d4b22f98704d95e37ac06bbced8ca8/Screenshot%20From%202025-04-20%2019-00-10.png?disposition=attachment&amp;amp;locale=en" alt="LoRa Node" /&gt;&lt;/p&gt;

&lt;p&gt;LoRas tem que ser escolhidas pra usar com os modelos em que elas foram baseadas. Como falei antes, pense no modelo como uma "língua", se fizermos um LoRa em alemão, não adianta misturar com um modelo chinês, a grosso modo. Mas essa é uma forma de fazer fine-tuning pro modelo que você gosta entregar resultados que nenhum outro consegue.&lt;/p&gt;

&lt;p&gt;Um problema que eu tenho com o site Civit A.I. é que só dá pra fazer download quando está logado no site. Por isso não coloquei nenhum modelo ou lora deles no meu script de instalação automática. Infelizmente no caso deles precisa manualmente baixar os arquivos e mover pros diretórios certos, anotar em algum lugar, pra em outra máquina baixar e mover tudo de novo. É um saco que eu ainda não parei pra resolver. Se alguém quiser contribuir uma solução pra isso (sem expor cookies de login de ninguém, mandem Pull Request).&lt;/p&gt;

&lt;h3&gt;Um Exemplo Real&lt;/h3&gt;

&lt;p&gt;Seu dia a dia com Comfy, sendo um novato como eu vai ser mais ou menos assim: começa indo no Google e procurando "best workflow image to anime ComfyUI". Primeiros links costumam ser alguma thread de Reddit:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa0lCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--46a1fceb848f2324de9b650d48c46dd746ae7270/Screenshot%20From%202025-04-20%2019-09-26.png?disposition=attachment&amp;amp;locale=en" alt="Reddit" /&gt;&lt;/p&gt;

&lt;p&gt;Sub-Reddits como &lt;a href="https://www.reddit.com/r/comfyui"&gt;r/comfyui&lt;/a&gt; ou &lt;a href="https://www.reddit.com/r/StableDiffusion"&gt;r/StableDiffusion&lt;/a&gt; costumam ter novidades e workflows como esse. Daí vamos direto pros primeiros comentários:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa01CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8efef2dee78966ebe358a16d18fe694d8a6dda6b/Screenshot%20From%202025-04-20%2019-11-27.png?disposition=attachment&amp;amp;locale=en" alt="Reddit Comentários Links" /&gt;&lt;/p&gt;

&lt;p&gt;Ou isso, ou abrimos o workflow direto no ComfyUI e vasculhamos os Nodes, um a um:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa1FCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--cc779a27a9a88d366896b35f6b07017de9f60bb9/Screenshot%20From%202025-04-20%2019-12-35.png?disposition=attachment&amp;amp;locale=en" alt="Nodes Check" /&gt;&lt;/p&gt;

&lt;p&gt;Já temos talvez um problema: o Node veio pré-configurado pra carregar um LoRa chamado "SDXL_aidma-niji_jini.safetensors" mas o link no comentário do Reddit baixa um "SDXL_Niji_V6_DLC_LoRa_V4.safetensors". Procurando especificamente pelo anterior no Google, eu não acho. Mas parece "seguro" usar esse outro, pelo menos os nomes são muito parecidos, ambos são derivados de SDXL então teoricamente são compatíveis pelo menos. Esta á a página desse modelo no Civit A.I., então tem que baixar manualmente e mandar pro diretório &lt;code&gt;models/loras&lt;/code&gt; a mesma coisa pro Checkpoint AniToon no Node acima. Baixa manualmente e move pra &lt;code&gt;models/checkpoints&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://civitai.com/models/541460/sdxlnijiv6dlclora"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa1VCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0ea383d77e0a46589a0648b60af06977d897925b/Screenshot%20From%202025-04-20%2019-15-25.png?disposition=attachment&amp;amp;locale=en" alt="LoRa Niji" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note que nessa página ele explica que esse LoRa foi feito pra funcionar com o modelo SDXL_Niji_V6 e não com o AniToon como o Workflow que baixei sugere no Node de Load Checkpoint. Vale testar com ambos. De qualquer forma, já que estamos no site, vamos baixar o modelo sugerido também:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://civitai.com/models/120765"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa1lCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--1c9648f989e1e4938898c22e0579372abc02df83/Screenshot%20From%202025-04-20%2019-17-02.png?disposition=attachment&amp;amp;locale=en" alt="SDXL Niji" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Arquivo &lt;code&gt;sdxlNijiSeven_sdxlNijiSeven.safetensors&lt;/code&gt; de 6.5GB, move pra &lt;code&gt;models/checkpoints&lt;/code&gt;, tão entendendo o processo? Baixa, move pro lugar certo, recarrega o workflow no navegador e agora aparece, selecionamos:&lt;/p&gt;

&lt;p&gt;Aliás, acho que é intuitivo mas pra quem não entendeu se abriu a interface do ComfyUI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;botão esquerdo do mouse seleciona Nodes&lt;/li&gt;
&lt;li&gt;botão do meio arrasta a tela&lt;/li&gt;
&lt;li&gt;scroll faz zoom na interface&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Agora sim, vamos escolher esses dois pra testar:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa2NCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--bdb032cbdbae9df38f5eb72786c34f2c29fb9218/Screenshot%20From%202025-04-20%2019-20-35.png?disposition=attachment&amp;amp;locale=en" alt="Nodes Niji" /&gt;&lt;/p&gt;

&lt;p&gt;Aproveitando essa mesma imagem, note no canto esquerdo um Node de Florence2 que é uma LLM da Microsoft (e meu script de Docker já instala pra você). Não dá pra mostrar todos os Nodes relacionados mas estes são os principais de pra que isso serve:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa2dCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--0e795d84bb23f3ae63252d5c367483ac12c56df8/Screenshot%20From%202025-04-20%2019-22-49.png?disposition=attachment&amp;amp;locale=en" alt="Florence" /&gt;&lt;/p&gt;

&lt;p&gt;O ideal em geração de imagem é fazer o prompt mais longo e bem detalhado sobre a imagem original que você conseguir, mas a maioria das pessoas só escreve alguma porcaria como "pessoa sorrindo de pé" e acha que é suficiente. O modelo Florence2 é feito pra ler uma imagem e descrever ela em texto.&lt;/p&gt;

&lt;p&gt;Agora tem outro detalhe: muitos checkpoints ou Loras são treinados com alguma palavra-chave de ativação. Assim ela só influencia se você digitar essas palavras no seu prompt. Então esse workflow não precisa digitar prompt manualmente, o Florence2 vai fazer isso e toda vez o texto dele começa como "The image ... bla bla bla", daí temos um Node de "Text Find and Replace" que troca esse "The Image" pelas palavras chaves do Anitoon ou Niji: "aidma-niji, niji".&lt;/p&gt;

&lt;p&gt;Essas são as palavras certas? &lt;strong&gt;EU NÃO SEI&lt;/strong&gt; kkkkk&lt;/p&gt;

&lt;p&gt;O workflow já trouxe essas palavras, mas nas páginas do Civit A.I. - onde deveria estar documentado, não está!! Então estamos realmente &lt;strong&gt;CHUTANDO&lt;/strong&gt; que deve ser "niji" já que é o nome do modelo e do lora. Preste atenção na palavra-chave, é importante, senão você adiciona o lora e nada acontece de diferente e é porque faltou isso no prompt!!&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa2tCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a0e8f0add32bcd5fcaefcd6958ab9f2d47677ffe/Screenshot%20From%202025-04-20%2019-29-11.png?disposition=attachment&amp;amp;locale=en" alt="ControlNet" /&gt;&lt;/p&gt;

&lt;p&gt;Tem mais, tem que checar se os Nodes de coisas como VAE, ControlNet, Canny e tudo mais estão populados com arquivos que temos. Normalmente se usa a mesma meia dúzia e eu já pré-instalei no Docker os principais. Na imagem, veja que ControlNet é o &lt;code&gt;contronet-union-sdxl-1.0-promax.safetensors&lt;/code&gt;, compatível com SDXL e estamos usando Niji que é feito em cima de SDXL. Vamos chutando assim.&lt;/p&gt;

&lt;p&gt;Também notem que ele usa Zoe Depth Map pra tirar o Mapa de Profundidade e Canny pra tirar o Mapa de Bordas como no outro workflow. Dá pra configurar, nesse workflow note que o Mapa de Bordas pegou alguns artefatos esquisitos no fundo, dá pra ajustar pra tirar isso. Por isso que no exemplo anterior também a imagem final apareceu uma janela, o modelo achou que era uma janela! Mas por enquanto deixa assim mesmo.&lt;/p&gt;

&lt;p&gt;Agora podemos rodar:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa29CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ac68bcde904893dd00cd6d14b491aabff8c75528/Screenshot%20From%202025-04-20%2019-32-40.png?disposition=attachment&amp;amp;locale=en" alt="Run Comfy" /&gt;&lt;/p&gt;

&lt;p&gt;Tem um botão enorme "RUN" em azul lá embaixo. Se estiver tudo configurado certo, você vai ver que um trabalho foi colocado na fila. A interface é inteligente o suficiente pra deixar você trabalhar em outros workflow e só ir enfileirando trabalhos enquanto a GPU sua processando. Não precisa ficar esperando, só deixar vários enfileirados e ir dormir.&lt;/p&gt;

&lt;p&gt;Ao rodar, na interface os Nodes que estão processando neste momento vão ficar com uma borda verde, o que é bom pra você saber em que etapa do processo ele está. E se algum Node tiver erro - porque faltou arquivo, por exemplo - ele vai ficar com borda vermelha, daí precisa corrigir e rodar de novo.&lt;/p&gt;

&lt;p&gt;E no final, eis um dos resultados:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa3NCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--7ca410d7a634dbbe2a18b0ad8360010e44d59b72/Screenshot%20From%202025-04-20%2019-35-11.png?disposition=attachment&amp;amp;locale=en" alt="Resultado Niji" /&gt;&lt;/p&gt;

&lt;p&gt;Note como os mapas da controlnet garantiram a pose correta. O modelo deduziu aqueles artefatos de fundo do mapa de borda como iluminação de alguma janela, mas na foto original vemos que é tipo o tijolo de concreto da parede kkkk mas é isso, sem saber, ele chuta. E se rodar várias vezes, sempre vai dar um resultado bem diferente. Olha outro:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBa3dCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2baef1df7442d76d91df7c518c8021819304b72e/Screenshot%20From%202025-04-20%2019-37-04.png?disposition=attachment&amp;amp;locale=en" alt="Niji 2" /&gt;&lt;/p&gt;

&lt;p&gt;Nada a ver kkkk Mas podemos trocar o modelo SDXL-Niji pelo Anitoon. Vamos ver um dos resultados:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBazBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d7307b44f6bcf349c007b0c08edde44488a391d7/Screenshot%20From%202025-04-20%2019-38-03.png?disposition=attachment&amp;amp;locale=en" alt="Anitoon" /&gt;&lt;/p&gt;

&lt;p&gt;Trocando pro modelo waiNSFIllustrious:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBazRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a5825cde17f1b4b9ae1566e6ab6d847b534d78fa/Screenshot%20From%202025-04-20%2019-39-05.png?disposition=attachment&amp;amp;locale=en" alt="waiNSFIllustrious" /&gt;&lt;/p&gt;

&lt;p&gt;Esse ficou ainda mais nada a ver. Alguma coisa nos mapas está fazendo o modelo assumir que eu tenho cabelo cacheado, sei lá porque. Mas é isso: tem que ajustar os parâmetros. O resultado do workflow que mostrei no começo do artigo deu um resultado melhor, usando os mesmos modelos.&lt;/p&gt;

&lt;p&gt;De todos os Nodes, um dos mais importante é o "motor" do processo, o &lt;strong&gt;KSampler&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBazhCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--fff575758f89eac1e166ce7c92e3b61f3c24cb48/Screenshot%20From%202025-04-20%2019-40-58.png?disposition=attachment&amp;amp;locale=en" alt="KSampler" /&gt;&lt;/p&gt;

&lt;p&gt;Esse é o cara que comanda o show. Damos pra ele o modelo, o prompt, saídas de control-net e ele quem vai iterativamente (steps) fazer o "desnoise" do Latente. Note que sempre existe um fator aleatório ("seed"), quantidade de passos (25 a 50 é a média), "cfg" (classifier-free guidance) ou "guidance_scale", onde valores maiores puxam a imagem mais seu prompt positivo. Denoise é quando de noise o scheduler aplica (1.0 é noise completo, abaixo de 1.0 pode produzir resultados mais "artísticos", tem que testar).&lt;/p&gt;

&lt;p&gt;Sampler Name é um saco porque precisaria entender a matemática por trás, mas são os algoritmos de steps, por exemplo &lt;code&gt;euler&lt;/code&gt;, &lt;code&gt;ddim&lt;/code&gt;, &lt;code&gt;plms&lt;/code&gt; e cada um tem um trade-off entre velocidade e fidelidade. Na prática: teste um a um e veja os resultados. Scheduler tem &lt;code&gt;normal&lt;/code&gt;, &lt;code&gt;klms&lt;/code&gt;, &lt;code&gt;dpmsolver&lt;/code&gt;. Mesma coisa: teste um a um.&lt;/p&gt;

&lt;p&gt;Como podem ver, tem BASTANTE parâmetros que dá pra brincar. Mas o fluxo principal é mais ou menos esse. Daí tem que ir aprendendo Node a Node o que cada um pode fazer e baixar bastante workflow pré-pronto pra ver como o pessoal tem solucionado diversos problema, quais os modelos e loras mais populares pra cada tipo de aplicação. Enfim, o Reddit é seu amigo. Espero que tenha dado pra dar um Norte, eu também ainda estou aprendendo.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6194</id>
    <published>2025-04-20T17:30:00-03:00</published>
    <updated>2025-04-20T17:41:41-03:00</updated>
    <link href="/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda" rel="alternate" type="text/html"/>
    <title>Gerando Imagens com I.A - até estilo Ghibli &#128514; - com Docker e CUDA</title>
    <content type="html">&lt;p&gt;Este tema vai ser dividido em dois posts. Neste é só técnico de como fazer rodar, no outro vou explicar o que diabos é um ComfyUI e mais ou menos como usar.&lt;/p&gt;

&lt;p&gt;Mais de ano atrás, quando fiz meus últimos videos sobre I.A. e nos podcasts eu sempre falava que rodava tudo na minha máquina. Mas nunca detahei como. Então hoje consertei isso com vários posts de blog e projetinhos no GitHub com Dockerfiles pra você mesmo rodar na sua própria máquina, sem configurar nada difícil.&lt;/p&gt;

&lt;p&gt;Finalmente fiz um &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"&gt;Dockerfile&lt;/a&gt; pra ComfyUI, que é a melhor interface gráfica pra edição de workflows de geração de imagens. Se você já trabalhou com Davinci Resolve Fusion ou Blender Geometry Nodes da vida, workflows são basicamente configurações de nós pra processar suas imagens, áudio, video e ir passando por processamentos não só de I.A.. É como programar visualmente. Por isso é uma ferramenta muito mais complicada do que o FramePack que mostrei no post anterior. Não é só clicar.&lt;/p&gt;
&lt;p&gt;Recentemente todo mundo ficou histérico com as atualizações do ChatGPT e como ele &lt;em&gt;"OH MY GOD"&lt;/em&gt; consegue gerar imagens em estilo de anime da Ghibli e ficaram spammando o mundo com memes idiotas em estilo anime. Histeria online é um saco mesmo. Mas como eu falei, por mais impressionante que seja, é isso: um gerador de memes. Não dá pra usar profissionalmente.&lt;/p&gt;

&lt;p&gt;Cansei de tentar subir fotos minhas ou até fotos genéricas, de personagens fictícios, alguns até desenhos meus, e vira e mexe, aleatoriamente, o ChatGPT se recusa a gerar a imagem. Alegando privacidade, segurança, copyright e tudo mais que ele conseguir inventar de desculpa. Não dá pra confiar nisso pra trabalhar. Quem é profissional deveria ter controle completo sobre o processo e é isso que vou ensinar hoje.&lt;/p&gt;

&lt;p&gt;Pra começar, clone meu &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded"&gt;repositório no GitHub&lt;/a&gt; e siga as instruções do README ou faça isto:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded.git
cd ComfyUI-Docker-CUDA-preloaded
docker compose build
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sendo mais complexo, precisei colocar um Docker Compose desta vez. Então pra subir, depois do build finalizar, só fazer isso:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Agora vai demorar, porque por padrão ele vai baixar quase &lt;strong&gt;500GB&lt;/strong&gt; de modelos pré-treinados pra conseguir fazer quase tudo de imagens. Sim, pra brincar com I.A. localmente precisa ter infra. Ou se não, alugar máquina parruda com muito armazenamento numa RunPod da vida e rodar remoto. Tem como baixar menos modelos, vou explicar a seguir.&lt;/p&gt;

&lt;p&gt;No Docker Compose. Recomendo evitar a opção "-d" de subir como daemon, porque é bom reservar um terminal pra ficar vendo os outputs (você pode dar &lt;code&gt;docker compose logs --follow&lt;/code&gt;, se quiser também, mas é mais fácil direto).&lt;/p&gt;

&lt;p&gt;O ComfyUI é um programa mais complexo que a média, muito mais que um FramePack da vida. Ele tem suporte a instalar "extensions" (plugins, addons, chame como quiser). E na verdade ele sozinho não faz muita coisa, precisa instalar várias extensions pra começar a ficar realmente útil. Vira e mexe alguma dessas extensions não trata erros direito e estoura no console, algo como isso:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBakVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d73bdac23b4af02f8f14242942f257181181fa08/Screenshot%20From%202025-04-20%2017-04-48.png?disposition=attachment&amp;amp;locale=en" alt="Console Error" /&gt;&lt;/p&gt;

&lt;p&gt;É útil sempre ficar de olho no console pra descobrir se ele tentou carregar um arquivo que não existe. Daí dá pra pegar o nome do arquivo e buscar no Google, baixar e colocar no lugar certo. Ou se é uma extension muito bugada que é melhor tirar do diretório "custom_nodes", e assim por diante. Sem ver o console, não aparece na interface web e você vai ficar a ver navios.&lt;/p&gt;

&lt;p&gt;Muito dos modelos e extensions eu me baseei na configuraçãol &lt;strong&gt;Ultimate ComfyUI versão 3&lt;/strong&gt; recomendada pelo &lt;strong&gt;Aitrepreneur&lt;/strong&gt;, de novo. Assista &lt;a href="https://youtu.be/q5kpr84uyzc"&gt;este video dele&lt;/a&gt; pra ter todos os detalhes e tudo que é possível fazer. Ele tem os scripts pra Linux, Windows, e os workflow na versão paga do Patreon dele e, claro, não vou compartilhar os workflows porque não são meus. Em teoria, minha config deve ser capaz de fazer o que ele ensina.&lt;/p&gt;

&lt;p&gt;O workflow V3 do Aitrepreneur é muito ph0da. Mas eu não posso compartilhar porque é material fechado no Patreon dele, mas eis uma foto de tela do meu ComfyUI com tudo dele carregado:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.patreon.com/posts/ultimate-all-in-121355089"&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaklCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--2c231ae898e583c757b2bf9478d78483d3363c32/Screenshot%20From%202025-04-20%2017-09-33.png?disposition=attachment&amp;amp;locale=en" alt="Ultimate V3" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Mas a config em si, eu refiz inteira pra funcionar neste Docker. Dá pra carregar qualquer outro workflow aberto que achar num Reddit da vida.&lt;/p&gt;

&lt;p&gt;O que eu fiz foi o seguinte:&lt;/p&gt;

&lt;h2&gt;Models&lt;/h2&gt;

&lt;p&gt;No projeto tem que ter um diretório "models", se não tiver crie, porque o docker compose vai mapear pra dentro do container. Quando iniciar, ele vai rodar o arquivo &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/init_models.sh"&gt;init_models.sh&lt;/a&gt;. Esse script vai carregar um arquivo de configuração que você pode editar depois: &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"&gt;models.conf&lt;/a&gt;. Ele é enorme e um pedaço dele é assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;...
[CONTROLNET]
https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-canny-controlnet-v3.safetensors
https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-depth-controlnet-v3.safetensors
https://huggingface.co/brad-twinkl/controlnet-union-sdxl-1.0-promax/resolve/main/diffusion_pytorch_model.safetensors|controlnet-union-sdxl-1.0-promax.safetensors
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_canny.pth
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11f1p_sd15_depth.pth
https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_openpose.pth
https://huggingface.co/Aitrepreneur/FLX/resolve/main/Shakker-LabsFLUX1-dev-ControlNet-Union-Pro.safetensors

[STYLE_MODELS]
https://huggingface.co/Aitrepreneur/FLX/resolve/main/flux1-redux-dev.safetensors

[SAMS]
https://huggingface.co/Aitrepreneur/FLX/resolve/main/sam_vit_b_01ec64.pth
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Cada seção mapeia pra um sub-diretório, então "[SAMS]" vai mapear pra "models/sams" e baixar os binários nos lugares certos. Tem alguns especiais nas seções "[GIT_REPOS]", para os que podem ser clonados via git ou "[CUSTOM]" pra arquivos que precisa baixar depois dentro desses projetos clonados, por exemplo. Não sei ainda se dá pra simplificar, mas isso eu peguei na config do Aitrepreneur.&lt;/p&gt;

&lt;p&gt;O ideal é sempre manter essa lista organizada com modelos que acharem depois. Se quiserem contribuir também seria bacana. O motivo é que fica tudo espalhado em tudo que é site por aí, você baixa manualmente, daí move pro diretório dentro de "models". Amanhã, se reinstalar sua máquina, e pra lembrar todos os sites? São dezenas de modelos disponíveis.&lt;/p&gt;

&lt;p&gt;No outro post vou tentar resumir o que diabos são "checkpoints", "diffusion models", "loras", "controlnet" e tudo mais. Mas por enquanto, a extensão dos arquivos não diz muita coisa. Dizer ".pt" por exemplo só quer dizer que é um arquivo que o PyTorch consegue abrir, mas não diz se é um clip ou um text encoder, por exemplo. Extensão ".safetensors" mesma coisa, pode ser qualquer coisa, e o ComfyUI precisa que cada arquivo esteja no sub-diretório correto pra conseguir usar depois, não pode sair movendo arquivo aleatoriamente.&lt;/p&gt;

&lt;p&gt;O script "init_models.sh" roda toda vez que o Docker é reiniciado e é inteligente pra só baixar URLs que já não estão baixadas no diretório "models". Por isso sempre roda quando o container sobe, no entrypoint. Essa é uma das vantagens da minha config: ela é tem manutenção automática.&lt;/p&gt;

&lt;h3&gt;Extensions e Dependências&lt;/h3&gt;

&lt;p&gt;Eu fiz um segundo script chamado &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/init_extensions.sh"&gt;init_extensions.sh&lt;/a&gt;, que também roda no entrypoint do container e você não deve mexer, mas ele carrega um outro arquivo de configuração chamado &lt;a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/extensions.conf"&gt;extensions.conf&lt;/a&gt; e, esse sim, você pode mexer e adicionar novas extensions depois. Um pedaço dele é assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;[EXTENSIONS]
https://github.com/ltdrdata/ComfyUI-Manager.git
https://codeberg.org/Gourieff/comfyui-reactor-node.git
https://github.com/ssitu/ComfyUI_UltimateSDUpscale.git
https://github.com/kijai/ComfyUI-LivePortraitKJ.git
https://github.com/PowerHouseMan/ComfyUI-AdvancedLivePortrait.git
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Aqui eu declaro todas as extensions que adicionam novas funcionalidades em cima do ComfyUI, em particular novos &lt;strong&gt;NODES&lt;/strong&gt;, por isso ficam no diretório "custom_nodes", que é um volume externo ao Docker e por isso é persistente, ou seja, toda nova extension sobrevive restarts do container.&lt;/p&gt;

&lt;p&gt;Mas tem ainda um porém GRANDE: &lt;strong&gt;Dependências de Python&lt;/strong&gt;. Um pesadelo.&lt;/p&gt;

&lt;p&gt;A build da Imagem Docker faz um monte de &lt;code&gt;pip install&lt;/code&gt; e como estou usando Virtual ENV, vai tudo instalado em &lt;code&gt;/venv&lt;/code&gt; dentro do container, como deveria.&lt;/p&gt;

&lt;p&gt;Porém, as extensions que são instaladas fora da build - toda vez que o container reinicia - costumam ser código Python e também tem mais dependências. Extensions são projetos no GitHub e podem estar mudando neste exato momento. Se mudar e fizer um &lt;code&gt;git pull&lt;/code&gt; pra atualizar, precisa rodar o &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;. Mas se tiver dezenas de extensions, rodar &lt;code&gt;pip install&lt;/code&gt;, de cada um, em cada restart do Docker é uma demora grande pra não fazer quase nada. Como eu disse, pesadelo.&lt;/p&gt;

&lt;p&gt;Minha solução - pra me gabar um pouco - conserta boa parte dessa manutenção. Quando o script &lt;code&gt;init_extensions.sh&lt;/code&gt; executa, se uma extension da lista já estiver instalada, ele vai pular. Daí vai gravar no diretório &lt;code&gt;./custom_nodes/.last_commit/*.commit&lt;/code&gt; o último commit de cada extension. Quando o Docker reiniciar, ele vai dar um &lt;code&gt;git fetch&lt;/code&gt; e checar se o hash do último commit mudou.&lt;/p&gt;

&lt;p&gt;Só se mudou, ele vai rodar &lt;code&gt;pip install&lt;/code&gt; pra essa extension que ganhou atualização e pular todas as outras que não mudaram desde o último restart, economizando MUITO tempo e evitando que você precisa manualmente atualizar as coisas. Como estou usando VENV, vai ficar tudo persistido no volume "/venv". Sim, é um VOLUME, porque ela muda depois do build a cada atualização de extension.&lt;/p&gt;

&lt;p&gt;Dessa forma, tudo que tem na imagem vai ser copiada nesse volume, e tudo que as extensions instalarem por fora, também vai pra dentro desse volume. Toda vez que reiniciar, volta tudo como estava antes. Foi a solução que encontrei e até agora parece funcionar direitinho.&lt;/p&gt;

&lt;p&gt;Mas é só questão de tempo pra alguma extension mal feita instalar dependências que quebrem outras dependências, ou um caso real que eu passei: tinha uma extension que crasheia sem mensagem de erro se estiver usando Python 3.11, o Zoe Depth Map da extension &lt;code&gt;comfyui_controlnet_aux&lt;/code&gt;, um dos pacotes que parece que são populares.&lt;/p&gt;

&lt;p&gt;Eu não tinha me dado conta que comecei usando uma image base de Docker de ubuntu22 em vez de ubuntu24, eu estava com medo de usar algo novo demais e quebrar, mas acabei usando velha demais e quebrou do mesmo jeito. Gerenciar versionamento é um saco. Enfim, mudei o Dockerfile pra ubuntu24.&lt;/p&gt;

&lt;p&gt;Mas no volume externo do VENV agora sobrou um monte de dependências que se limitavam ao Python 3.11. Não tem problema, basta apagar o volume:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker compose down # garantir que está parado
docker volume rm comfyui-venv
rm ./custom_nodes/.last_commits/*.commit
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E pronto, o VENV vai reiniciar vazio, preencher com as dependências corretas de Python 3.12 da imagem e ao iniciar o container meu script &lt;code&gt;init_extensions.sh&lt;/code&gt; não vai achar o ".last_commits" e vai forçar um &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; de todas as extensions, agora com Python 3.12. Super limpo e confiável.&lt;/p&gt;

&lt;p&gt;Se mesmo assim tiver erro, aí pode ser bug numa extension em especial, só vá até o diretório dele em "./custom_nodes" e apague manualmente antes de reiniciar o container.&lt;/p&gt;

&lt;p&gt;Acho que isso é o menos complicado que dá pra fazer nesse caso. Esse docker não foi feito pra rodar sozinho num servidor, é pra rodar na sua própria máquina local, onde você está monitorando o output do terminal mesmo.&lt;/p&gt;

&lt;p&gt;Ah sim, se sair versão nova do ComfyUI aí tem que re-buildar a imagem e apagar o volume de VENV. A imagem é estática, não esqueçam. Dentro o Dockerfile faz um novo git clone e pega o commit mais novo. Daí vai instalar pacotes python mais novos, então precisa zerar o volume da VENV e fazer as extensions re-instalarem suas dependências depois também. É um enorme saco, mas é assim mesmo que funciona.&lt;/p&gt;

&lt;h3&gt;Conclusão&lt;/h3&gt;

&lt;p&gt;Feito tudo isso, com um simples &lt;code&gt;docker compose up&lt;/code&gt; consigo ter um ambiente de ComfyUI estável e isolado do resto do meu sistema, sem sujar nada com milhares de dependências duvidosas. Quando terminar só fazer &lt;code&gt;docker compose down&lt;/code&gt; ou "ctrl-c" no console dele e pronto, meu sistema continua limpo.&lt;/p&gt;

&lt;p&gt;Além disso, diretórios como "models" e seus 500GB de modelos ficam separados, dá pra fazer backup pra outro lugar se precisar. E se quiser subir em outra máquina, basta levar somente esses diretórios e subir Docker Compose lá e tem que subir igualzinho, zero-setup. Esse foi meu objetivo ao Dockerzar ComfyUI, FramePack, Video2K, ter tudo portátil, isolado e limpo pra subir em qualquer máquina depois sem precisar ficar rodando scripts duvidosos de instalação direto na minha máquina.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/AkitaOnRails/status/1914030815962411367"&gt;No meu X&lt;/a&gt; eu já mostrei meu ComfyUI funcionando. Carreguei um workflow qualquer que achei na Web e olha só:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBamNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--60ceae3b972db1c3a1d4fcc6a79d24996913eee9/Go__77_WcAASeBr.jpg?disposition=attachment&amp;amp;locale=en" alt="ComfyUI" /&gt;&lt;/p&gt;

&lt;p&gt;Nesse workflow eu subo uma imagem de referência e posso escrever um prompt de como quero transformar ela:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBallCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4d51b8ec90a5db09555ebff721da04c37aad1112/GpAAMX-X0AATd1j.png?disposition=attachment&amp;amp;locale=en" alt="Prompt" /&gt;&lt;/p&gt;

&lt;p&gt;Com ComfyUI eu tenho controle sobre todas as etapas do processo, por exemplo, esse workflow consegue separar coisas como Mapa de Profundidade e Contorno de Bordas da imagem original, pra usar de contexto antes de fazer a imagem final, garantindo um resultado muito mais preciso:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBalVCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8a42084f0f05a715da7dab2770ec36a11df6e354/GpAAfhNWwAAr_eb.jpg?disposition=attachment&amp;amp;locale=en" alt="Depth Map" /&gt;&lt;/p&gt;

&lt;p&gt;E eis um resultado que já consegui usando o modelo "waiNSFIllustrious" que esse workflow sugere, que é um modelo treinado pra gerar imagens estilo anime (tem dezenas, pra vários estilos diferentes).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBalFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--34e09db5b606c6414707e8c58c93ff15039cd744/GpAA4ByWoAAkcav.jpg?disposition=attachment&amp;amp;locale=en" alt="anime" /&gt;&lt;/p&gt;

&lt;p&gt;Mudando o modelo pra &lt;code&gt;Mistoon&lt;/code&gt;, baseado em Stable Diffusion (SDXL), eis outro resultado, com o mesmo prompt, no mesmo workflow:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBak1CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--367e105788479b952fb131e4618d7de6c87111a9/GpAJr-jXQAAQhE0.jpg?disposition=attachment&amp;amp;locale=en" alt="anime 2" /&gt;&lt;/p&gt;

&lt;p&gt;E posso ficar tunando dezenas de parâmetros dentro desse workflow, trocar vaes, text encodes, controlnets e muito mais até conseguir exatamente o resultado que eu quiser. Esse é o poder do ComfyUI e seus Custom Nodes e Workflows. É o que vou tentar explicar o básico no próximo post.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6193</id>
    <published>2025-04-19T20:00:00-03:00</published>
    <updated>2025-04-19T20:00:54-03:00</updated>
    <link href="/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a" rel="alternate" type="text/html"/>
    <title>Gerando Videos de até 2 min a partir de uma Foto com I.A.</title>
    <content type="html">&lt;p&gt;Fácil o melhor canal de tutoriais de ferramentas open source pra I.A. é o &lt;a href="https://www.youtube.com/watch?v=GywyMij88rY"&gt;Aitrepreneur&lt;/a&gt;. E nesse link ele apresenta uma nova ferramenta chamada &lt;a href="https://github.com/lllyasviel/FramePack"&gt;FramePack&lt;/a&gt;, que usa o famoso modelo &lt;a href="https://github.com/Tencent/HunyuanVideo"&gt;HunyuanVideo&lt;/a&gt; da Tencent pra pegar uma única imagem, uma foto, e conseguir gerar videos de excelente qualidade de até 2 minutos de duração. É realmente impressionante! Assistam o video, mas eis aqui embaixo um video gerado de 10 segundos de uma foto que eu tirei de uma das action figures da minha coleção.&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/link-animado.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/link-animado.mp4)
    &lt;/video&gt;
&lt;/div&gt;

&lt;p&gt;Já que estava no embalo de empacotar esse tipo de ferramenta em Docker, resolvi fazer mais um projetinho com um Dockerfile pra rodar no meu Manjaro Linux com RTX 4090. O bom do Aitrepreneur é que ele tem um Patreon que você pode assinar pra contribuir (é barato) e ele sempre sobe scripts pra rodar no &lt;a href="https://www.runpod.io/"&gt;RunPod&lt;/a&gt; caso você não tenha uma máquina parruda ou GPU, ou script .BAT pra rodar no seu Windows.&lt;/p&gt;

&lt;p&gt;Meu projetinho está &lt;a href="https://github.com/akitaonrails/FramePack-Docker-CUDA"&gt;neste GitHub&lt;/a&gt; e pra usar é muito simples:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
git clone https://github.com/akitaonrails/FramePack-Docker-CUDA.git
cd FramePack-Docker-CUDA
mkdir outputs
mkdir hf_download

# Build the image
docker build -t framepack-torch26-cu124:latest .

# Run mapping the directories outside:
docker run -it --rm --gpus all -p 7860:7860 \
  -v ./outputs:/app/outputs \
  -v ./hf_download:/app/hf_download \
  framepack-torch26-cu124:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Isso vai construir a imagem de Docker e rodar. E na primeira vez dá pra ver que ele vai baixar uma TONELADA de models, o HunyuanVideo, Flux e mais. Se prepara pra mais de 30GB. Mas é só na primeira vez porque eu mapeio o diretório de download pra fora, então se reiniciar o container ele já vai ter na próxima vez.&lt;/p&gt;

&lt;p&gt;Quando terminar, é só acessar &lt;strong&gt;http://localhost:7860&lt;/strong&gt; e vai ver esta interface super simples:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaTBCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--ef9d04d08f6aec03aeb39557b51251997073a872/Screenshot%20From%202025-04-19%2018-06-34.png?disposition=attachment&amp;amp;locale=en" alt="FramePack Web UI" /&gt;&lt;/p&gt;

&lt;p&gt;Os controles são muito simples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desabilite a opção "Use TeaCache" que deixa mais rápido mas aí você arrisca a ter aqueles defeitos de I.A. como mão com mais dedos e coisas assim.&lt;/li&gt;
&lt;li&gt;Total do Video pode ser até 2 minutos, mas só faça isso se realmente tiver paciência porque demora BASTANTE! Acho melhor começar mesmo testando com videos curtos de 5 a 10 segundos primeiro.&lt;/li&gt;
&lt;li&gt;Se sua GPU tiver pouca VRAM vai ser obrigado a mexer em "GPU Inference Preserved Memory (GB) (larger means slower)". No meu caso que tenho 24GB de VRAM deixei como está, mas olha só rodando como ele consome:&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaTRCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--d00ca1f1355e5af7bb250690726805a75c52e903/Screenshot%20From%202025-04-19%2017-52-44.png?disposition=attachment&amp;amp;locale=en" alt="btop" /&gt;&lt;/p&gt;

&lt;p&gt;Sim! 100% da GPU e quase 100% da VRAM, puxando mais de 350W da parede! (foda-se meio ambiente! kkkkk) A CPU até fica de boa, porque esse processo é feito pra realmente MASSACRAR a GPU. Se não tiver GPU boa, alugue uma máquina na RunPod como falei antes.&lt;/p&gt;

&lt;p&gt;O video de 10 segundos no começo do post é uma foto da uma das action figures que eu mais gosto na minha coleção, o Link de Breath of the Wild. E isso é uma coisa que me deixou empolgado. Esta é a foto original que serviu de base pro FramePack fazer o video:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaThCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--4f310812061da0185500b9844c4dc28f78eb1984/20250419_184724.jpg?disposition=attachment&amp;amp;locale=en" alt="Link Foto" /&gt;&lt;/p&gt;

&lt;p&gt;Fazer video de fotos de pessoas é sem graça e tedioso, mas agora que estou aprendendo a modelar 3D pra imprimir na minha Bambulab, imagina depois ainda conseguir fazer ele ficar animado?&lt;/p&gt;

&lt;p&gt;Pra animar no Blender e renderizar, também é bastante processamento e demora. Mas com isso eu consigo rapidamente prototipar uma animação e ver como fica, pra ver se vale a pena fazer uma versão melhor no Blender depois. Abre muitas possibildades de experimentação!&lt;/p&gt;

&lt;p&gt;Por mais impressionante que isso pareça, ele não faz &lt;strong&gt;QUALQUER COISA&lt;/strong&gt;, tive várias experimentações que deram errado e a movimentação que ele faz é bem limitada, afinal ele só tem uma foto pra usar de referência e não tem dá pra fazer movimentos bruscos demais, tem limites. Mesmo assim é bem divertido de brincar. O fato dele conseguir identificar mais que seres humanos é um grande "plus" na minha opinião, porque é onde eu me interessei mais.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AH SIM!&lt;/strong&gt; Lembram o &lt;a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"&gt;outro blog post&lt;/a&gt; que publiquei hoje sobre fazer &lt;strong&gt;UPSCALE PRA 4K&lt;/strong&gt;?? Dá pra usar aqui também, pegue um video que o FramePack gerou e você gostou muito e faça upscale pra 4K pra ter mais definição ainda! Aí fica em qualidade pra até usar em edição de videos.&lt;/p&gt;

&lt;p&gt;Videos de 2 minutos deve levar horas pra fazer. 30 segundos levou quase 1 hora. Melhor coisa é ficar em videos de 5 segundos pra brincar e experimentar, só isso já leva aí quase meia hora. Aqui vão mais alguns exemplos tirados de fotos de itens da minha coleção (aqueles que vocês viam no fundo dos meus videos). Divirtam-se!&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/ultraseven-animado.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/ultraseven-animado.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Este é um Ultraseven de mais de 30cm de altura que eu encontrei e trouxe direto de um sebo de Tóquio ano passado, um dos que eu mais gosto especialmente porque assisti muito no começo dos anos 80 quando eu era criança.&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/jiraya-animado.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/jiraya-animado.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Falando em nostalgia, esse é outro item da hora da minha coleção, um Jiraya super detalhado feito pela Iron Studios. E animado até que ficou bem da hora. O que acharam?&lt;/p&gt;

&lt;p&gt;Mas agora o meu &lt;strong&gt;FAVORITO&lt;/strong&gt;, quem me acompanha no &lt;a href="https://www.instagram.com/p/DIAe3pNOFgffFZ5J32fWlc-8Kmy3Lhap5Vm58U0/?img_index=1"&gt;Instagram&lt;/a&gt; viu que faz algumas semanas que venho me dedicando a melhorar minhas técnicas de 3D e também de desenho e eu fiz um conceito de Mandaloriam + Judge Dredd:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBakFCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a88d1ecfaee2f2bfb9849acee0830fdc9d6ee5f4/Illustration23%20(2" alt="Boba Dredd" /&gt;.png?disposition=attachment&amp;amp;locale=en)&lt;/p&gt;

&lt;p&gt;Depois vejam os outros desenhos no meu Instagram, mas enfim, eu precisava saber se o FramePack era capaz de animar também desenhos e SIM, ELE CONSEGUE!! OLHA QUE PH0DA!!! Esse me deixou impressionado, dá uma nova dimensão nas minhas coisas!&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/boba-dredd-animado_upscaled.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/boba-dredd-animado_upscaled.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Sim, tudo isso é bem pesado, mas a vantagem é que eu posso experimentar INFINITAMENTE, porque tudo roda local. Num produto comercial de alguém no "cloud", eu precisaria pagar alguma assinatura e ele ia me limitar, seria impossível ficar experimentando quando eu quisesse. Agora eu posso fazer tudo que eu quiser, quando eu quiser, como eu quiser, sem que ninguém nem nada possa encher meu s@co.&lt;/p&gt;

&lt;p&gt;Aliás, se puderem assistam esse clip do "Boba Dredd" em tela cheia num monitor grande. Notaram que tá bem nítido? É porque eu passei no Video2K e fiz upscale pra 4x a resolução que o FramePack me deu. Juntando essas ferramentas dá pra fazer coisas BEM interessantes, depois me falem se conseguiram usar e o que fizeram de legal!&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6192</id>
    <published>2025-04-19T15:30:00-03:00</published>
    <updated>2025-04-19T16:22:34-03:00</updated>
    <link href="/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a" rel="alternate" type="text/html"/>
    <title>Aumentando Resolução de Anime velho pra 4K com I.A.</title>
    <content type="html">&lt;p&gt;Mais de um ano atrás eu estava brincando com ErsGAN, redes adversariais generativas pra tarefa de fazer "upscaling" (aumentar resolução) de arquivos de animes velhos que eu tenho.&lt;/p&gt;

&lt;p&gt;Pra quem coleciona, o problema é que muito anime dos anos 90 pra trás nunca saíram e nem nunca vão sair em Blu-Ray (1080p) nem UHD (4K). Só os mais famosos recebem tratamento de "remaster" (pegar as fitas master originais e recapturar em mais resolução). Então muito anime velho está preso na era de DVD (480p) ou VHS (480i).&lt;/p&gt;
&lt;p&gt;Nem todo anime velho dá pra aumentar resolução, pode ficar "lavado" demais. Quem baixa torrent já deve ter visto upscalings mal feitos. Mas eu queria poder testar isso eu mesmo, na minha máquina local, com meus arquivos velhos. Eis um pequeno clip de exemplo:&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip1.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;É um pedaço do 1o episódio do lendário Rurouni Kenshin de 1996. Sim, hoje temos um remake, que estão refazendo do zero. Mas pra quem tem nostalgia pelo original, não tem substituição. A qualidade de DVD não está horrenda, dá pra assistir de boa ainda hoje assim mesmo. Mas fico curioso pra ver como fica em 4K.&lt;/p&gt;

&lt;p&gt;Não fiz nenhuma pesquisa longa, mas um ano atrás eu tinha esbarrado no &lt;a href="https://github.com/xinntao/Real-ESRGAN"&gt;&lt;strong&gt;Real ERSGAN&lt;/strong&gt;&lt;/a&gt;. Esse projeto está descontinuado faz uns 4 anos, então não sei o que é o mais moderno hoje. Mas os modelos pré-treinados deles ainda são úteis.&lt;/p&gt;

&lt;p&gt;Ainda tem projetos que usam esse modelo, o que eu conheço é o &lt;a href="https://github.com/k4yt3x/video2x"&gt;&lt;strong&gt;Video2K&lt;/strong&gt;&lt;/a&gt; Em Windows parece que tem até uma interface gráfica bonitinha, mas a vantagem pra Linux é que ele roda em Docker. Um exemplo seria assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run --rm --gpus all \
  -v "$PWD/videos_in":/input \
  -v "$PWD/videos_out":/output \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i /input/old_anime.mp4 -o /output/old_anime_4K.mkv \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Se tiver com sua NVIDIA configurada com o Container Toolkit, Cuda e tudo mais (pergunte ao ChatGPT), o Docker consegue mapear o dispositivo direto pra dentro do container. Isso facilita muito não deixar meu Linux sujo cheio de dependências e configuraçõe soltas por aí. Não faço mais nada desse tipo fora de containers.&lt;/p&gt;

&lt;p&gt;Mas eu esbarrei com um problema. Se você estiver usando GPU AMD, só assim já deve funcionar, porque essa imagem Docker na verdade usa Vulkan e não CUDA. No meu caso eu tenho uma GPU integrada AMD primária e uma NVIDIA RTX 4090 secundária. Eu tentei passar direto:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ docker run --gpus all --privileged \
  --device=/dev/nvidia0 \
  --device=/dev/nvidiactl \
  --device=/dev/nvidia-uvm \
  --device=/dev/nvidia-modeset \
  --runtime=nvidia \
  -e VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/nvidia_icd.json \
  -e VK_LAYER_PATH=/usr/share/vulkan/explicit_layer.d \
  -v "$HOME/Downloads/Video2K/videos_in":/input \
  -v "$HOME/Downloads/Video2K/videos_out":/output \
  --rm -it \
  ghcr.io/k4yt3x/video2x:6.4.0 \
  -i "/input/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57).mkv" \
  -o "/output/(B-A)Rurouni_Kenshin_-_01_(A5D3CB57)_upscaled.mkv" \
  -p realesrgan -s 4 --realesrgan-model realesr-animevideov3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Mas não vai de jeito nenhum. Se eu só disser "--gpus all" ele vai pegar a iGPU por padrão. Fazer o upscaling de um video acaba consumindo 100% da minha CPU, todas as 32 threads, mas minha GPU NVIDIA fica parada olhando sem fazer nada. Se eu tentar passar "/dev/dri/card0" ou "/dev/nvidia0", ele dá erro e não consegue começar.&lt;/p&gt;

&lt;p&gt;Então a partir daqui a solução é só pra quem tem NVIDIA:&lt;/p&gt;

&lt;h3&gt;Dockerfile CUDA&lt;/h3&gt;

&lt;p&gt;Desta vez resolvi não ser preguiçoso e organizar tudo no projetinho no Github. Clone direto de lá &lt;a href="https://github.com/akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA"&gt;Batch Anime Upscaler Video2K Docker CUDA&lt;/a&gt; Só fazer assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;git clone git@github.com:akitaonrails/Batch-Anime-Upscaler-Video2K-Docker-CUDA.git
cd Batch-Anime-Upscaler-Video2K-Docker-CUDA
mkdir input # coloque seus videos velhos aqui
mkdir output

docker build --build-arg HOST_UID=$(id -u) --build-arg HOST_GID=$(id -g) -t anime-upscaler:latest .
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Se nada der errado, isso vai construir uma imagem de Docker com tudo que você precisa, daí é colocar seus videos no subdiretório que falei, ou outro que quiser e configurar no comando pra rodar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; docker run --gpus all --rm \
  -v "./input":/input \
  -v "./output":/output \
  -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) \
  anime-upscaler:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E é só isso. Demora bastante. Com meu Ryzen 7950X3D mais RTX 4090, ele processa a uma taxa de uns 13 frames por segundo. Então menos da metade da velocidade de playback. Se um video tiver 20 min, vai levar mais de 40 minutos pra processar cada um. É bom reservar um bom tempo, e por isso também é bom só pra videos que você realmente queira assistir.&lt;/p&gt;

&lt;p&gt;O script original do Video2K suporta vários parâmetros, que eu deixei expostos como variáveis de ambiente no Dockerfile:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;MODEL="${MODEL:-realesr-animevideov3}"
SCALE="${SCALE:-4}"
TILE="${TILE:-0}"
DENOISE="${DENOISE:-1.0}"
NUM_PROC="${NUM_PROC:-1}"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Daí dá pra mudar mexendo assim:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run --gpus all --rm \
  -e DENOISE=0.5 \
  -v ... \
  anime-upscaler:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Como podem ver, por padrão usamos o modelo pré-treinado "realesr-animevideov3" que é menos detalhado que o modelo de 6 bilhões de parâmetro mas é bem mais estável entre frames, que é mais importante pra um video. A maioria dos outros modelos que ele suporta, como "RealESRGAN_x4plus" ou "ESRGAN_x4" servem pra fotos e imagens mas não pra video. Video exige coerência temporal, que é bem difícil.&lt;/p&gt;

&lt;p&gt;No máximo vale testar o modelo "realesr-general-x4v3" que dizem ser um pouco mais leve, caso sua máquina esteja capengando muito pra rodar o padrão. Mas aí vai ter que modificar o Dockerfile pra fazer download desse modelo e re-buildar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# === OPTIONAL: Preload additional Real-ESRGAN models ===
# Uncomment any models you want available in the container at build time

# Anime 6-block model (better for anime stills, line art)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus_anime_6B.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth

# Original RealESRGAN 4x general-purpose model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus.pth

# RealESRGAN 2x model (lighter upscale)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRGAN_x2plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRGAN_x2plus.pth

# ESRNet (non-GAN model, ultra-smooth)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/RealESRNet_x4plus.pth \
#     -O /opt/Real-ESRGAN/weights/RealESRNet_x4plus.pth

# General-purpose lightweight video-friendly model (with denoise control)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.3.0/realesr-general-x4v3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-general-x4v3.pth

# Anime video model (default in your setup)
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth \
#     -O /opt/Real-ESRGAN/weights/realesr-animevideov3.pth

# Original ESRGAN (2018) model
# RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/ESRGAN_x4.pth \
#     -O /opt/Real-ESRGAN/weights/ESRGAN_x4.pth
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Eu não testei todos, mas pra quem quiser, acho que esses são os links. Mude no Dockerfile, faça o build, mude a variável MODEL e teste nos seus videos.&lt;/p&gt;

&lt;p&gt;O que vale mexer são coisas como DENOISE, que está pra "1.0" que é máximo, mas isso pode deixar a imagem "esticada" demais. Vale testar valores menores, como "0.5". Varia de caso a caso, só testando.&lt;/p&gt;

&lt;p&gt;Outro fator é a escala, SCALE, que o default é "4x", que transforma 1080p em 4K ou 720p em 1440p. Mas às vezes não precisa subir tanto, e nem adianta porque a imagem original já tem muito pouco detalhe mesmo. Talvez só 2x seja suficiente. O script suporta 2x, 3x, 4x mas tem teto na resoluçãod e 4K, então não adianta tentar transformar video que já é 4K em 8K por exemplo.&lt;/p&gt;

&lt;p&gt;Minha RTX 4090 e meu Ryzen 9 são bem potentes. Processando só um video eu não vejo ele suando nem um pouco:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaXNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--56910f41a51dd75ebad025da6c2d1d35d2502c0e/Screenshot%20From%202025-04-19%2015-27-50.png?disposition=attachment&amp;amp;locale=en" alt="btop" /&gt;&lt;/p&gt;

&lt;p&gt;Claramente daria pra rodar mais videos em paralelo. Tem que tomar cuidado quando é desbalanceado. Se a CPU subir pra perto de 100% pra separar os frames, não adianta estar sobrando na GPU. Os dois tendo sobra, dá pra fazer o script subir mais de 1 processo na GPU e mandar frames em paralelo com a opção "-e NUM_PROC=2". Olha como agora ele já usa mais da GPU e a CPU ainda tá sobrando. Talvez 3 seja o máximo, mas tem que testar, não é constante o tempo todo.&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaXdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--1683ba1da0cadc1a489e4261e2d5971521af3790/Screenshot%20From%202025-04-19%2015-30-35.png?disposition=attachment&amp;amp;locale=en" alt="btop x2" /&gt;&lt;/p&gt;

&lt;p&gt;Isso pode diminuir o tempo total dramaticamente. Você pode escolher entre aumentar os processo na GPU pelo script ou ter dois diretórios separados de video e subir dois containers paralelos desse programa pra rodar ao mesmo tempo, o que funcionar melhor pra você.&lt;/p&gt;

&lt;h2&gt;Resultado Final&lt;/h2&gt;

&lt;p&gt;Vamos ver como ficou aquele primeiro clip com upscale 4x e denoise 1.0:&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip1_upscaled_jp.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Dá uma diferença ENORME, mas se ficou "BOM" vai ser BEM subjetivo. Eu particularmente não achava o original ruim de assistir. Talvez se mexer no DENOISE pra um pouco menos ou SCALE pra 2x em vez de 4x já fique bom, não sei.&lt;/p&gt;

&lt;p&gt;Pra comparar, eis outro clip na versão original antiga:&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_old_clip2.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;E de novo, como fica depois do upscale 4x:&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_clip2_upscaled_jp.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Pelo menos está funcionando como deveria, a partir daqui é fazer algum tuning. Se alguém tiver mais dicas ou conhecer modelos ou projetos mais novos de upscaling open source, mande nos comentários abaixo!&lt;/p&gt;

&lt;p&gt;Antes que alguém pergunte, porque não usar DLSS 3 da NVIDIA, que eles usam pra aumentar resolução de games? Resposta: porque eles não expõe essa funcionalidade como uma API que dá pra acessar, é só pros drivers proprietários deles em profile de jogo. Eles ligam e tunam caso a caso. Não tem API geral na lib CUDA.&lt;/p&gt;

&lt;h2&gt;Update pós-publicação&lt;/h2&gt;

&lt;p&gt;Resolvi testar o modelo RealERSGAN_x4plus_anime_6B, que é bem mais pesado que o realesr-animevideov3 que subi no repositório.&lt;/p&gt;

&lt;p&gt;Mude o Dockerfile e adicione isto depois do wget do outro modelo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;RUN wget -q https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth \
     -O /opt/Real-ESRGAN/weights/RealESRGAN_x4plus_anime_6B.pth
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Esse modelo não tem como rodar em paralelo nem na minha RTX 4090, que fica constantemente em 100%. Se antes ia a uns 13 frames por segundo, com esse vai só a 3 frames por segundo. É 4x mais pesado e somente pra GPUs realmente parrudas. Se não tiver algo assim, fique na "realesr-animevideov3" mesmo ou no mais leve ainda (menos qualidade) "realesr-general-x4v3". Esses modelos dependem muito da potência da GPU e principalmente, quantidade de VRAM.&lt;/p&gt;

&lt;p&gt;Pra rodar adicione "-e MODEL=RealESRGAN_x4plus_anime_6B" no comando "docker run".&lt;/p&gt;

&lt;p&gt;Veja como ficou o mesmo clip de antes usando este modelo:&lt;/p&gt;

&lt;div class="video-container"&gt;
    &lt;video controls&gt;
        &lt;source src="https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4"&gt;
        Your browser does not support the video tag. [Direct Link](https://akitaonrails-videos.s3.us-east-2.amazonaws.com/kenshin_upscaled_clip1_x4plus.mp4)
    &lt;/video&gt;
&lt;/div&gt;


&lt;p&gt;Pra ter gasto 4x mais recursos da minha máquina pra processar, o resultado final eu não achei que ficou tão diferente, e em algumas partes eu acho que ficou mais defeitos até. Então é isso, o resultado vai variar, tem que testar. Depende muito da qualidade do anime original, mas não é porque existe um modelo maior e teoricamente "melhor" que todo resultado vai ficar melhor.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
  <entry>
    <id>tag:www.akitaonrails.com,2008:Post/6191</id>
    <published>2025-04-19T01:40:00-03:00</published>
    <updated>2025-04-19T01:50:09-03:00</updated>
    <link href="/2025/04/19/colorindo-imagens-preto-e-branco-com-i-a" rel="alternate" type="text/html"/>
    <title>Colorindo Imagens Preto e Branco com I.A.</title>
    <content type="html">&lt;p&gt;Minha namorada me deu um desafio hoje: ela tinha fotos preto e branco antigas e queria saber se eu conseguia colorizar elas. Se sair procurando na Web esbarra em alguns sites pra isso, como esse: &lt;a href="https://palette.fm/"&gt;Palette&lt;/a&gt;. Mas é pago, não é barato e acho que ele não consegue uma coisa que ela queria: usar uma outra imagem colorida como referência pra tirar as cores em vez de tentar colorizar por chute do modelo.&lt;/p&gt;

&lt;p&gt;Saí fuçando GitHub e tem uma página &lt;a href="https://github.com/MarkMoHR/Awesome-Image-Colorization"&gt;Awesome Image Colorization&lt;/a&gt; com vários papers de pesquisa. Muito útil pra quem for pesquisador mas totalmente inútil pra mim que não vou fazer um do zero kkkk. Tinha links pra alguns projetos como ChromaGAN, mas que está descontinuado faz uns 5 anos. E vários outros projetos que eu vi foram descontinuados lá atrás mesmo, não sei porque tem tão pouco open source ainda atualizado.&lt;/p&gt;
&lt;p&gt;Mas felizmente esbarrei em um promissor: &lt;a href="https://github.com/piddnad/DDColor"&gt;DDColor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Bora clonar o repositório. E pra continuar, prefiro fazer tudo dentro de Docker. Ficar baixando dependência de python sempre zoa meu sistema e deixa um monte de lixo pra trás, então melhor coisa é isolar tudo. Outra coisa, precisa baixar os binários dos modelos pré-treinados. Só pra isso vou usar um pouco de Python por causa da lib "modelscope" que faz isso:&lt;/p&gt;

&lt;pre&gt;&lt;code class="bash"&gt;git clone https://github.com/piddnad/DDColor.git
cd DDColor

mkdir modelscope

# criar um novo venv dentro do projeto DDColor
python -m venv venv

venv/bin/pip3 install modelscope

venv/bin/python3 -c "from modelscope.hub.snapshot_download import snapshot_download; snapshot_download('damo/cv_ddcolor_image-colorization', cache_dir='./modelscope')"
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Boa prática de Python acho que é fazer tudo dentro de uma VENV né? Enfim, isso deve baixar o binário do modelo, que pesa quase 900MB. Não é grande.&lt;/p&gt;

&lt;p&gt;Agora precisamos de um Dockerfile:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# Use NVIDIA CUDA base image with cuDNN 8 and Python support
FROM nvidia/cuda:11.8.0-cudnn8-runtime-ubuntu22.04

# Install Python and system dependencies
RUN apt-get update &amp;amp;&amp;amp; apt-get install -y --no-install-recommends \
    python3 python3-pip git libgl1 libglib2.0-0 &amp;amp;&amp;amp; \
    apt-get clean &amp;amp;&amp;amp; rm -rf /var/lib/apt/lists/*

# Set working directory
WORKDIR /app

# Copy DDColor source code into the image
COPY . /app

# Install PyTorch (CUDA 11.8 compatible) and matching torchvision/torchaudio
RUN pip3 install torch==2.2.0 torchvision==0.17.0 torchaudio==2.2.0 \
    --index-url https://download.pytorch.org/whl/cu118

# Install Python dependencies, skipping dlib (not needed for inference)
RUN sed '/dlib/d' requirements.txt &amp;gt; temp-req.txt &amp;amp;&amp;amp; pip3 install -r temp-req.txt &amp;amp;&amp;amp; rm temp-req.txt

# Optional: install modelscope if you want to run snapshot_download manually inside the container
RUN pip3 install modelscope
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Preguiça de abrir um pull request pra isso, se alguém quiser, mande lá.&lt;/p&gt;

&lt;p&gt;Agora precisamos colocar uma imagem preto e branco em algum lugar:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;mkdir input
mkdir results
mv ~/Downloads/bw.jpg input/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Como as fotos da minha namorada são familiares e particulares, obviamente não vou compartilhar, então peguei uma aleatória qualquer no Google Images:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaWNCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--e3e7776c3d381bc6c1f2f9425c5d624eeeb1ec73/director-francis-ford-coppola-DXJA6X.jpg?disposition=attachment&amp;amp;locale=en" alt="Foto B&amp;amp;W" /&gt;&lt;/p&gt;

&lt;p&gt;Agora colocamos em "./input" e rodamos o Docker com este comando:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;docker run --rm --gpus all \
  -v "$PWD/input":/workspace/input_images:ro \
  -v "$PWD/results":/workspace/results:rw \
  -v "$PWD/modelscope":/app/modelscope:ro \
  ddcolor:latest \
  python3 infer.py \
    --model_path /app/modelscope/damo/cv_ddcolor_image-colorization/pytorch_model.pt \
    --input /workspace/input_images \
    --output /workspace/results
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Preste atenção no mapeamento de diretórios. E note a opção "--gpus" que só funciona no meu caso porque eu tenho uma RTX 4090 instalada aqui. Não tenho a mínima idéia se é a mesma coisa com AMD, mas como a imagem é baseada em cnDNN/CUDA, acho que não. Pergunte ao ChatGPT.&lt;/p&gt;

&lt;p&gt;Se estiver tudo certo e rodar com sucesso, vai levar só 1 segundo e ter este output no terminal:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;==========
== CUDA ==
==========

CUDA Version 11.8.0

Container image Copyright (c) 2016-2023, NVIDIA CORPORATION &amp;amp; AFFILIATES. All rights reserved.

This container image and its contents are governed by the NVIDIA Deep Learning Container License.
By pulling and using the container, you accept the terms and conditions of this license:
https://developer.nvidia.com/ngc/nvidia-deep-learning-container-license

A copy of this license is made available in this container at /NGC-DL-CONTAINER-LICENSE for your convenience.

Output path: /workspace/results
100%|██████████| 1/1 [00:00&amp;lt;00:00,  4.09it/s]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;E eis o resultado que aparece no diretório `./results":&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaWdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--fc30bf1dbea556b63f851ba574ad16401cd6350d/director-francis-ford-coppola-DXJA6X.jpg?disposition=attachment&amp;amp;locale=en" alt="Foto re-colorizada" /&gt;&lt;/p&gt;

&lt;p&gt;Sim, eu fiquei impressionado. Mesmo a imagem sendo da Web, com marca d'agua e tudo, ele fez tudo direitinho e funcionou!!&lt;/p&gt;

&lt;p&gt;Único problema é que ainda falta o problema de conseguir usar uma imagem de referência pra tentar pegar as cores. Esse é um problema mais difícil e eu perguntei ao ChatGPT que me respondeu isto:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sem suporte nativo para imagem de referência:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O modelo DDColor, tanto na versão oficial hospedada no ModelScope quanto no repositório GitHub &lt;code&gt;piddnad/DDColor&lt;/code&gt;, foi projetado apenas para colorização &lt;strong&gt;automática de imagens em preto e branco&lt;/strong&gt;, sem aceitar uma imagem de referência como entrada. A própria descrição oficial deixa claro que o modelo recebe &lt;strong&gt;uma única imagem P&amp;amp;B&lt;/strong&gt; e retorna uma imagem colorida.&lt;/p&gt;

&lt;p&gt;O método &lt;code&gt;forward&lt;/code&gt; do modelo espera &lt;strong&gt;apenas um tensor&lt;/strong&gt;, referente à imagem P&amp;amp;B convertida em RGB artificial (na verdade, a luminância expandida para 3 canais). Ou seja, &lt;strong&gt;não há lugar no modelo para passar uma segunda imagem como referência de cores&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Isso também fica evidente no &lt;code&gt;predict.py&lt;/code&gt; original da biblioteca Cog, que aceita apenas o parâmetro &lt;code&gt;image&lt;/code&gt; como entrada — sem suporte a &lt;code&gt;reference&lt;/code&gt;, &lt;code&gt;ref_image&lt;/code&gt; ou algo do tipo.&lt;/p&gt;

&lt;h3&gt;Como resolver o problema: duas abordagens possíveis&lt;/h3&gt;

&lt;p&gt;Como o DDColor não suporta nativamente uma imagem de referência para guiar as cores, temos duas alternativas:&lt;/p&gt;

&lt;h3&gt;&#128257; 1. Usar um modelo especializado em colorização com referência&lt;/h3&gt;

&lt;p&gt;Existem modelos desenvolvidos especificamente para isso, como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deep Exemplar-based Colorization&lt;/strong&gt; (Zhang et al., CVPR 2018)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ChromaGAN&lt;/strong&gt; (2019)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exemplar-Transformer Colorization&lt;/strong&gt; (Yin et al., ACM MM 2021)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;UniColor&lt;/strong&gt; (Huang et al., 2022)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Esses modelos aceitam &lt;strong&gt;duas imagens&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A imagem em P&amp;amp;B (entrada principal)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uma imagem colorida como &lt;strong&gt;referência de cores&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Esses modelos mapeiam similaridades semânticas (ex: “pele aqui deve ter a mesma cor de pele dali”) e fazem uma transferência &lt;strong&gt;baseada no conteúdo da imagem&lt;/strong&gt;, obtendo resultados muito mais detalhados e específicos. No entanto, isso exigiria:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Baixar pesos diferentes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Alterar completamente a arquitetura usada&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adaptar o pipeline e a inferência&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;&#127919; 2. Usar transferência global de cor após a saída do DDColor (nossa abordagem atual)&lt;/h3&gt;

&lt;p&gt;A solução prática e leve que usamos foi:&lt;br/&gt;
➡️ Rodar o DDColor normalmente para gerar a imagem colorida automaticamente&lt;br/&gt;
➡️ Depois, ajustar &lt;strong&gt;globalmente&lt;/strong&gt; as cores do resultado para ficarem mais parecidas com as da imagem de referência.&lt;/p&gt;

&lt;p&gt;Esse ajuste é feito com o algoritmo de &lt;strong&gt;Reinhard et al. (2001)&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Convertemos tanto a imagem de saída quanto a referência para o espaço de cor Lab.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Calculamos a &lt;strong&gt;média e o desvio padrão&lt;/strong&gt; dos canais &lt;code&gt;a&lt;/code&gt; e &lt;code&gt;b&lt;/code&gt; (os que representam cor).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ajustamos o resultado para ter &lt;strong&gt;a mesma distribuição estatística&lt;/strong&gt; de cor que a imagem de referência.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Isso tende a deixar a imagem com o mesmo “clima” ou tom geral (mais quente, mais frio, mais vibrante).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;Esse método é rápido, leve, e funciona &lt;strong&gt;sem alterar o modelo DDColor&lt;/strong&gt;.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2&gt;Continuando&lt;/h2&gt;

&lt;p&gt;Eu usei a modificação que ele sugeriu ao arquivo predict.py:&lt;/p&gt;

&lt;pre&gt;&lt;code class="python"&gt;import os
import cv2
import numpy as np
import torch
import torch.nn.functional as F
from cog import BasePredictor, Input, Path
from basicsr.archs.ddcolor_arch import DDColor

def color_transfer(reference_bgr: np.ndarray, target_bgr: np.ndarray) -&amp;gt; np.ndarray:
    """Transfer color palette of reference image to target image using Lab mean/std."""
    # Convert BGR images to Lab color space (float32 for precision)
    ref_lab = cv2.cvtColor(reference_bgr, cv2.COLOR_BGR2LAB).astype("float32")
    tgt_lab = cv2.cvtColor(target_bgr, cv2.COLOR_BGR2LAB).astype("float32")
    # Split channels
    L_ref, a_ref, b_ref = cv2.split(ref_lab)
    L_tgt, a_tgt, b_tgt = cv2.split(tgt_lab)
    # Compute mean and std for reference and target (a and b channels)
    a_ref_mean, a_ref_std = a_ref.mean(), a_ref.std()
    b_ref_mean, b_ref_std = b_ref.mean(), b_ref.std()
    a_tgt_mean, a_tgt_std = a_tgt.mean(), a_tgt.std()
    b_tgt_mean, b_tgt_std = b_tgt.mean(), b_tgt.std()
    # Subtract target means, scale by reference/target std, add reference means
    # (Avoid division by zero in case of zero std)
    if a_tgt_std &amp;gt; 1e-6:
        a_tgt = ((a_tgt - a_tgt_mean) * (a_ref_std / a_tgt_std)) + a_ref_mean
    else:
        a_tgt = a_tgt - a_tgt_mean + a_ref_mean
    if b_tgt_std &amp;gt; 1e-6:
        b_tgt = ((b_tgt - b_tgt_mean) * (b_ref_std / b_tgt_std)) + b_ref_mean
    else:
        b_tgt = b_tgt - b_tgt_mean + b_ref_mean
    # Clip values to valid Lab range [0,255] after transfer
    a_tgt = np.clip(a_tgt, 0, 255)
    b_tgt = np.clip(b_tgt, 0, 255)
    # Merge channels back (use original L from target to preserve brightness)
    merged_lab = cv2.merge([L_tgt, a_tgt, b_tgt]).astype("uint8")
    # Convert back to BGR color space
    result_bgr = cv2.cvtColor(merged_lab, cv2.COLOR_LAB2BGR)
    return result_bgr

class ImageColorizationPipeline:
    """Helper pipeline to load DDColor model and process images."""
    def __init__(self, model_path: str, input_size: int = 256, model_size: str = "large"):
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        # Choose encoder backbone based on model size
        encoder_name = "convnext-t" if model_size == "tiny" else "convnext-l"
        # Initialize DDColor model
        self.model = DDColor(
            encoder_name=encoder_name,
            decoder_name="MultiScaleColorDecoder",
            input_size=[input_size, input_size],
            num_output_channels=2,    # model predicts ab channels
            last_norm="Spectral",
            do_normalize=False,
            num_queries=100,
            num_scales=3,
            dec_layers=9,
        ).to(self.device)
        # Load weights
        state = torch.load(model_path, map_location="cpu")
        # Some checkpoints store weights under 'params' key
        self.model.load_state_dict(state.get("params", state), strict=False)
        self.model.eval()
        self.input_size = input_size  # store for processing

    @torch.no_grad()
    def process(self, img_bgr: np.ndarray) -&amp;gt; np.ndarray:
        """Colorize a BGR image (numpy array) using the loaded DDColor model."""
        # Preserve original resolution L channel
        orig_h, orig_w = img_bgr.shape[:2]
        # Convert to Lab and extract L channel at original size
        img_lab = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2LAB).astype("float32")
        orig_L = img_lab[:, :, 0:1]  # shape (H, W, 1)
        # Prepare grayscale input at model resolution
        # Resize input to model expected size
        inp = cv2.resize(img_bgr, (self.input_size, self.input_size))
        inp_lab = cv2.cvtColor(inp, cv2.COLOR_BGR2LAB).astype("float32")
        L = inp_lab[:, :, 0:1]
        # Create grayscale Lab image by zeroing AB channels
        gray_lab = np.concatenate([L, np.zeros_like(L), np.zeros_like(L)], axis=2)
        # Convert back to RGB (now it's a gray RGB image of size input_size)
        gray_rgb = cv2.cvtColor(gray_lab.astype("uint8"), cv2.COLOR_LAB2RGB)
        # Prepare tensor and run model
        tensor = torch.from_numpy(gray_rgb.transpose(2, 0, 1)).float().unsqueeze(0).to(self.device)
        out_ab = self.model(tensor)  # output is (1,2,H_out,W_out) in Lab AB
        out_ab = out_ab.cpu().float()
        # Resize output AB to original image size
        out_ab_resized = F.interpolate(out_ab, size=(orig_h, orig_w), mode="bilinear", align_corners=False)
        out_ab_resized = out_ab_resized[0].numpy().transpose(1, 2, 0)  # (H, W, 2)
        # Combine original L and predicted AB, then convert to BGR
        out_lab = np.concatenate([orig_L, out_ab_resized], axis=2).astype("uint8")
        out_bgr = cv2.cvtColor(out_lab, cv2.COLOR_LAB2BGR)
        return out_bgr

class Predictor(BasePredictor):
    def setup(self):
        """Load models into memory for efficient multiple predictions."""
        # Determine paths for large and tiny model weights
        large_model_path = "checkpoints/ddcolor_modelscope.pth"
        tiny_model_path  = "checkpoints/ddcolor_paper_tiny.pth"
        # (Ensure the above files exist. If not, download from ModelScope or HuggingFace as described.)
        # Initialize pipelines for large and tiny models
        self.colorizer_large = ImageColorizationPipeline(model_path=large_model_path, input_size=512, model_size="large")
        self.colorizer_tiny  = ImageColorizationPipeline(model_path=tiny_model_path,  input_size=512, model_size="tiny")

    def predict(
        self,
        image: Path = Input(description="Grayscale input image."),
        reference: Path = Input(description="Optional reference image to guide colors", default=None),
        model_size: str = Input(description="Model size to use (large = highest quality, tiny = faster).",
                                choices=["large", "tiny"], default="large"),
    ) -&amp;gt; Path:
        """Run a single prediction. Colorizes the image, optionally using a reference image for color bias."""
        # Read input image
        img = cv2.imread(str(image))
        if img is None:
            raise ValueError("Failed to load input image")
        # Select model pipeline
        pipeline = self.colorizer_tiny if model_size == "tiny" else self.colorizer_large
        # Colorize the image using DDColor
        output_bgr = pipeline.process(img)
        # If a reference image is provided, transfer its color style to the output
        if reference is not None:
            ref_img = cv2.imread(str(reference))
            if ref_img is None:
                raise ValueError("Failed to load reference image")
            # Only apply transfer if reference is a color image
            if ref_img.shape[2] == 3:
                output_bgr = color_transfer(ref_img, output_bgr)
        # Save result
        out_path = "/tmp/out.png"
        cv2.imwrite(out_path, output_bgr)
        return Path(out_path)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Com essa modificação posso rodar com esse novo parâmetro, note a opção "--ref" que tem agora e coloque sua imagem no lugar certo:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;❯ docker run --rm --gpus all \
  -v "$PWD/input":/workspace/input_images:ro \
  -v "$PWD/results":/workspace/results:rw \
  -v "$PWD/ref_images":/workspace/ref_images:ro \
  -v "$PWD/modelscope":/app/modelscope:ro \
  -v "$PWD/infer.py":/app/infer.py:ro \
  ddcolor:latest \
  python3 /app/infer.py \
    --model_path /app/modelscope/damo/cv_ddcolor_image-colorization/pytorch_model.pt \
    --input /workspace/input_images \
    --ref /workspace/ref_images/images.jpg \
    --output /workspace/results
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Passei outra foto colorida de referência mas não dferença no resultado. Novamente, perguntei ao ChatGPT:&lt;/p&gt;

&lt;h3&gt;Por que você não percebeu diferença no resultado?&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;transferência global de cor&lt;/strong&gt; só altera a “paleta geral” — e não as cores de cada objeto.&lt;/p&gt;

&lt;p&gt;Se a imagem de referência tem tons suaves ou parecidos com os que o DDColor já aplicaria automaticamente, o efeito final &lt;strong&gt;vai parecer idêntico&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Mas se você usar como referência uma imagem bem saturada (por exemplo, um céu alaranjado, ou um ambiente azul escuro), você verá a saída do DDColor ser &lt;strong&gt;puxada visualmente&lt;/strong&gt; para aquele estilo.&lt;/p&gt;

&lt;h2&gt;Conclusão&lt;/h2&gt;

&lt;p&gt;Segui a sugestão e baixei outra imagem nada a ver, colorida, super saturada com cores vibrantes pra ver se fazia diferença. Esta é a imagem de referência que passei pro Docker:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaWtCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--39eb77b5eee6892d8ce03f4ea007711c121edc89/images.jpg?disposition=attachment&amp;amp;locale=en" alt="Imagem de Referência" /&gt;&lt;/p&gt;

&lt;p&gt;E eis o novo resultado aplicando ela à imagem anterior em pós-processamento (esse passo não é mais I.A. é pós-processamento de imagem mesmo).&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaW9CIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--8067f27feeb424fc7dc201f85d9fe8d667fe2abd/director-francis-ford-coppola-DXJA6X.jpg?disposition=attachment&amp;amp;locale=en" alt="Foto pós-processada" /&gt;&lt;/p&gt;

&lt;p&gt;Comparem com a outra foto re-colorizada, vou colocar aqui embaixo de novo pra dar pra comparar:&lt;/p&gt;

&lt;p&gt;&lt;img src="https://d1g6lioiw8beil.cloudfront.net/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBBaWdCIiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--fc30bf1dbea556b63f851ba574ad16401cd6350d/director-francis-ford-coppola-DXJA6X.jpg?disposition=attachment&amp;amp;locale=en" alt="Repetindo a primeira re-colorização" /&gt;&lt;/p&gt;

&lt;p&gt;Entenderam? Agora precisa ficar testando com referências diferentes pra ver a influência no resultado final, mas de fato, ele consegue aplicar o algoritmo de Reinhard e fazer uma transferência global de cor. Como o nome diz, é "global", não "por objeto", então é difícil conseguir controlar só partes da imagem e sim a imagem toda. Mas em teoria eu acho que você conseguiria fazer uma colorização manual no Photoshop com cores saturadas, perto de onde quer influenciar, pra tentar ajustar, mas eu não tentei isso ainda, depois se alguém fizer, mande nos comentários.&lt;/p&gt;

&lt;p&gt;Mas é isso. Agora pelo menos eu consigo me divertir pegando fotos antigas de família e re-colorizar.&lt;/p&gt;
</content>
    <author>
      <name>Fabio Akita</name>
    </author>
  </entry>
</feed>