<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Machine-Learning | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/machine-learning/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/machine-learning/index.xml" rel="self" type="application/rss+xml"/><item><title>Treinei meu próprio modelo de IA (Spoiler: é só pra estudo)</title><link>https://akitaonrails.github.io/2026/10/14/treinei-meu-proprio-modelo-de-ia-spoiler-e-so-pra-estudo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2026/10/14/treinei-meu-proprio-modelo-de-ia-spoiler-e-so-pra-estudo/</guid><pubDate>Wed, 14 Oct 2026 15:00:00 -0300</pubDate><description>&lt;p&gt;Faltava um exercício na minha lista deste ano: tentar construir o meu próprio LLM. Não um modelo de fronteira. Construir um GPT-5 ou um Claude exige anos de estudo e experimentação, times inteiros e centenas de milhões de dólares em computação. A estimativa do &lt;a href="https://hai.stanford.edu/ai-index/2024-ai-index-report"target="_blank" rel="noopener"&gt;AI Index de Stanford&lt;/a&gt; pro GPT-4 foi de US$ 78 milhões só de compute, e pro Gemini Ultra, US$ 191 milhões.&lt;/p&gt;
&lt;p&gt;Só que ninguém precisa de um modelo classe GPT pra tudo. Modelo de fronteira é generalista por definição: tenta saber tudo sobre qualquer coisa, de direito tributário a letra de música, e o retorno disso é decrescente.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Faltava um exercício na minha lista deste ano: tentar construir o meu próprio LLM. Não um modelo de fronteira. Construir um GPT-5 ou um Claude exige anos de estudo e experimentação, times inteiros e centenas de milhões de dólares em computação. A estimativa do <a href="https://hai.stanford.edu/ai-index/2024-ai-index-report"target="_blank" rel="noopener">AI Index de Stanford</a> pro GPT-4 foi de US$ 78 milhões só de compute, e pro Gemini Ultra, US$ 191 milhões.</p>
<p>Só que ninguém precisa de um modelo classe GPT pra tudo. Modelo de fronteira é generalista por definição: tenta saber tudo sobre qualquer coisa, de direito tributário a letra de música, e o retorno disso é decrescente.</p>
<p>E a maioria deles hoje é mistura de especialistas, <em>mixture of experts</em>: grosso modo, vários modelos menores agrupados num bloco gigante de bits, com um roteador que acorda só alguns pra cada token. O <a href="https://arxiv.org/abs/2508.10925"target="_blank" rel="noopener">gpt-oss-120b</a> da OpenAI tem 117 bilhões de parâmetros no total e usa 5,1 bilhões por token. O <a href="https://arxiv.org/abs/2412.19437"target="_blank" rel="noopener">DeepSeek-V3</a> tem 671 bilhões e ativa 37. O modelo grande já é, por dentro, uma coleção de modelos pequenos.</p>
<p>Eu podia ter pego um modelo aberto, gpt-oss, Qwen, Phi, e adaptado. As técnicas existem e são bem documentadas:</p>
<ul>
<li><strong>Fine-tuning completo</strong>: continuar treinando todos os pesos de um modelo pronto nos seus dados. É a transferência de aprendizado clássica, formalizada pro texto pelo <a href="https://arxiv.org/abs/1801.06146"target="_blank" rel="noopener">ULMFiT</a> em 2018.</li>
<li><strong>LoRA e QLoRA</strong>: congelar os pesos originais e treinar só matrizes pequenas de baixo posto ao lado deles. O <a href="https://arxiv.org/abs/2106.09685"target="_blank" rel="noopener">LoRA</a> reduz em até dez mil vezes os parâmetros treináveis; o <a href="https://arxiv.org/abs/2305.14314"target="_blank" rel="noopener">QLoRA</a> quantiza o modelo base em 4 bits e ajusta um modelo de 65 bilhões numa única GPU de 48 GB.</li>
<li><strong>Destilação</strong>: treinar um modelo menor pra imitar as saídas de um maior. A ideia é de <a href="https://arxiv.org/abs/1503.02531"target="_blank" rel="noopener">Hinton, 2015</a>, e o exemplo recente mais famoso é o <a href="https://arxiv.org/abs/2501.12948"target="_blank" rel="noopener">DeepSeek-R1</a> destilado em versões de 1,5 a 70 bilhões em cima de Qwen e Llama.</li>
<li><strong>Pré-treino continuado</strong>: mais uma fase de treino em texto do seu domínio antes de qualquer ajuste de instrução, o <a href="https://arxiv.org/abs/2004.10964"target="_blank" rel="noopener">&ldquo;Don&rsquo;t Stop Pretraining&rdquo;</a> de 2020.</li>
<li><strong>SFT, RLHF e DPO</strong>: ensinar o formato de assistente com demonstrações, depois alinhar com preferência humana via aprendizado por reforço (<a href="https://arxiv.org/abs/2203.02155"target="_blank" rel="noopener">InstructGPT</a>) ou com uma perda de classificação direta (<a href="https://arxiv.org/abs/2305.18290"target="_blank" rel="noopener">DPO</a>).</li>
</ul>
<p>Com qualquer uma dessas eu teria um modelo útil em horas. Só que o objetivo era outro. Eu queria organizar, com as mãos, as técnicas básicas pra construir um modelo menor que o GPT-2, do zero, em poucos dias, e ver até onde dava pra chegar. Se desse, treinar um modelo minúsculo partindo de pesos aleatórios e adicionar funcionalidade suficiente pra ele quase funcionar dentro de um harness agêntico como o OpenCode.</p>
<p>Antes de continuar, quem eu sou nessa história: eu sou engenheiro de software curioso e amador no assunto. Pesquisador de LLM eu sou zero. Eu sabia que não ia ter um LLM funcional em tão pouco tempo, e isso faz parte do exercício.</p>
<p>Mas não é inalcançável. Se você é estudante de computação, estuda sério e dá anos pra isso, você consegue tentar por conta própria. E se você não é cientista, entenda isto: ninguém precisa ser Einstein pra fazer isso. Os fundamentos estão todos disponíveis, publicados, pra você estudar e tentar. O que é obrigatório é suor.</p>
<p>Spoiler: a gente chegou longe no &ldquo;quase&rdquo; e parou lá. O <a href="https://github.com/akitaonrails/microgpt"target="_blank" rel="noopener">repositório é público</a>, com cada etapa e cada fracasso documentado. Este texto é o resumo.</p>
<blockquote>
  <p><strong>TL;DR:</strong> parti do microgpt do Karpathy, 200 linhas de Python puro que treinam um GPT em um minuto num MacBook, e expandi pra um laboratório em PyTorch com tokenizer próprio, pré-treino, ajuste supervisionado, servidor compatível com a API da OpenAI e integração com o OpenCode. Em menos de duas semanas, numa RTX 5090, treinei um modelo de 337 milhões de parâmetros em 10 bilhões de tokens: 137 horas de treino, no máximo uns R$ 80 de luz. Ele acerta 58 de 100 funções Python simples de um conjunto reservado, e zera em tudo que exige ler, editar e testar um repositório. O <a href="https://huggingface.co/HuggingFaceTB/SmolLM2-360M"target="_blank" rel="noopener">SmolLM2 de 360 milhões</a>, do mesmo tamanho, viu 4 trilhões de tokens, 400 vezes mais. Pule pro <a href="#o-exerc%c3%adcio-do-karpathy">exercício do Karpathy</a>, pras <a href="#as-etapas-de-construir-um-llm">etapas</a>, pras <a href="#as-dimens%c3%b5es-do-problema">dimensões</a>, pros <a href="#os-trope%c3%a7os">tropeços</a>, pro <a href="#o-que-saiu-do-outro-lado">resultado</a> ou pra <a href="#o-futuro-%c3%a9-menor-e-mais-especializado">conclusão</a>.</p>

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

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

</blockquote>
<h2>O futuro é menor e mais especializado<span class="hx:absolute hx:-mt-20" id="o-futuro-é-menor-e-mais-especializado"></span>
    <a href="#o-futuro-%c3%a9-menor-e-mais-especializado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Depois de onze dias olhando pra dentro de um modelo, a minha leitura do mercado ficou mais firme. Modelo de fronteira generalista vai continuar existindo e vai continuar sendo caro, e o retorno de cada bilhão a mais de parâmetros é cada vez menor. Eu já escrevi que <a href="/2026/10/12/llms-agentes-competente-o-suficiente/">os modelos crus convergiram</a> e que o ganho recente veio do harness. O exercício me deu a outra metade do argumento.</p>
<p>A mistura de especialistas é a indústria admitindo isso por dentro. Se o gpt-oss de 117 bilhões acorda 5 bilhões por token, o que você está usando numa pergunta de contabilidade é um modelo de 5 bilhões escolhido por um roteador. A pergunta que fica é por que carregar os outros 112 bilhões, de arte, cinema, meme e física de partículas, num servidor de escritório contábil.</p>
<p>Imagine um modelo local, feito pra contadores. Ele conhece o plano de contas, a legislação tributária do país, os formatos de arquivo da Receita, o jargão. Ele dispensa parâmetros sobre tudo que um contador não precisa, e pode rodar no PC do escritório, com os dados do cliente dentro da sala.</p>
<p>Os exemplos já existem em outras áreas: o <a href="https://research.google/blog/medgemma-our-most-capable-open-models-for-health-ai-development/"target="_blank" rel="noopener">MedGemma</a> de 4 bilhões pra saúde, o <a href="https://arxiv.org/abs/2403.03883"target="_blank" rel="noopener">SaulLM</a> de 7 bilhões pra direito, o <a href="https://arxiv.org/abs/2503.16252"target="_blank" rel="noopener">Fin-R1</a> de 7 bilhões pra raciocínio financeiro. Em 2023 o <a href="https://arxiv.org/abs/2303.17564"target="_blank" rel="noopener">BloombergGPT</a> precisou de 50 bilhões de parâmetros e 1,3 milhão de horas de A100 pra fazer finanças; hoje um ajuste em cima de um modelo aberto de 7 bilhões faz o mesmo papel por uma fração.</p>
<p>O que mudou pra isso virar viável foi o agente. Um modelo especializado de 2023 precisava saber tudo de cor, porque a única coisa que ele fazia era completar texto. Um modelo de 2026 precisa de uma base: raciocínio suficiente, capacidade de sustentar uma tarefa longa, chamada de ferramenta confiável, cache de prompt. O resto, a legislação que mudou ontem, o documento do cliente, a tabela que ele nunca viu, ele busca com ferramenta e internet na hora, e preenche as lacunas do treino em tempo de uso.</p>
<p>A <a href="https://arxiv.org/abs/2506.02153"target="_blank" rel="noopener">NVIDIA publicou em 2025</a> exatamente essa posição: modelos pequenos são suficientes, mais adequados e mais baratos pra maioria das chamadas de um agente, com o modelo grande chamado só quando a conversa aberta exige. A Apple já <a href="https://machinelearning.apple.com/research/apple-foundation-models-2025-updates"target="_blank" rel="noopener">roda um modelo de 3 bilhões no aparelho</a> com protocolo de ferramentas e adaptadores por tarefa.</p>
<p>É o mesmo raciocínio dos <a href="/2026/10/10/qual-modelo-de-decisao-classificador-de-ia-tipo-jev-e-melhor/">modelos de decisão que eu testei</a> semana passada: um classificador de 4 bilhões treinado pra uma tarefa bate um modelo de chat genérico do mesmo tamanho nela, numa fração do tempo de resposta. Em vez de tentar caber o mundo inteiro num modelo maior, cabe o domínio num modelo menor e deixa o harness buscar o mundo quando precisar.</p>
<p>E o que o meu modelo de 337 milhões me ensinou é o tamanho do &ldquo;suficiente&rdquo; nessa frase. Ele sabe a sintaxe e sabe o nome das coisas. O que falta, semântica, ferramenta, memória, é exatamente o que separa um gerador de texto de um agente, e isso ninguém treina em duas semanas numa placa de vídeo. Treina em meses, em cluster, com trilhões de tokens, e depois especializa.</p>
<p>O código está no <a href="https://github.com/akitaonrails/microgpt"target="_blank" rel="noopener">repositório</a>, com o relatório de fechamento inteiro, pra quem quiser pegar de onde eu parei. É só pra estudo. E foi o melhor estudo do ano.</p>
]]></content:encoded><category>inteligencia-artificial</category><category>llms</category><category>machine-learning</category><category>modelos-locais</category></item><item><title>Último Tentativa de Treinar uma LLM com LoRa. Tiro de canhão, mas errando a mosca.</title><link>https://akitaonrails.github.io/2025/05/03/ultimo-tentativa-de-treinar-uma-llm-com-lora-tiro-de-canhao-mas-errando-a-mosca/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/05/03/ultimo-tentativa-de-treinar-uma-llm-com-lora-tiro-de-canhao-mas-errando-a-mosca/</guid><pubDate>Sat, 03 May 2025 21:15:00 -0300</pubDate><description>&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"target="_blank" rel="noopener"&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 à &amp;ldquo;pobreza&amp;rdquo; 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 &amp;ldquo;sim, eu sei Zig 0.14&amp;rdquo;, não é bom - porque o modelo base de só 8B já não era grande coisa.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Ok, eu sou insistente. no <a href="https://www.akitaonrails.com/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase"target="_blank" rel="noopener">post anterior</a> expliquei tudo que eu sabia sobre tentar fazer fine-tuning de um modelo com LoRa. Mas é um processo tedioso, demorado, e limitado à &ldquo;pobreza&rdquo; 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 &ldquo;sim, eu sei Zig 0.14&rdquo;, não é bom - porque o modelo base de só 8B já não era grande coisa.</p>
<p>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.</p>
<p>&ldquo;Ph0da-se&rdquo;, pensei. Aluguei a maior configuração que tem na RunPod:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/9m5uitxvhhbpotu5lkbrw0znd276" alt="H100"  loading="lazy" /></p>
<p>Essa H100 já é &ldquo;obsoleta&rdquo;, nos datacenters mais modernos já existe H200 Blackwell e outras coisas maiores. Mas no mundo mais de &ldquo;mortais&rdquo;, 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!</p>
<p>E aqui a vantagem de ter feito tudo organizado e subir neste <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">repo no GitHub</a>. 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 <code>pip install -r requirements.txt</code> e já podia rodar meu <code>./train-lora.py</code>.</p>
<p>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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/iahqr9hzs2aaku3fup7u3cw9navk" alt="74GB"  loading="lazy" /></p>
<p>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 &ldquo;cara&rdquo; que é mais estável. É isso que estou querendo testar, com o LoRa.</p>
<p>Isso foi na hora de almoço. Estou escrevendo este post na hora da janta. Esta foi a previsão do treinamento:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lzrfdl97n96qesnhde33lugidr3i" alt="6h"  loading="lazy" /></p>
<p>Essa estimativa fica variando, mas eu acho que levou em torno de 5 a 6 horas mesmo.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/stoakih27g3o9c1e1d07y14szrf0" alt="lora size"  loading="lazy" /></p>
<p>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.</p>
<h3>Depois do Treino<span class="hx:absolute hx:-mt-20" id="depois-do-treino"></span>
    <a href="#depois-do-treino" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>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:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">vllm serve Qwen/Qwen3-32B <span class="se">\
</span></span></span><span class="line"><span class="cl">    --enable-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --lora-modules <span class="nv">ziglora</span><span class="o">=</span>./qwen3-zig-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --max-model-len <span class="m">10000</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E 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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ykusgib7o6wybhi5rs4zvx2yrp8j" alt="KV Cache error"  loading="lazy" /></p>
<p>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).</p>
<p>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 &hellip;</p>
<p>Falando sério, parece que não sou só eu. Esta <a href="https://github.com/vllm-project/vllm/issues/6876"target="_blank" rel="noopener">Issue</a> não teve solução. &ldquo;Pode&rdquo; ser algum bug no vLLM, &ldquo;pode&rdquo; 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.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mluc626od3h1q0swnv8kepe1aji5" alt="slow load"  loading="lazy" /></p>
<p>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.</p>
<p>Só pra tirar a dúvida, experimentei subir o vLLM só com o Qwen3 e tirando a LoRa completamente e, pra minha surpresa:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2hzvcqrk087i5ov4otx0rxa8erdf" alt="sem lora"  loading="lazy" /></p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>No final, com 10k o server subiu, consumindo absurdos 63GB na inicialização, sem nem começar a processar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/uetjwlzlgy7bpd4itn1qxczl3mhs" alt="63GB"  loading="lazy" /></p>
<p>E &hellip;</p>
<h3>Primeiros Testes<span class="hx:absolute hx:-mt-20" id="primeiros-testes"></span>
    <a href="#primeiros-testes" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>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).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/h4pzcdxydfiou5iulc7m67zctb9a" alt="runpod connect"  loading="lazy" /></p>
<p>MInha idéia é usar essa porta pra mapear o vLLM. Mas por default ele sobe na porta 8000. Eu esqueci de colocar <code>--port 8888</code>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>vllm serve Qwen/Qwen3-32b --enable-lora --lora-modules ziglora=./qwen3-zig-lora --max_model_len 10000 --port 8888</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E lá vamos nós de novo, esperar mais 10 minutos &hellip; &hellip; e finalmente subiu na porta certa e agora podemos testar. Primeiro, os teste de curl:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/p8d3ogzwd7lzpu05o7lkm50f8k4h" alt="curl hello"  loading="lazy" /></p>
<p>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.</p>
<p>Aliás, se subir um modelo remoto, como eu fiz, depois é só pegar a URL e colocar na variável <code>OPENAI_API_BASE</code> e qualquer coisa em <code>OPENAI_API_KEY</code> e ferramentas como Aider vão funcionar igual. A API do vLLM e Ollama são compatíveis com da OpenAI.</p>
<p>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.</p>
<p>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 &ldquo;think&rdquo; longo, mas a resposta desta vez é mais condizente com o release note (cortei só o trecho que interessa):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>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&#39;s length inferred from the destination type. Here&#39;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&#39;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 });  
}  
....</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O 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.</p>
<p>Agora vamos ver se ele consegue fazer algum código que funciona. Meu prompt foi este:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;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 &#39;/list&#39;, which will list the files in the current directory, or &#39;/cat&#39; which will read the file asked, or &#39;/quit&#39; to exit the chat?&#34;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isto aconteceu:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4n3bzm5g0gt46jfmqv55qy0x9tlu" alt="abort"  loading="lazy" /></p>
<p>Lembra como eu tive que ir diminuindo contexto pra conseguir subir? E lembram como eu falo que &ldquo;thinking&rdquo; 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 <code>curl</code> mandando <code>enable_thinking=false</code> assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl https://d8zck5aw8eimij-8888.proxy.runpod.net/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;, &#34;enable_thinking&#34;: &#34;false&#34;, &#34;skip_special_tokens&#34;: true,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;...&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">      }&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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 &ldquo;criativo&rdquo; (aleatório, na verdade), eu quero respostas o mais exatas quanto possível.</p>
<p>Enfim, mesmo tentando desligar o thinking, alguma coisa está errada. Possivelmente o LoRa que eu adicionei está fazendo ele &ldquo;pensar em círculos&rdquo; e nunca conseguindo retornar, estourando o contexto. Tentei várias vezes, mas ele está sendo incapaz de responder código.</p>
<h3>Final: Desisto por enquanto<span class="hx:absolute hx:-mt-20" id="final-desisto-por-enquanto"></span>
    <a href="#final-desisto-por-enquanto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Sim, eu quebrei a LLM, ao que parece kkkkk 😅</p>
<p>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 <code>stream=true</code>, eu consigo ver devolvendo tokens, mas em algum momento acaba espaço em algum lugar (com certeza o contexto, que é pequeno) e crasheia.</p>
<p>A sensação é que meu LoRa provavelmente &ldquo;grudou&rdquo; 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.</p>
<p>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 <code>&lt;|im_end|&gt;</code> 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:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;|im_start|&gt;system
You are a helpful assistant.&lt;|im_end|&gt;
&lt;|im_start|&gt;user
What is Zig?&lt;|im_end|&gt;
&lt;|im_start|&gt;assistant
Zig is a compiled programming language...&lt;|im_end|&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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.</p>
<p>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.</p>
<p>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 <em>&ldquo;nesta linha de python você carrega o dataset, e nesta próxima linha começa o treinamento&rdquo;</em> - mas cadê as instruções de COMO fazer um dataset e COMO determinar se ela é adequada? Não tem.</p>
<p>Se alguém tiver dicas de como melhorar o treinamento ou quiser um problema de pesquisa na área, <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">este é o GitHub</a> 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.</p>
]]></content:encoded><category>llms</category><category>machine-learning</category><category>performance</category></item><item><title>Ensinando Zig mais recente pra sua LLM - Treinando LoRas (quase)</title><link>https://akitaonrails.github.io/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/05/03/ensinando-zig-mais-recente-pra-sua-llm-treinando-loras-quase/</guid><pubDate>Sat, 03 May 2025 02:30:00 -0300</pubDate><description>&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"target="_blank" rel="noopener"&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"target="_blank" rel="noopener"&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;</description><content:encoded><![CDATA[<p><a href="https://www.akitaonrails.com/2025/05/01/quando-llms-nao-funcionam-pra-programar-um-caso-de-uso-mais-real"target="_blank" rel="noopener">Dois posts atrás</a> 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.</p>
<p>Nota: todos os códigos que vou mostrar mais abaixo estão disponíveis <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">neste repositório no meu GitHub</a>. Fucem lá depois de ler até o fim.</p>
<p>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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/39h50oguk03tci9reclcbiovolhm" alt="zig antigo"  loading="lazy" /></p>
<p>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 &ldquo;Hello World&rdquo;) ontem: ele não sabe que já estamos no <a href="https://ziglang.org/download/0.14.0/release-notes.html"target="_blank" rel="noopener">Zig 0.14</a>. E foi de propósito, eu sabia que ele não ia ter essa informação.</p>
<p><em>&ldquo;E outros LLMs comerciais? Eles não são melhores que os open source?&rdquo;</em></p>
<p>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):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/z9wbo5yi49m4vd3zi3pjpxbo7zj7" alt="LLM cut off"  loading="lazy" /></p>
<p>Resumindo:</p>
<ul>
<li>ChatGPT 4.1 diz que ele só tem dados até Junho de 2024 e só conhece até Zig 0.11.</li>
<li>Claude Sonnet diz que ele só tem dados até Abril de 2023 e só conhece até 0.10.</li>
<li>Gemini 2.5 Pro Exp diz que só tem dados até começo de 2023 e só conhece até Zig 0.11 também</li>
<li>Deepseek R1 (depois de deep thinking &hellip;) respondeu curto e grosso que só tem dados até Outubro de 2023 e só conhece até Zig 0.11.</li>
</ul>
<p><em>&ldquo;Por que eles não tem dados mais novos?&rdquo;</em></p>
<p>Vou responder isso demonstrando os motivos nas próximas seções.</p>
<p><em>&ldquo;Mas ele consegue puxar informações da internet, então não importa&rdquo;</em></p>
<p>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.</p>
<p><em>&ldquo;Mas como que quando ele responde, ele mostra os links de onde veio essa informação?&rdquo;</em></p>
<p>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.</p>
<p>Enfim.</p>
<p>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 &ldquo;Vibe Coding&rdquo; de Zig a menos que você fixe seu compilador pra versão 0.11 ou 0.12. Mas isso seria &ldquo;tosco&rdquo;, pra dizer o mínimo. Eu escolhi tentar Zig de propósito, exatamente pra provar esse ponto.</p>
<p>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.</p>
<p><strong>&ldquo;Ah, basta fazer um RAG&rdquo;</strong></p>
<p>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.</p>
<p>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.</p>
<p><strong>&ldquo;Ah, mas os Gemini da vida suporta 1 milhão de tokens&rdquo;</strong></p>
<p>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 <strong>PAGAR</strong> 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.</p>
<p>Então ph0deu? Não tem como usar Zig com LLMs?</p>
<h3>RAGs?<span class="hx:absolute hx:-mt-20" id="rags"></span>
    <a href="#rags" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>O hype de RAG (Retrieval Augmented Generation, que é só um nome bonitinho) faz parecer que é algo &ldquo;mágico&rdquo;. 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.</p>
<p>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 &ldquo;pode me resumir qual o problema desse cliente?&rdquo;, &ldquo;consegue imaginar uma solução pra ele?&rdquo; e assim por diante.</p>
<p>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.</p>
<p><strong>&ldquo;Ah, mas usa VECTOR databases, deve ser mais ph0da&rdquo;</strong></p>
<p>Ph0da é ouvir asneira. Poderia ser um Postgres e fazer um SELECT pra achar o ticket, é exatamente a mesma coisa. Se você habilitar &ldquo;full-text-search&rdquo; no Postgres ele vai indexar seus tickets em um &ldquo;VECTOR SPACE MODEL&rdquo;, que é essencialmente, um VECTOR DATABASE.</p>
<p>Se em vez disso prefere SOLR ou ELASTIC, ambos são VECTOR DATABASES especializados em pesquisa de texto.</p>
<p><em>Duh, the more you know.</em></p>
<p>O principal:</p>
<ul>
<li>
<p>quanto mais entuchar no contexto (seja prompt texto, seja &ldquo;embedding&rdquo; 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.</p>
</li>
<li>
<p>seja texto-prompt seja &ldquo;embedding&rdquo;, 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.</p>
</li>
</ul>
<p>Então qual a saída? Apesar de só falarem nisso. Não existe só RAG no mundo de LLM.</p>
<h3>Possível Solução: LoRas<span class="hx:absolute hx:-mt-20" id="possível-solução-loras"></span>
    <a href="#poss%c3%advel-solu%c3%a7%c3%a3o-loras" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se em vez de coisas que crescem diariamente, for informação mais &ldquo;permanente&rdquo; 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.</p>
<p>Sim, bons providers tem <a href="https://x.com/pashmerepat/status/1918084120514900395"target="_blank" rel="noopener">CACHE STATS</a> 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.</p>
<p>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.</p>
<p>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 &ldquo;atualizado&rdquo; pra saber Zig 0.14. Isso são LoRas (<strong>Low-Rank Adapter</strong>). É 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).</p>
<p>E fica um <strong>DISCLAIMER</strong>:</p>
<pre><code>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 &quot;tunar&quot; os parâmetros do treinamento. Tem gente especializada só nisso e não existe uma única receita que serve pra tudo. É quase uma &quot;arte&quot; e precisa de muito mais dedicação do que eu fiz só pra este post.
</code></pre>
<p>Alguns artigos pra começar a estudar:</p>
<ul>
<li><a href="https://medium.com/@ufuk.birbiri/instruction-tuning-large-language-models-llms-with-huggingface-lora-and-sfttrainer-544bad770b50"target="_blank" rel="noopener">Instruction-Tuning Large Language Models (LLMs) with HuggingFace, LoRA and SFTTrainer</a></li>
<li><a href="https://huggingface.co/docs/trl/main/en/sft_trainer"target="_blank" rel="noopener"># Supervised Fine-tuning Trainer</a></li>
<li><a href="https://rocm.docs.amd.com/projects/ai-developer-hub/en/latest/notebooks/fine_tune/fine_tuning_lora_qwen2vl.html"target="_blank" rel="noopener"># Fine-tuning with the Hugging Face ecosystem (TRL)</a></li>
<li><a href="https://clay-atlas.com/us/blog/2024/10/08/en-multi-modal-large-language-model-sft-trainer/"target="_blank" rel="noopener"># Notes on Fine-Tuning a Multi-Modal Large Language Model Using SFTTrainer</a></li>
<li><a href="https://zohaib.me/a-beginners-guide-to-fine-tuning-llm-using-lora/"target="_blank" rel="noopener"># A beginners guide to fine tuning LLM using LoRA</a></li>
</ul>
<p>E tem muito mais. É aqui que tem que entender mais os &ldquo;internals&rdquo; 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.</p>
<p>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.</p>
<p>O processo é uma versão miniatura de um treinamento de modelo de verdade. Temos que resolver os seguintes passos:</p>
<ul>
<li>conseguir o material de treinamento (ex. documentação)</li>
<li>escrever prompts de fine-tuning (eu preciso &ldquo;ensinar&rdquo; como ele deve responder - essa é a parte mais difícil)</li>
<li>precisa tokenizar essa informação (trivial), carregar o modelo de base (fácil), e começar a treinar (quanto melhor a GPU, menos lento)</li>
<li>se der sucesso precisa subir esse LoRa junto com o modelo de base</li>
<li>apontar ferramentas como Aider pra esse novo modelo modificado.</li>
</ul>
<p>Vamos lá, passo a passo.</p>
<h3>1. Material de Treinamento<span class="hx:absolute hx:-mt-20" id="1-material-de-treinamento"></span>
    <a href="#1-material-de-treinamento" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu acho que esta é a parte mais difícil: conseguir material de treinamento de qualidade pra treinar.</p>
<p>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 &ldquo;prompt&rdquo; e &ldquo;completion&rdquo; parecido com:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{ &#34;prompt&#34;: &#34;How do the new @splat works for arrays in Zig 0.14.0?, &#34;completion&#34;: &#34;Zig 0.14.0 expands the `@splat` builtin to apply not only to vectors, but to arrays...&#34;}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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 &ldquo;arte&rdquo;.</p>
<p>O arquivo de <a href="https://ziglang.org/download/0.14.0/release-notes.html"target="_blank" rel="noopener">RELEASE NOTES</a> convertido em Markdown tem ridículos <strong>107 mil caracteres</strong>. 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 &ldquo;grudarem&rdquo; o suficiente pra LLM se lembrar delas. Pra puxar, é simples:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>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 &gt; raw_data/zig-0.14.0-release-notes.md</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>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 &gt; raw_data/zig-0.14.0-docs.md</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso deu um Markdown de <strong>464 kilobytes</strong>. 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 <code>zig/doc</code> que clonei direto do <a href="https://github.com/ziglang/zig/tree/master/doc/langref"target="_blank" rel="noopener">repositório oficial do Zig</a>. Não sei qual a melhor maneira, mas eu tentei organizar um único arquivão, com nome do arquivo <code>.zig</code> e o código delimitado por uma tag, com um prompt no começo indicando isso:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">zig_files</span> <span class="o">=</span> <span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s2">&#34;raw_data/zig/doc/langref/**/*.zig&#34;</span><span class="p">,</span> <span class="n">recursive</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">total_input_bytes</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="n">file_count</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">filepath</span> <span class="ow">in</span> <span class="n">zig_files</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_bytes</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">getsize</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total_input_bytes</span> <span class="o">+=</span> <span class="n">file_bytes</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_count</span> <span class="o">+=</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="n">filename</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">filepath</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filepath</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="n">entry</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;Reference Filename: </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="se">\n\n</span><span class="s2">```zig</span><span class="se">\b</span><span class="si">{</span><span class="n">content</span><span class="si">}</span><span class="se">\b</span><span class="s2">```</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">entry</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">outpath</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_docs_with_filenames.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">outpath</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">line</span> <span class="o">+</span> <span class="s2">&#34;</span><span class="se">\n\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Notem que neste caso eu estou formatando cada exemplo começando com &ldquo;Reference filename: {filename}&rdquo; 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.</p>
<p>Isso resultou em mais um arquivão de <strong>152 kilobytes</strong>. Espero que não confuda mais do que ajude. E não parei nisso. Eu queria colocar o código-fonte inteiro da <a href="https://github.com/ziglang/zig/tree/master/lib/std"target="_blank" rel="noopener">Standard Library</a> e cheguei a fazer o script, baixar e compilar em um formato estruturado, mas o arquivão final deu <strong>15 MEGABYTES</strong>. 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.</p>
<p>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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/lawgc11lm8bv08hc2u4ms62v4hm0" alt="out of memory vram"  loading="lazy" /></p>
<p>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.</p>
<p>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 <a href="https://github.com/zigcc/zig-cookbook"target="_blank" rel="noopener">Zig Cookbook</a>, 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:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">lines</span> <span class="o">=</span> <span class="p">[]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="n">prob_file</span> <span class="ow">in</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">glob</span><span class="o">.</span><span class="n">glob</span><span class="p">(</span><span class="s1">&#39;raw_data/zig-cookbook/book-src/*.md&#39;</span><span class="p">)):</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Extract the nn-nn from the problem filename (e.g., 01-05-matrix-mul.md -&gt; 01-05)</span>
</span></span><span class="line"><span class="cl">    <span class="n">base</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">basename</span><span class="p">(</span><span class="n">prob_file</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">base</span> <span class="o">==</span> <span class="s2">&#34;SUMMARY.md&#34;</span> <span class="ow">or</span> <span class="n">base</span> <span class="o">==</span> <span class="s2">&#34;database.md&#34;</span> <span class="ow">or</span> <span class="n">base</span> <span class="o">==</span> <span class="s2">&#34;intro.md&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="n">base</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">match_part</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s1">&#39;-&#39;</span> <span class="o">+</span> <span class="n">base</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">&#39;-&#39;</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>  <span class="c1"># e.g., 01-05</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">code_file</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&#34;raw_data/zig-cookbook/src/</span><span class="si">{</span><span class="n">match_part</span><span class="si">}</span><span class="s2">.zig&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">code_file</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;WARN: No solution found for problem </span><span class="si">{</span><span class="n">prob_file</span><span class="si">}</span><span class="s2"> (expected </span><span class="si">{</span><span class="n">code_file</span><span class="si">}</span><span class="s2">)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">continue</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">prob_file</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">pf</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">problem_text</span> <span class="o">=</span> <span class="n">pf</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">code_file</span><span class="p">,</span> <span class="s1">&#39;r&#39;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s1">&#39;utf-8&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">cf</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">code_text</span> <span class="o">=</span> <span class="n">cf</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Compose chat format</span>
</span></span><span class="line"><span class="cl">    <span class="n">block</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="sa">f</span><span class="s2">&#34;&lt;|user|&gt;Solve this problem in Zig 0.14.0:</span><span class="se">\n</span><span class="si">{</span><span class="n">problem_text</span><span class="si">}</span><span class="se">\n</span><span class="s2">&lt;|endoftext|&gt;</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="sa">f</span><span class="s2">&#34;&lt;|assistant|&gt;This is the Zig 0.14.0 way to solve this:</span><span class="se">\n</span><span class="s2">```zig</span><span class="se">\n</span><span class="si">{</span><span class="n">code_text</span><span class="si">}</span><span class="se">\n</span><span class="s2">```</span><span class="se">\n</span><span class="s2">&lt;|endoftext|&gt;</span><span class="se">\n</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">lines</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">block</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;raw_data/zig_cookbook_chat.txt&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">out</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">out</span><span class="o">.</span><span class="n">writelines</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Wrote </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span><span class="si">}</span><span class="s2"> problem/solution chat pairs to zig_cookbook_chat.txt&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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 &ldquo;quem está falando&rdquo; (system, user, assistant) e parece que é uma forma alternativa a fazer em formato JSON. Daí cada problema/código fica assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;|user|&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)
...
&lt;|endoftext|&gt;
&lt;|assistant|&gt;
...
fn expensive_function() void {
    // sleep 500ms
    time.sleep(time.ns_per_ms * 500);
}
&lt;|endoftext|&gt;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Vai funcionar bem assim? Não sei 😆 Como falei, preciso gastar muito mais horas me dedicando só a entender boa formatação de material de treinamento.</p>
<p>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).</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ir04nsralua23ada3wk45xvavm97" alt="training prompts"  loading="lazy" /></p>
<p>É assim que se faz o chamado <strong>&ldquo;alinhamento&rdquo;</strong>. 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.</p>
<p>Entendo que não tem um jeito trivial de dizer <em>&ldquo;ignora a versão antiga e só puxa do meu novo material&rdquo;</em>. Não é assim que funciona. Você &ldquo;influencia&rdquo; o treinamento (<strong>treinamento supervisionado</strong>), mas não tem &ldquo;garantias&rdquo; 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.</p>
<pre><code>É 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.
</code></pre>
<p>No meu caso, não basta só subir a documentação, eu deveria dizer nos prompts &ldquo;quais as mudanças de linguagem no Zig 0.14.0?&rdquo; e dar a resposta &ldquo;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, &hellip;&hellip;.&rdquo; (sim, mudou bastante coisa mesmo).</p>
<p>Enfim. vou subir meu procedimento de treinamento <a href="https://github.com/akitaonrails/qwen3-zig-lora-training"target="_blank" rel="noopener">no meu GitHub</a>. Lá tem um diretório <code>raw_data</code> que é onde fica o material que compilei. Tem scripts como <code>concat-zig-docs.py</code>, <code>concat-zig-stdlib.py</code> ou <code>concat-zig-cookbook.py</code> pra concatenar cada grupo de material num único arquivão como expliquei. E no final, tem o <code>train-zig.py</code> que de fato carrega todos os datasets e faz o treinamento.</p>
<p>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 <strong>OVER-SAMPLING</strong>. 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:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">numpy</span> <span class="k">as</span> <span class="nn">np</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">json_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/training-prompts.json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig-0.14.0-docs.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text2_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig-0.14.0-release-notes.md&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text3_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_docs_with_filenames.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text4_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_cookbook_chat.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">text5_dataset_path</span> <span class="o">=</span> <span class="s2">&#34;raw_data/zig_stdlib_concat.txt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Prepare HuggingFace dataset for text</span>
</span></span><span class="line"><span class="cl"><span class="n">text5_dataset</span> <span class="o">=</span> <span class="n">load_dataset</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="n">data_files</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;train&#34;</span><span class="p">:</span> <span class="n">text5_dataset_path</span><span class="p">})[</span><span class="s1">&#39;train&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="n">json_dataset</span> <span class="o">=</span> <span class="n">load_dataset</span><span class="p">(</span><span class="s2">&#34;text&#34;</span><span class="p">,</span> <span class="n">data_files</span><span class="o">=</span><span class="p">{</span><span class="s2">&#34;train&#34;</span><span class="p">:</span> <span class="n">json_dataset_path</span><span class="p">})[</span><span class="s1">&#39;train&#39;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># oversample the prompts to try to influence the bias towards this new documentation</span>
</span></span><span class="line"><span class="cl"><span class="n">N</span> <span class="o">=</span> <span class="mi">100</span>  <span class="c1"># Oversampled target size</span>
</span></span><span class="line"><span class="cl"><span class="n">indices</span> <span class="o">=</span> <span class="n">np</span><span class="o">.</span><span class="n">random</span><span class="o">.</span><span class="n">choice</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">json_dataset</span><span class="p">),</span> <span class="n">size</span><span class="o">=</span><span class="n">N</span><span class="p">,</span> <span class="n">replace</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">oversampled_ds</span> <span class="o">=</span> <span class="n">json_dataset</span><span class="o">.</span><span class="n">select</span><span class="p">(</span><span class="n">indices</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># text5 (std) won&#39;t fit in the 4090 so removed from combined</span>
</span></span><span class="line"><span class="cl"><span class="n">combined_dataset</span> <span class="o">=</span> <span class="n">concatenate_datasets</span><span class="p">([</span><span class="n">text_dataset</span><span class="p">,</span> <span class="n">text2_dataset</span><span class="p">,</span> <span class="n">text3_dataset</span><span class="p">,</span> <span class="n">text4_dataset</span><span class="p">,</span> <span class="n">oversampled_ds</span><span class="p">])</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É assim que se carrega os dados de treinamento como <code>datasets</code>. E eu uso <code>random_choice</code> 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 <code>combined_dataset</code> que é tudo concatenado, cerca de quase 900 kilobytes de material.</p>
<p>Depois leia o arquivo <code>train_lora.py</code>  mas tendo o dataset combinado, agora é a parte do treinamento em si:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Tokenize your dataset FIRST:</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">tokenize_function</span><span class="p">(</span><span class="n">example</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">tokenizer</span><span class="p">(</span><span class="n">example</span><span class="p">[</span><span class="s2">&#34;text&#34;</span><span class="p">],</span> <span class="n">truncation</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">padding</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tokenized_dataset</span> <span class="o">=</span> <span class="n">combined_dataset</span><span class="o">.</span><span class="n">map</span><span class="p">(</span><span class="n">tokenize_function</span><span class="p">,</span> <span class="n">batched</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Training hyperparameters</span>
</span></span><span class="line"><span class="cl"><span class="n">training_args</span> <span class="o">=</span> <span class="n">SFTConfig</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">output_dir</span> <span class="o">=</span> <span class="s2">&#34;qwen3-zig-lora&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">per_device_train_batch_size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">gradient_accumulation_steps</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">warmup_steps</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">num_train_epochs</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">learning_rate</span> <span class="o">=</span> <span class="mf">2e-4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">fp16</span> <span class="o">=</span> <span class="ow">not</span> <span class="n">is_bfloat16_supported</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">bf16</span> <span class="o">=</span> <span class="n">is_bfloat16_supported</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">optim</span> <span class="o">=</span> <span class="s2">&#34;adamw_8bit&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">weight_decay</span> <span class="o">=</span> <span class="mf">0.01</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">lr_scheduler_type</span> <span class="o">=</span> <span class="s2">&#34;linear&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">seed</span> <span class="o">=</span> <span class="mi">3407</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">report_to</span> <span class="o">=</span> <span class="s2">&#34;none&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">max_seq_length</span> <span class="o">=</span> <span class="mi">2048</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">dataset_num_proc</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">packing</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span> <span class="c1"># Can make training 5x faster for short sequences.</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Trainer for unsupervised SFT</span>
</span></span><span class="line"><span class="cl"><span class="n">trainer</span> <span class="o">=</span> <span class="n">SFTTrainer</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">model</span><span class="o">=</span><span class="n">model</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">args</span><span class="o">=</span><span class="n">training_args</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">train_dataset</span><span class="o">=</span><span class="n">tokenized_dataset</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">processing_class</span><span class="o">=</span><span class="n">tokenizer</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">trainer</span><span class="o">.</span><span class="n">train</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">model</span><span class="o">.</span><span class="n">save_pretrained</span><span class="p">(</span><span class="s2">&#34;qwen3-zig-lora&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">tokenizer</span><span class="o">.</span><span class="n">save_pretrained</span><span class="p">(</span><span class="s2">&#34;qwen3-zig-lora&#34;</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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).</p>
<p>Em <code>SFTConfig</code> configuramos os parâmetros do treinamento. Acho que maior parte são valores padrão, mas tem dois importantes:</p>
<ul>
<li>
<p>Epoch: Uma passada pelo material de treinamento é chamado de &ldquo;Um Epoch&rdquo;. Dizem que pra pouco material (como meu caso), é bom passar mais de 3 Epochs, até 10 Epochs. Mais Epochs tenta fazer meu novo LoRa &ldquo;grudar&rdquo; mais. Se o dataset fosse ordens de grandeza maior, era melhor menos Epochs, porque existe o problema de <strong>OVER-FITTING</strong>. Ele &ldquo;gruda&rdquo; 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.</p>
</li>
<li>
<p>Learning Rate: é o fator de escala de quanto queremos que os &ldquo;pesos&rdquo; dentro do modelo mudem. Quão agressivamente queremos que essa nova informação mude os pesos do modelo, pra ele realmente &ldquo;aprender&rdquo;? Se for alto demais, ele vai começar a &ldquo;esquecer&rdquo; o que já tinha no treinamento anterior e criar buracos de conhecimento (&ldquo;catastrophic forgetting&rdquo;). Se for baixo demais, muda pouco, e nosso LoRa não vai ter nenhum efeito significativo.</p>
</li>
</ul>
<p>Tem mais um parâmetro que me deixa em dúvida <code>max_seq_length</code> que serve pra truncar texto longo demais. Teoricamente a biblioteca <code>unsloth</code>sabe dividir o material de treinamento em &ldquo;chunks&rdquo; 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.</p>
<p>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 &ldquo;arte&rdquo;: 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.</p>
<p>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.</p>
<p>Com o material no lugar, tratado, organizado, basta rodar meu <code>train_lora.py</code> e vai ter algo assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/s3rpq8b6a3h1p0qt60yazf042cr7" alt="training"  loading="lazy" /></p>
<p>E com tantos Epochs e Learning Rate alto, vai demorar! Na minha RTX 4090 deve levar <strong>mais de 1 hora treinando</strong> (com 5 Epochs), talvez mais (não cronometrei), e enquanto isso vai chumbar a GPU mesmo:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/ivjodz630t9svrpvofuzpmgjanhe" alt="GPU usage"  loading="lazy" /></p>
<p>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).</p>
<p>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.</p>
<p>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 &hellip; vai tomar um café.</p>
<p>No final, vai surgir um diretório <code>qwen3-zig-lora</code> mais ou menos assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/83h7fwsx0jsod3h17ruv4ag237r1" alt="lora"  loading="lazy" /></p>
<p>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.</p>
<h3>Mais um Problema - Limitações do Ollama<span class="hx:absolute hx:-mt-20" id="mais-um-problema---limitações-do-ollama"></span>
    <a href="#mais-um-problema---limita%c3%a7%c3%b5es-do-ollama" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra usar um modelo local, como já demonstrei <a href="https://www.akitaonrails.com/2025/04/25/seu-proprio-co-pilot-gratuito-universal-que-funciona-local-aider-ollama-qwen"target="_blank" rel="noopener">neste outro artigo</a> 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.</p>
<p>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 <code>.safetensors</code> como os que tem numa Hugging Face. Enfim, esbarrei nesse problema e acho que não dá pra prosseguir fácil com Ollama.</p>
<p>Então temos que trocar e, felizmente, existem opções. A mais popular alternativa é o <a href="https://blog.vllm.ai/2023/06/20/vllm.html"target="_blank" rel="noopener">vLLM</a>. 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 <a href="https://docs.vllm.ai/en/latest/features/lora.html"target="_blank" rel="noopener">modelos com LoRas</a>. Veja a documentação no link anterior, mas no meu caso acho que é bem simples.</p>
<p>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.</p>
<p>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 &ldquo;acho&rdquo; que seria melhor pra esse propósito - vale pesquisar depois se tiver esse interesse.</p>
<p>Meu treinamento, no final, vai gerar um diretório chamado <code>qwen3-zig-lora</code> e com vLLM podemos subir o server assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># pip install vllm[all]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">vllm serve Qwen/Qwen3-8B <span class="se">\
</span></span></span><span class="line"><span class="cl">    --enable-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --lora-modules <span class="nv">ziglora</span><span class="o">=</span>./qwen3-zig-lora <span class="se">\
</span></span></span><span class="line"><span class="cl">    --max_model_len <span class="m">8192</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra testar que subiu, podemos rodar um comando simples de curl:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl localhost:8000/v1/models <span class="p">|</span> jq .</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E vamos ter o lora listado como se fosse um modelo qualquer. veja o <code>ziglora</code>lá embaixo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;list&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;data&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen3-8B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;model&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="mi">1746207038</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;owned_by&#34;</span><span class="p">:</span> <span class="s2">&#34;vllm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen3-8B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;parent&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;max_model_len&#34;</span><span class="p">:</span> <span class="mi">8192</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;permission&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="err">...</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;id&#34;</span><span class="p">:</span> <span class="s2">&#34;ziglora&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;object&#34;</span><span class="p">:</span> <span class="s2">&#34;model&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;created&#34;</span><span class="p">:</span> <span class="mi">1746207038</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;owned_by&#34;</span><span class="p">:</span> <span class="s2">&#34;vllm&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;root&#34;</span><span class="p">:</span> <span class="s2">&#34;./qwen3-zig-lora&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;parent&#34;</span><span class="p">:</span> <span class="s2">&#34;Qwen/Qwen3-8B&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;max_model_len&#34;</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;permission&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="err">...</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra conversar com ele, podemos diretamente usar a API com curl e mandar a mensagem pro Lora:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/f1etshmornpoawgci7ndb7791r3a" alt="curl lora"  loading="lazy" /></p>
<p>E prestem atenção na resposta. Eu perguntei qual a versão mais recente de Zig que ele tem familiaridade e a resposta foi <em>&ldquo;Eu estou familiarizado com a linguagem Zig até a versão 0.14.0 de 2025.&rdquo;</em></p>
<p>Não sei dizer se é verdade 😂 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 &ldquo;saiba&rdquo; do que está falando, eu só forcei ela a responder com convicção que sim. E essa parte funcionou.</p>
<p>O Qwen3-8b tem uma janela de contexto minúscula, só 8K de tokens. Então no <code>.aider.model.settings.yaml</code> tem que configurar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">hosted_vllm/ziglora</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">edit_format</span><span class="p">:</span><span class="w"> </span><span class="l">diff</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">use_repo_map</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extra_params</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">max_tokens</span><span class="p">:</span><span class="w"> </span><span class="m">8192</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">temperature</span><span class="p">:</span><span class="w"> </span><span class="m">0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">accepts_settings</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">reasoning_effort</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra rodar o Aider, temporariamente estou fazendo assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">OPENAI_API_BASE</span><span class="o">=</span>http://localhost:8000/v1 aider --watch-files --model hosted_vllm/ziglora --reasoning-effort none --verbose</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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 &ldquo;openai/&hellip;&rdquo; tem que usar &ldquo;hosted_vllm/&hellip;&rdquo;. Também pra economizar tokens, estou desligando repo-maps do Aider e tentando desligar o thinking do modemo com <code>-reasoning-effort none</code> mas não sei se funciona, ainda vejo ele fazendo uns pensamentos longos assim.</p>
<p>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.</p>
<h3>Tangente: Aprendendo uma Linguagem<span class="hx:absolute hx:-mt-20" id="tangente-aprendendo-uma-linguagem"></span>
    <a href="#tangente-aprendendo-uma-linguagem" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Eu suspeito que meu material de treinamento é ainda muito fraco. Vamos pensar:</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>Precisaria buscar mais ainda que só um Cookbook. Se tiver livros, cursos, exercícios, precisaria organizar tudo isso no treinamento.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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 &ldquo;arquivão&rdquo; da LLM). São dúvidas que ainda não tenho resposta.</p>
<h3>1as Impressões do LoRa<span class="hx:absolute hx:-mt-20" id="1as-impressões-do-lora"></span>
    <a href="#1as-impress%c3%b5es-do-lora" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>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 <code>curl</code> e <code>jq</code> pra ver as respostas.</p>
<p>É pra isso que serve aquele <code>--max-model-len</code> 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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/5sb1dj2x0st18wsbyecwef7lbwk6" alt="vllm out of memory"  loading="lazy" /></p>
<p>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.</p>
<p>Repetindo o primeiro teste:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">curl http://localhost:8000/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Hello, which most recent version of the Zig language are you familiar with?&#34;}]                    }&#39;</span> <span class="p">|</span> jq</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Eis a resposta (só a mensagem, eu tirei do JSON pra não ficar longo aqui):</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&#34;&lt;think&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&#39;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;/think&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&#39;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.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Veja como é devolvido a parte do &ldquo;deep thinking&rdquo; ou &ldquo;reasoning&rdquo;. Esse modelo faz isso e é ruim porque a janela de contexto é pequena e esses tokens de &ldquo;think&rdquo; ocupam espaço no contexto. Veja como ele conseguiu responder que sabe 0.14 e até algumas das novidades nele. Vamos continuar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ curl http://localhost:8000/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;Can you quickly explain the new @splat support for Arrays in Zig 0.14.0? can you give me a code example&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">        }&#39;</span> <span class="p">|</span> jq .</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E eis a resposta:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;think&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&#39;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&#39;s important to mention the version. Let me make sure the example is clear and concise. I&#39;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.
&lt;/think&gt;

Here&#39;s an example demonstrating `@splat` with Arrays in Zig 0.14.0:  

const std = @import(\&#34;std\&#34;);  

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.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/fuks9uthx1l56sfpcltclhlx4n2h" alt="release note splat"  loading="lazy" /></p>
<p>Sendo honesto, eu não sei dizer se ele está correto (eu não sei Zig ainda também 🤣). Mas de bater o olho eu acho que ele inventou lero-lero, não parece correto.</p>
<p>Agora vamos ver o teste mais difícil: tentar fazer um programinha.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"> curl http://localhost:8000/v1/chat/completions <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H <span class="s2">&#34;Content-Type: application/json&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">  -d <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;model&#34;: &#34;ziglora&#34;, &#34;temperature&#34;: &#34;0.1&#34;,
</span></span></span><span class="line"><span class="cl"><span class="s1">        &#34;messages&#34;: [{&#34;role&#34;: &#34;user&#34;, &#34;content&#34;: &#34;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 &#39;</span>/list<span class="s1">&#39;, which will list the files in the current directory, or &#39;</span>/cat<span class="s1">&#39; which will read the file asked, or &#39;</span>/quit<span class="s1">&#39; to exit the chat?&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">      }&#39;</span> <span class="p">|</span> jq </span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A resposta 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:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&lt;think&gt;
Okay, let&#39;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&#39;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&#39;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&#39;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&#39;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&#39;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&#39;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&#43;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;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(\&#34; \&#34;); 
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&#39;ll need std&#39;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&#39;s a possible structure:

const std = @import(\&#34;std\&#34;); 

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, &#39;\
&#39;, 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&#39;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 \&#34;/cat filename\&#34;, then the second part is the filename. But what if there are multiple spaces? Like \&#34;/cat  file name\&#34;? In that case, the code would split into parts, and the second part is \&#34;file\&#34;, but that&#39;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 == \&#34;/list\&#34; { 
    // list files 
} else if command == \&#34;/cat\&#34; { 
    if (parts.len &lt; 2) { 
        print(\&#34;Usage: /cat &lt;filename&gt;\
\&#34;, .{}); 
        continue; 
    } 
    const filename = parts[1]; 
    // read file 
} else if command == \&#34;/quit\&#34; { 
    return; 
} 

But again, this approach may not handle filenames with spaces correctly. However, for the scope of this example, it&#39;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&#39;s the code example. 

I&#39;ll also include the necessary error handling. 

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

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

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


const std = @import(\&#34;std\&#34;);  

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, &#39;\
&#39;, 4096);  
        if (input.len == 0) break;  
        line.appendSlice(input);  
        line.append(&#39;\
&#39;);  

        const cmd = parseCommand(input);  
        switch (cmd) {  
            .List =&gt; {  
                if (std.fs.cwd().openDir(.{}, .{})) |dir| {  
                    var iter = dir.iterate();  
                    while (true) |entry| {  
                        if (entry.file_name.len == 0) continue;  
                        print(\&#34;  {}\
\&#34;, .{entry.file_name});  
                        if (entry.file_name == \&#34;README.md\&#34;) break : \&#34;loop\&#34;;  
                    }  
                    iter.deinit();  
                } else |err| {  
                    print(\&#34;Error listing directory: {s}\
\&#34;, .{@errorName(err)});  
                }  
            },  
            .Cat =&gt; {  
                if (input.len &lt; 2) {  
                    print(\&#34;Usage: /cat &lt;filename&gt;\
\&#34;, .{});  
                    continue;  
                }  
                const filename = input[input.find(&#39;/&#39;) &#43; 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 &lt; 4099) break :\&#34;\&#34; buffer;  
                    }  
                    print(\&#34;{s}\
\&#34;, .{buffer.join(\&#34; \&#34;)});  
                    buffer.deinit();  
                } else |err| {  
                    print(\&#34;Error reading file: {s}\
\&#34;, .{@errorName(err)});  
                }  
            },  
            .Quit =&gt; {  
                print(\&#34;Goodbye!\
\&#34;, .{});  
                return;  
            },  
            .Unknown =&gt; {  
                print(\&#34;Unknown command: {s}\
\&#34;, .{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] == &#39;/&#39;) {  
        if (input == \&#34;/list\&#34;) return .List;  
        if (input == \&#34;/cat\&#34;) return .Cat;  
        if (input == \&#34;/quit\&#34;) 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.</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tá, mas esse código pelo menos compila? Infelizmente não:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">❯ zig build
</span></span><span class="line"><span class="cl">install
</span></span><span class="line"><span class="cl">└─ install foo
</span></span><span class="line"><span class="cl">   └─ zig build-lib foo Debug native <span class="m">1</span> errors
</span></span><span class="line"><span class="cl">error: unable to load <span class="s1">&#39;src/root.zig&#39;</span>: FileNotFound
</span></span><span class="line"><span class="cl">error: the following <span class="nb">command</span> failed with <span class="m">1</span> compilation errors:
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">install
</span></span><span class="line"><span class="cl">└─ install foo
</span></span><span class="line"><span class="cl">   └─ zig build-exe foo Debug native <span class="m">3</span> errors
</span></span><span class="line"><span class="cl">src/main.zig:7:39: error: use of undeclared identifier <span class="s1">&#39;allocator&#39;</span>
</span></span><span class="line"><span class="cl">    var <span class="nv">line</span> <span class="o">=</span> std.ArrayList<span class="o">(</span>u8<span class="o">)</span>.init<span class="o">(</span>allocator<span class="o">)</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                                      ^~~~~~~~~
</span></span><span class="line"><span class="cl">src/main.zig:73:19: error: cannot compare strings <span class="nv">with</span> <span class="o">==</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="o">(</span><span class="nv">input</span> <span class="o">==</span> <span class="s2">&#34;/list&#34;</span><span class="o">)</span> <span class="k">return</span> .List<span class="p">;</span>
</span></span><span class="line"><span class="cl">            ~~~~~~^~~~~~~~~~
</span></span><span class="line"><span class="cl">/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:643:46: error: <span class="s1">&#39;main&#39;</span> is not marked <span class="s1">&#39;pub&#39;</span>
</span></span><span class="line"><span class="cl">    const <span class="nv">ReturnType</span> <span class="o">=</span> @typeInfo<span class="o">(</span>@TypeOf<span class="o">(</span>root.main<span class="o">))</span>.@<span class="s2">&#34;fn&#34;</span>.return_type.?<span class="p">;</span>
</span></span><span class="line"><span class="cl">                                         ~~~~^~~~~
</span></span><span class="line"><span class="cl">src/main.zig:3:8: note: declared here
</span></span><span class="line"><span class="cl"><span class="nb">export</span> fn main<span class="o">()</span> !void <span class="o">{</span>
</span></span><span class="line"><span class="cl">~~~~~~~^~~~~~~~~~~~~~~
</span></span><span class="line"><span class="cl">/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:616:20: note: called from here
</span></span><span class="line"><span class="cl">    <span class="k">return</span> callMain<span class="o">()</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">           ~~~~~~~~^~
</span></span><span class="line"><span class="cl">/home/akitaonrails/.local/share/mise/installs/zig/0.14.0/lib/std/start.zig:571:36: note: called from here
</span></span><span class="line"><span class="cl">    std.posix.exit<span class="o">(</span>callMainWithArgs<span class="o">(</span>argc, argv, envp<span class="o">))</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                   ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
</span></span><span class="line"><span class="cl">error: the following <span class="nb">command</span> failed with <span class="m">3</span> compilation errors:</span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>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.</p>
<p>Algumas coisas do treinamento, podemos ver que ele conseguiu acessar. Esse conhecimento novo <strong>não ocupa espaço no contexto, diferente de um RAG ou prompt</strong> 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 &ldquo;direitinho&rdquo; então é um <strong>semi-sucesso</strong>.</p>
<p>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).</p>
<h3>Conclusão: Semi-Sucesso?<span class="hx:absolute hx:-mt-20" id="conclusão-semi-sucesso"></span>
    <a href="#conclus%c3%a3o-semi-sucesso" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>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.</p>
<p>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á &ldquo;ouviram falar&rdquo; que poderia ter isso. Estou provando que tem mesmo e que você mesmo pode fazer em casa. Viram como ele já consegue responder &ldquo;Eu sei Zig 0.14&rdquo; 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 <em>&ldquo;ah, isso é alinhamento, mas ele na verdade não sabe tanto assim.&rdquo;</em> Esse é o ponto.</p>
<p>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 <a href="https://huggingface.co/collections/Umbra-AI/llms-uncensored-6531951e548eae0c99f4a534"target="_blank" rel="noopener">LLMs (Uncensored)</a></p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/mom2pmui347tgdwzinkz8kq9wonp" alt="Uncensored"  loading="lazy" /></p>
<p>O usuário mais dedicado é o <a href="https://huggingface.co/TheBloke"target="_blank" rel="noopener">TheBloke</a> sigam ele pra ver qual ele LLM ele desbloqueou recentemente. No perfil dele tem modelos como esse <a href="https://huggingface.co/TheBloke/CodeLlama-70B-Python-GPTQ"target="_blank" rel="noopener">CodeLlama-70B-Python-GPTQ</a> 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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
]]></content:encoded><category>llms</category><category>machine-learning</category><category>zig</category></item><item><title>Entendendo o Básico de ComfyUI pra gerar imagens com I.A.</title><link>https://akitaonrails.github.io/2025/04/20/entendendo-o-basico-de-comfyui-pra-gerar-imagens-com-i-a/</link><guid isPermaLink="true">https://akitaonrails.github.io/2025/04/20/entendendo-o-basico-de-comfyui-pra-gerar-imagens-com-i-a/</guid><pubDate>Sun, 20 Apr 2025 19:30:00 -0300</pubDate><description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Atualização:&lt;/strong&gt; este post está obsoleto. Substitui a configuração manual por um projeto containerizado mais limpo: &lt;a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"&gt;&lt;strong&gt;akitaonrails/aitrepreneur-docker&lt;/strong&gt;&lt;/a&gt;. Ele roda as stacks do Aitrepreneur localmente em Docker, com versões fixadas, modelos persistentes e workflows pré-carregados. Veja o README para detalhes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No &lt;a href="https://www.akitaonrails.com/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda"target="_blank" rel="noopener"&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;</description><content:encoded><![CDATA[<blockquote>
  <p><strong>Atualização:</strong> este post está obsoleto. Substitui a configuração manual por um projeto containerizado mais limpo: <a href="https://github.com/akitaonrails/aitrepreneur-docker"target="_blank" rel="noopener"><strong>akitaonrails/aitrepreneur-docker</strong></a>. Ele roda as stacks do Aitrepreneur localmente em Docker, com versões fixadas, modelos persistentes e workflows pré-carregados. Veja o README para detalhes.</p>

</blockquote>
<p>No <a href="https://www.akitaonrails.com/2025/04/20/gerando-imagens-com-i-a-ate-estilo-ghibli-com-docker-e-cuda"target="_blank" rel="noopener">post anterior</a> 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.</p>
<p>Eu também não sou nenhum especialista, mas achei legal explicar alguns conceitos que muitos ignoram.</p>
<h3>Modelos Pré-Treinados<span class="hx:absolute hx:-mt-20" id="modelos-pré-treinados"></span>
    <a href="#modelos-pr%c3%a9-treinados" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>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. &ldquo;B&rdquo; de &ldquo;Bilhões de Parâmetros&rdquo;. Parâmetros não tem definição usável fora da matemática, são simples números. Esses números representam &ldquo;alguma coisa&rdquo; dentro milhões de dimensões dentro desse arquivão.</p>
<p>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 &ldquo;relacionamentos&rdquo; de um token com vários tokens e vai &ldquo;posicionando&rdquo; no espaço, mas não em 3 dimensões, mas milhões de dimensões. Modelos de I.A. são &ldquo;Vector Space Models&rdquo; 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 &ldquo;Full Text Search&rdquo; de um Postgres ou MS-SQL. São todos VSMs.</p>
<p>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 &ldquo;dicionário&rdquo; pra uma nova &ldquo;língua&rdquo;. Agora, quando você &ldquo;fala&rdquo; com um GPT, primeiro ele precisa transformar seu texto da sua língua pra essa &ldquo;língua interna&rdquo; dele, isso é o que chamamos de um <strong>&ldquo;Embedding&rdquo;</strong>.</p>
<p>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.</p>
<p>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 &ldquo;loras&rdquo;.</p>
<p>Pra gerar uma imagem a partir de um prompt, primeiro precisa decodificar seu prompt num embedding, pra isso servem coisas como um <strong>TEXT ENCODER</strong> no ComfyUI.</p>
<p>No caso específico de I.A. pra imagens, eu entendo que se usa um tal de modelo de &ldquo;DIFUSÃO&rdquo; 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 &ldquo;noise&rdquo; tipo isso aqui:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rcvhb8m0boe3jetpy72kww2wmucw" alt="noise"  loading="lazy" /></p>
<p>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.</p>
<p>O que eu &ldquo;ACHO&rdquo;, &ldquo;CHUTO&rdquo; 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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/qj0ba39q5xbs7xyei52h4qmmpr2d" alt="Mapa de Profundidade"  loading="lazy" /></p>
<p>A foto original era assim:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/6zionu0khn01gpudd9z0ws2mm8zx" alt="Original"  loading="lazy" />.jpg)</p>
<p>Extrair um Mapa de Profundidade é um <strong>ALGORITMO</strong> e não &ldquo;I.A.&rdquo;. 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.</p>
<p>Outro tipo de ALGORITMO bem conhecido é o <strong>Canny Edge Detection</strong> que faz um mapa de bordas, pra ficar mais fácil de saber onde alguma coisa começa e onde termina na imagem:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/std6jaroftohqjlc22mii9m7oso6" alt="Canny"  loading="lazy" /></p>
<p>Então é mais ou menos assim:</p>
<ul>
<li>assim como um texto é &ldquo;convertido&rdquo; numa <strong>Embedding</strong>, vetores que capturam a &ldquo;essência&rdquo; do significado do texto, dependente da VSM do modelo pré-treinado, uma imagem é &ldquo;convertida&rdquo; internamente num <strong>LATENT</strong>, que por ser imagem é um &ldquo;espaço&rdquo; (por isso se fala em Latent Space). Latents não temos como &ldquo;visualizar&rdquo;, porque é uma representação interna que depende da U-NET, o modelo pré-treinado, a rede neural que aprende a &ldquo;tirar barulho&rdquo; (denoise) o Latent.</li>
<li>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.</li>
<li>Uma resposta de GPT é uma continuação do contexto da conversa anterior. Ele não está &ldquo;respondendo&rdquo; você diretamente, está só &ldquo;continuando&rdquo; o texto da conversa e por acaso, pelo treinamento, o resultado &ldquo;se parece&rdquo; 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 &ldquo;reorganizando&rdquo; o Latent numa imagem que conseguimos reconhecer.</li>
<li>Existe um &ldquo;Scheduler&rdquo; que decide quanto barulho é adicionado ou removido a cada passo no tempo. E existe o conceito de <strong>DIFFUSION MODEL</strong> que é mais ou menos o &ldquo;pipeline&rdquo; de processamento.</li>
<li>Podemos controlar o processo adicionando um prompt de texto, que é decodificado numa &ldquo;embedding&rdquo; usando um <strong>Text Decoder</strong> normalmente tem nomes como &ldquo;CLIP&rdquo; ou propriamente um Transformer (como LLava). Esses embeddings vão pra U-NET usando camadas de atenção cruzada, direcionando o &ldquo;denoising&rdquo; em direção ao significado do prompt.</li>
<li>Além disso temos <strong>CONTROLNET</strong> 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 <strong>INJETAR</strong> nos mapas principais da U-NET.</li>
<li>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.</li>
</ul>
<p>Então a U-NET é o &ldquo;gerador&rdquo;, 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.</p>
<p>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 &ldquo;NODES PROGRAMÁVEIS&rdquo;, que podemos parametrizar e ajustar pra influenciar como queremos que a imagem final saia.</p>
<p>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.</p>
<p>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 &ldquo;noisy&rdquo; numa direção não-aleatória, como este que eu peguei numa etapa intermediária do workflow que estou rodando:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/yy0yrlrcel015bucfr3z60izt42p" alt="noise"  loading="lazy" /></p>
<p>Se forçar seus olhos, consegue ver influência das imagens anteriores, e o modelo de Difusão vai fazendo o &ldquo;denoising&rdquo;, sendo &ldquo;atrapalhado&rdquo; 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 &ldquo;colaborativo&rdquo; entre os dois. Se fosse só a U-NET, ele ia gerar uma nova imagem bem diferente da original, mas a ControlNet é o &ldquo;cliente&rdquo; que fica toda hora de buzinando na orelha &ldquo;não é assim que eu quero, muda&rdquo;, até chegar num resultado aceitável.</p>
<p>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 <strong>UPSCALE MODELS</strong>. Se leu meu artigo de ontem sobre <a href="https://www.akitaonrails.com/2025/04/19/aumentando-resolucao-de-anime-velho-pra-4k-com-i-a"target="_blank" rel="noopener">Video2K</a> eu explico que escolho um modelo lá que é bom pra coerência temporal de video. Mas tinha outros modelos que são melhores pra &ldquo;IMAGENS ESTÁTICAS&rdquo;, ou fotos. E podemos usar eles no ComfyUI.</p>
<p>Depois que a nova imagem pequena é gerada pela U-NET, depois podemos usar ela como entrada pra um segundo passo de &ldquo;UPSCALE&rdquo; 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 &ldquo;soft&rdquo; borrado. Como também é outro modelo de I.A. ele é treinado pra &ldquo;redesenhar&rdquo; em mais resolução. E podemos adicionar isso no workflow do ComfyUI também.</p>
<p>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 <a href="https://www.akitaonrails.com/2025/04/19/gerando-videos-de-ate-2-min-a-partir-de-uma-foto-com-i-a"target="_blank" rel="noopener">FramePack</a>. 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 <strong>TUDO</strong>, por isso também é infinitamente mais complicada e precisa se dedicar bastante pra aprender a usar.</p>
<p>Entendendo até aqui, olhem o workflow que eu usei (está com zoom out, dá pra aumentar o zoom dinamicamente):</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/rrtdjr0sq5vsl3xnm08w3sz459w8" alt="workflow"  loading="lazy" /></p>
<p>Dá pra ver esses passos intermediários usando Nodes de <strong>PREVIEW</strong> 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.</p>
<p>Vamos resumir alguns dos conceitos usados no ComfyUI agora:</p>
<h3><strong>1. Checkpoints (Modelos Base)</strong><span class="hx:absolute hx:-mt-20" id="1-checkpoints-modelos-base"></span>
    <a href="#1-checkpoints-modelos-base" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
São os modelos principais do Stable Diffusion (ex: SD 1.5, SDXL, Flux). Contêm toda a arquitetura do modelo:</p>
<ul>
<li>
<p>Text Encoder (CLIP)</p>
</li>
<li>
<p>Modelo de Difusão (U-Net)</p>
</li>
<li>
<p>VAE (Decoder de imagens)</p>
</li>
</ul>
<p><strong>Para que servem</strong>:<br>
Gerar imagens a partir do zero. Cada checkpoint tem um &ldquo;estilo&rdquo; diferente (realista, anime, etc).</p>
<p><strong>Exemplos</strong>:<br>
<code>flux.safetensors</code>, <code>realisticVisionV60.safetensors</code>, <code>anythingV5.safetensors</code></p>
<h3><strong>2. VAE (Autoencoders Variacionais)</strong><span class="hx:absolute hx:-mt-20" id="2-vae-autoencoders-variacionais"></span>
    <a href="#2-vae-autoencoders-variacionais" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Componente que decodifica as imagens do espaço latente (formato que o modelo entende) para pixels visíveis.</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Melhorar detalhes e cores</p>
</li>
<li>
<p>Alguns checkpoints requerem VAEs específicos</p>
</li>
</ul>
<p><strong>Exemplos</strong>:<br>
<code>vae-ft-mse-840000.safetensors</code>, <code>animevae.pt</code></p>
<h3><strong>3. Text Encoders (Codificadores de Texto)</strong><span class="hx:absolute hx:-mt-20" id="3-text-encoders-codificadores-de-texto"></span>
    <a href="#3-text-encoders-codificadores-de-texto" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos que convertem seu prompt de texto em representações numéricas que a IA entende.</p>
<p><strong>Tipos comuns</strong>:</p>
<ul>
<li>
<p><strong>CLIP</strong>: Padrão para a maioria dos modelos</p>
</li>
<li>
<p><strong>OpenCLIP</strong>: Versão alternativa usada no SDXL</p>
</li>
<li>
<p><strong>T5</strong>: Modelos maiores para prompts complexos</p>
</li>
</ul>
<p><strong>Exemplos</strong>:<br>
<code>clip_vision.safetensors</code>, <code>openclip.safetensors</code></p>
<h3><strong>4. CLIP Vision</strong><span class="hx:absolute hx:-mt-20" id="4-clip-vision"></span>
    <a href="#4-clip-vision" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos especializados em análise visual (ao contrário do CLIP normal que é texto→imagem).</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Criar descrições de imagens existentes</p>
</li>
<li>
<p>Ferramentas como &ldquo;Image Prompt&rdquo; (usam a imagem como input)</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>clip_vision_g.safetensors</code></p>
<h3><strong>5. Diffusion Models (Modelos de Difusão)</strong><span class="hx:absolute hx:-mt-20" id="5-diffusion-models-modelos-de-difusão"></span>
    <a href="#5-diffusion-models-modelos-de-difus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Parte específica da arquitetura (U-Net) responsável pelo processo de difusão (adição/remoção de &ldquo;ruído&rdquo;).</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Treinamento de LoRAs</p>
</li>
<li>
<p>Workflows avançados de reutilização</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>unet.safetensors</code></p>
<h3><strong>6. LoRAs (Adaptadores Leves)</strong><span class="hx:absolute hx:-mt-20" id="6-loras-adaptadores-leves"></span>
    <a href="#6-loras-adaptadores-leves" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Pequenos arquivos que modificam o comportamento do checkpoint principal sem substituí-lo.</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Adicionar estilos específicos (ex: anime)</p>
</li>
<li>
<p>Criar personagens consistentes</p>
</li>
<li>
<p>Ajustar proporções corporais</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>epiNoiseOffset.safetensors</code>, <code>add_detail.safetensors</code></p>
<h3><strong>7. ControlNet</strong><span class="hx:absolute hx:-mt-20" id="7-controlnet"></span>
    <a href="#7-controlnet" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos que impõem controle sobre a composição da imagem (poses, profundidade, bordas).</p>
<p><strong>Tipos comuns</strong>:</p>
<ul>
<li>
<p>OpenPose (poses humanas)</p>
</li>
<li>
<p>Canny (bordas)</p>
</li>
<li>
<p>Depth (profundidade)</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>control_v11p_sd15_openpose.safetensors</code></p>
<h3><strong>8. Upscale Models (Modelos de Upscaling)</strong><span class="hx:absolute hx:-mt-20" id="8-upscale-models-modelos-de-upscaling"></span>
    <a href="#8-upscale-models-modelos-de-upscaling" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Modelos para aumentar resolução e detalhes de imagens geradas.</p>
<p><strong>Tipos</strong>:</p>
<ul>
<li>
<p><strong>ESRGAN</strong>: Para detalhes realistas</p>
</li>
<li>
<p><strong>Anime6B</strong>: Especializado em arte anime</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>4x_NMKD-Superscale-SP_178000_G.pth</code></p>
<h3><strong>9. Embeddings (Textual Inversions)</strong><span class="hx:absolute hx:-mt-20" id="9-embeddings-textual-inversions"></span>
    <a href="#9-embeddings-textual-inversions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são</strong>:<br>
Pequenos arquivos que adicionam conceitos novos ao vocabulário do modelo.</p>
<p><strong>Para que servem</strong>:</p>
<ul>
<li>
<p>Estilos artísticos específicos</p>
</li>
<li>
<p>Objetos personalizados</p>
</li>
<li>
<p>Correções de artefatos</p>
</li>
</ul>
<p><strong>Exemplo</strong>:<br>
<code>bad_prompt.pt</code>, <code>easynegative.safetensors</code></p>
<h3><strong>10. Style Models</strong><span class="hx:absolute hx:-mt-20" id="10-style-models"></span>
    <a href="#10-style-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são:</strong><br>
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.</p>
<p><strong>Para que servem:</strong></p>
<ul>
<li>
<p><strong>Colorização temática</strong> (ex: paleta de aquarela, sépia, croma‑key)</p>
</li>
<li>
<p><strong>Simular técnicas artísticas</strong> (óleo, guache, traço de mangá)</p>
</li>
<li>
<p><strong>Unificar estética</strong> em séries de imagens (mesmo “clima” de cor e luz)</p>
</li>
</ul>
<p><strong>Exemplo de arquivos:</strong></p>
<ul>
<li>
<p><code>flux1-redux-dev.safetensors</code> (estilo “Flux Redux”)</p>
</li>
<li>
<p><code>watercolor_v2.safetensors</code> (pinceladas de aquarela)</p>
</li>
<li>
<p><code>film_grain_vintage.safetensors</code></p>
</li>
</ul>
<h3><strong>11. SAMs (Segment Anything Models)</strong><span class="hx:absolute hx:-mt-20" id="11-sams-segment-anything-models"></span>
    <a href="#11-sams-segment-anything-models" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p><strong>O que são:</strong><br>
Redes de segmentação gerais capazes de isolar objetos ou regiões de interesse em uma imagem, produzindo máscaras binárias ou ponderadas.</p>
<p><strong>Para que servem:</strong></p>
<ul>
<li>
<p><strong>Criar máscaras de objetos/pessoas</strong> para aplicação seletiva de filtros ou composições</p>
</li>
<li>
<p><strong>Guiar ControlNets</strong> (ex: aplicar difusão apenas dentro ou fora de uma região)</p>
</li>
<li>
<p><strong>Pré‑processar referências</strong> (recortar fundo, separar camadas)</p>
</li>
</ul>
<p><strong>Exemplo de arquivo:</strong></p>
<ul>
<li><code>sam_vit_b_01ec64.pth</code> (SAM ViT‑B, modelo base de segmentação)</li>
</ul>
<h3>Resumo Visual<span class="hx:absolute hx:-mt-20" id="resumo-visual"></span>
    <a href="#resumo-visual" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>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  </code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Tem 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 <a href="https://github.com/akitaonrails/ComfyUI-Docker-CUDA-preloaded/blob/master/models.conf"target="_blank" rel="noopener">&ldquo;models.conf&rdquo;</a> 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.</p>
<p>Ainda assim pode ter algumas outras dúvidas que eu também tive no começo. Por exemplo <strong>&ldquo;Por que a U-NET é chamada de Checkpoint?&rdquo;</strong></p>
<h3><strong>Checkpoints ≈ Sistema Operacional Completo</strong><span class="hx:absolute hx:-mt-20" id="checkpoints--sistema-operacional-completo"></span>
    <a href="#checkpoints--sistema-operacional-completo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>
<p><strong>Semelhança</strong>: Assim como um commit no Git captura um estado específico do código, um checkpoint é um &ldquo;snapshot&rdquo; completo de um modelo treinado em um estágio específico.</p>
</li>
<li>
<p><strong>Diferença</strong>: Checkpoints são <strong>autocontidos</strong> (não são &ldquo;diferenças&rdquo; como commits), cada um é um modelo completo que pode funcionar independentemente.</p>
</li>
<li>
<p><strong>Melhor Metáfora</strong>: Pense em checkpoints como diferentes versões do Windows (XP, 7, 10) - cada um é um sistema operacional completo, não incremental.</p>
</li>
</ul>
<p>Então checkpoints meio que se assemelham, em conceito, a um &ldquo;git commit&rdquo; - se você for desenvolvedor. Ou snapshots de BTRFS ou ZFS - se você for de infra/devops.</p>
<h3><strong>LoRAs ≈ Plugins/Extensions</strong><span class="hx:absolute hx:-mt-20" id="loras--pluginsextensions"></span>
    <a href="#loras--pluginsextensions" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>
<p><strong>Semelhança</strong>: Assim como um PR adiciona funcionalidades a um código base, um LoRA modifica o comportamento do modelo base.</p>
</li>
<li>
<p><strong>Diferença</strong>: LoRAs não são &ldquo;merged&rdquo; no checkpoint original, mas sim <strong>aplicados dinamicamente</strong> durante a inferência.</p>
</li>
<li>
<p><strong>Melhor Metáfora</strong>: São como extensões do Chrome (ex: AdBlock) - adicionam comportamentos específicos sem modificar o navegador em si.</p>
</li>
</ul>
<p>Pense assim: um modelo tem Checkpoints, como se fossem commits, assim dá pra &ldquo;continuar&rdquo; 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.</p>
<p>Em vez disso podemos usar o &ldquo;dicionário&rdquo; 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.</p>
<p>Existem comunidades online inteiras dedicadas a isso. Um exemplo. No site <a href="https://civitai.com/"target="_blank" rel="noopener">Civit A.I.</a> vamos encontrar diversos modelos baseados em modelos pré-existentes, como este <a href="https://civitai.com/models/24149/mistoonanime"target="_blank" rel="noopener">Mistoon</a> que é baseado no SDXL.</p>
<p><a href="https://civitai.com/models/24149/mistoonanime"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/a4s6zs7oq6rtwto79lqk5itgd4or" alt="Mistoon Civit.ai"  loading="lazy" /></a></p>
<p>É um checkpoint. Podemos nos cadastrar no site e e baixar o arquivo <code>mistoonAnime_v10Noobai.safetensor</code> e colocar no diretório <code>models/checkpoints</code>.</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/17z9fbwbzkg5kwoxzrcivpwwd74c" alt="Mistoon Checkpoint Node"  loading="lazy" /></p>
<p>Agora, em todo workflow que tiver o Node &ldquo;Load Checkpoint&rdquo; 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 <strong>LoRa</strong>:</p>
<p><a href="https://civitai.com/models/115968/mistoonanime-school-uniform"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xlvhozw5u90xx9130a1kin5dl0da" alt="Mistoon LoRa"  loading="lazy" /></a>
O modelo Mistoon é grande, uns 6.6GB mas essa LoRa - chamada <code>Mistoon_Anime\ school\ uniform.safetensor</code> que devemos mover pra <code>models/loras</code> tem menos de 290 MB, é muito menor porque é um treinamento específico só em imagens de uniformes colegiais.</p>
<p>Agora podemos ligar o Node de &ldquo;Load Checkpoint&rdquo; a este outro Node de &ldquo;Load LoRa&rdquo; e escolher essa LoRa que sabemos que é compatível:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8ahr2bgdxe5jg3xg4y2wcie3qzvs" alt="LoRa Node"  loading="lazy" /></p>
<p>LoRas tem que ser escolhidas pra usar com os modelos em que elas foram baseadas. Como falei antes, pense no modelo como uma &ldquo;língua&rdquo;, 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.</p>
<p>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).</p>
<h3>Um Exemplo Real<span class="hx:absolute hx:-mt-20" id="um-exemplo-real"></span>
    <a href="#um-exemplo-real" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Seu dia a dia com Comfy, sendo um novato como eu vai ser mais ou menos assim: começa indo no Google e procurando &ldquo;best workflow image to anime ComfyUI&rdquo;. Primeiros links costumam ser alguma thread de Reddit:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zhnvm94r6ze0279obqq9k0wd6t9c" alt="Reddit"  loading="lazy" /></p>
<p>Sub-Reddits como <a href="https://www.reddit.com/r/comfyui"target="_blank" rel="noopener">r/comfyui</a> ou <a href="https://www.reddit.com/r/StableDiffusion"target="_blank" rel="noopener">r/StableDiffusion</a> costumam ter novidades e workflows como esse. Daí vamos direto pros primeiros comentários:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n3y2qd1vxw4uldxlrs5bjsewt9vn" alt="Reddit Comentários Links"  loading="lazy" /></p>
<p>Ou isso, ou abrimos o workflow direto no ComfyUI e vasculhamos os Nodes, um a um:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/b6hu6k4ygjpjdcq0chzv6mt1kx9w" alt="Nodes Check"  loading="lazy" /></p>
<p>Já temos talvez um problema: o Node veio pré-configurado pra carregar um LoRa chamado &ldquo;SDXL_aidma-niji_jini.safetensors&rdquo; mas o link no comentário do Reddit baixa um &ldquo;SDXL_Niji_V6_DLC_LoRa_V4.safetensors&rdquo;. Procurando especificamente pelo anterior no Google, eu não acho. Mas parece &ldquo;seguro&rdquo; 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 <code>models/loras</code> a mesma coisa pro Checkpoint AniToon no Node acima. Baixa manualmente e move pra <code>models/checkpoints</code>.</p>
<p><a href="https://civitai.com/models/541460/sdxlnijiv6dlclora"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m8u6e9hc0vyrd41pdru0c26akolr" alt="LoRa Niji"  loading="lazy" /></a></p>
<p>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:</p>
<p><a href="https://civitai.com/models/120765"target="_blank" rel="noopener"><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/zrkk8wr9v1z24s5f5fgyv01uui4g" alt="SDXL Niji"  loading="lazy" /></a></p>
<p>Arquivo <code>sdxlNijiSeven_sdxlNijiSeven.safetensors</code> de 6.5GB, move pra <code>models/checkpoints</code>, tão entendendo o processo? Baixa, move pro lugar certo, recarrega o workflow no navegador e agora aparece, selecionamos:</p>
<p>Aliás, acho que é intuitivo mas pra quem não entendeu se abriu a interface do ComfyUI:</p>
<ul>
<li>botão esquerdo do mouse seleciona Nodes</li>
<li>botão do meio arrasta a tela</li>
<li>scroll faz zoom na interface</li>
</ul>
<p>Agora sim, vamos escolher esses dois pra testar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/m7bzo57hfez23vazar5ct4j5kndq" alt="Nodes Niji"  loading="lazy" /></p>
<p>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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/8pjx9pr8x0jsmz7bhouz5zcft1zt" alt="Florence"  loading="lazy" /></p>
<p>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 &ldquo;pessoa sorrindo de pé&rdquo; e acha que é suficiente. O modelo Florence2 é feito pra ler uma imagem e descrever ela em texto.</p>
<p>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 &ldquo;The image &hellip; bla bla bla&rdquo;, daí temos um Node de &ldquo;Text Find and Replace&rdquo; que troca esse &ldquo;The Image&rdquo; pelas palavras chaves do Anitoon ou Niji: &ldquo;aidma-niji, niji&rdquo;.</p>
<p>Essas são as palavras certas? <strong>EU NÃO SEI</strong> kkkkk</p>
<p>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 <strong>CHUTANDO</strong> que deve ser &ldquo;niji&rdquo; 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!!</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/4i1n0fc3uvvy85nr17x6imee33bh" alt="ControlNet"  loading="lazy" /></p>
<p>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 <code>contronet-union-sdxl-1.0-promax.safetensors</code>, compatível com SDXL e estamos usando Niji que é feito em cima de SDXL. Vamos chutando assim.</p>
<p>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.</p>
<p>Agora podemos rodar:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/29nzuo5t1nr6mn4wss0t77at94no" alt="Run Comfy"  loading="lazy" /></p>
<p>Tem um botão enorme &ldquo;RUN&rdquo; 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.</p>
<p>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.</p>
<p>E no final, eis um dos resultados:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/7mbzamhj8c5sc2fesp8kkz6cvjhu" alt="Resultado Niji"  loading="lazy" /></p>
<p>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:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/xbxu131ivlqu4endtdf6j5ayxzxt" alt="Niji 2"  loading="lazy" /></p>
<p>Nada a ver kkkk Mas podemos trocar o modelo SDXL-Niji pelo Anitoon. Vamos ver um dos resultados:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/w7fsjrui29pbp8e98ka4ubuvtl6f" alt="Anitoon"  loading="lazy" /></p>
<p>Trocando pro modelo waiNSFIllustrious:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/2un0xtv7g210xrqc74nbceiwg7t4" alt="waiNSFIllustrious"  loading="lazy" /></p>
<p>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.</p>
<p>De todos os Nodes, um dos mais importante é o &ldquo;motor&rdquo; do processo, o <strong>KSampler</strong>:</p>
<p><img src="https://new-uploads-akitaonrails.s3.us-east-2.amazonaws.com/n6875915bhqq39vxw0kfripyyj3d" alt="KSampler"  loading="lazy" /></p>
<p>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 &ldquo;desnoise&rdquo; do Latente. Note que sempre existe um fator aleatório (&ldquo;seed&rdquo;), quantidade de passos (25 a 50 é a média), &ldquo;cfg&rdquo; (classifier-free guidance) ou &ldquo;guidance_scale&rdquo;, 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 &ldquo;artísticos&rdquo;, tem que testar).</p>
<p>Sampler Name é um saco porque precisaria entender a matemática por trás, mas são os algoritmos de steps, por exemplo <code>euler</code>, <code>ddim</code>, <code>plms</code> e cada um tem um trade-off entre velocidade e fidelidade. Na prática: teste um a um e veja os resultados. Scheduler tem <code>normal</code>, <code>klms</code>, <code>dpmsolver</code>. Mesma coisa: teste um a um.</p>
<p>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.</p>
]]></content:encoded><category>midia-generativa</category><category>machine-learning</category><category>tutoriais</category></item><item><title>[Akitando] #142 - Entendendo COMO ChatGPT Funciona - Rodando sua Própria IA</title><link>https://akitaonrails.github.io/2023/06/19/akitando-142-entendendo-como-chatgpt-funciona-rodando-sua-propria-ia/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/06/19/akitando-142-entendendo-como-chatgpt-funciona-rodando-sua-propria-ia/</guid><pubDate>Mon, 19 Jun 2023 17:47:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/O68y0yRZL1Y"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Não, AGI não está próximo de acontecer.&lt;/p&gt;
&lt;p&gt;Quais são os limites da atual tecnologia de transformadores generativos pré-treinados, vulgo GPT?
O que são os tais &amp;ldquo;modelos&amp;rdquo; que eles usam?
O que são os tais &amp;ldquo;parâmetros&amp;rdquo;.
Porque as IAs parecem humanas nas respostas?
IAs vão substituir programadores??
Como é possível conseguir rodar um clone de ChatGPT na sua própria máquina, totalmente offline?&lt;/p&gt;</description><content:encoded><![CDATA[

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

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

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

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

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

<p>O Twitter liberou parte do código que chamou de &ldquo;The Algorithm&rdquo;, que forma o sistema que popula a aba de recomendações &ldquo;Para Você&rdquo;.</p>
<p>Vamos entender qual é o contexto de ciência da computação e história necessários pra começar a entender esse código todo. E uma pequena introdução à ciência por trás de recomendações e grafos em geral.</p>
<p>Finalmente, vou poder mostrar na prática a diferença de &ldquo;código de curso&rdquo; e &ldquo;código de verdade&rdquo; e exatamente porque software nunca acaba.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:00:57 - CAP 1 - Avisos e Descrevendo o Repositório</li>
<li>00:02:51 - CAP 2 - Interpretando Errado: Não Acredite em Blog Posts</li>
<li>00:07:14 - CAP 3 - Derrubando o Meme: Pessoas Idiotas</li>
<li>00:11:30 - CAP 4 - Objetivos do Video: derrubar os Clones. Código de Verdade</li>
<li>00:16:37 - CAP 5 - Entendendo Grafos. Introdução a Relevância</li>
<li>00:21:47 - CAP 6 - Primeira Geração de Relevância: WTF</li>
<li>00:27:49 - CAP 7 - Relevância nos Primórdios da Web: PageRank e HITS</li>
<li>00:35:43 - CAP 8 - Segunda Geração de Relevância: RealGraph e SALSA</li>
<li>00:39:22 - CAP 9 - Escalando com Hadoop e MapReduce: HDFS e Pig</li>
<li>00:43:15 - CAP 10 - Algoritmos Escaláveis de Grafos: Relembrando Mergesort</li>
<li>00:46:58 - CAP 11 - Terceira Geração de Relevância: GraphJet</li>
<li>00:50:36 - CAP 12 - Earlybird: Light Ranker. Apache Lucene</li>
<li>00:51:37 - CAP 13 - Ids para Sistemas Distribuídos: Snowflake</li>
<li>00:55:23 - CAP 14 - Serialização Eficiente sem JSON: Thrift e Protobufs</li>
<li>01:01:07 - CAP 15 - A Controvérsia do &ldquo;Rails Não Escala&rdquo;. Por que Scala?</li>
<li>01:07:37 - CAP 16 - Introdução a Similarity Clusters: Similaridade de Cosseno</li>
<li>01:16:08 - CAP 17 - Resumindo os demais Projetos: Aplicando o que Aprendemos</li>
<li>01:20:22 - CAP 18 - Qualidade do Código: Mini-Assessment</li>
<li>01:22:43 - CAP 19 - Sendo Educado na Web: Memes Idiotas</li>
<li>01:24:32 - CAP 20 - Refletindo sobre o Código: Lições a Aprender</li>
<li>01:27:31 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Fio no Twitter (<a href="https://twitter.com/AkitaOnRails/status/1641887891327537158?s=20"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1641887891327537158?s=20</a>)</li>
<li>Twitter: The Algorithm (<a href="https://github.com/twitter/the-algorithm"target="_blank" rel="noopener">https://github.com/twitter/the-algorithm</a>)</li>
<li>Twitter: The Algorithm ML (<a href="https://github.com/twitter/the-algorithm-ml"target="_blank" rel="noopener">https://github.com/twitter/the-algorithm-ml</a>)</li>
<li>Twitter&rsquo;s Recommendation Algorithm (<a href="https://blog.twitter.com/engineering/en_us/topics/open-source/2023/twitter-recommendation-algorithm"target="_blank" rel="noopener">https://blog.twitter.com/engineering/en_us/topics/open-source/2023/twitter-recommendation-algorithm</a>)</li>
<li>Steven Tey: # How the Twitter Algorithm works in 2023 (<a href="https://steventey.com/blog/twitter-algorithm"target="_blank" rel="noopener">https://steventey.com/blog/twitter-algorithm</a>)</li>
<li>Aakash Gupta: The Real Twitter Files: The Algorithm (<a href="https://aakashgupta.substack.com/p/the-real-twitter-files-the-algorithm"target="_blank" rel="noopener">https://aakashgupta.substack.com/p/the-real-twitter-files-the-algorithm</a>)</li>
<li>Earlybird: Real-Time Search at Twitter (<a href="https://stephenholiday.com/notes/earlybird/"target="_blank" rel="noopener">https://stephenholiday.com/notes/earlybird/</a>)</li>
<li>Twitter Search is Now 3x Faster (<a href="https://blog.twitter.com/engineering/en_us/a/2011/twitter-search-is-now-3x-faster"target="_blank" rel="noopener">https://blog.twitter.com/engineering/en_us/a/2011/twitter-search-is-now-3x-faster</a>)</li>
<li>Paper: GraphJet: Real-Time Content Recommendations at Twitter (<a href="https://www.vldb.org/pvldb/vol9/p1281-sharma.pdf"target="_blank" rel="noopener">https://www.vldb.org/pvldb/vol9/p1281-sharma.pdf</a>)</li>
<li>Paper: RealGraph: User Interaction Prediction at Twitter (<a href="https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf"target="_blank" rel="noopener">https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf</a>)</li>
<li>Paper: Real-Time Twitter Recommendation: Online Motif Detection in Large Dynamic Graphs (<a href="http://www.vldb.org/pvldb/vol7/p1379-lin.pdf"target="_blank" rel="noopener">http://www.vldb.org/pvldb/vol7/p1379-lin.pdf</a>)</li>
<li>Paper: Discovering Similar Users on Twitter (<a href="http://snap.stanford.edu/mlg2013/submissions/mlg2013_submission_20.pdf"target="_blank" rel="noopener">http://snap.stanford.edu/mlg2013/submissions/mlg2013_submission_20.pdf</a>)</li>
<li>GitHub: FlockDB (<a href="https://github.com/twitter-archive/flockdb"target="_blank" rel="noopener">https://github.com/twitter-archive/flockdb</a>)</li>
<li>GitHub: Cassovary (<a href="https://github.com/twitter/cassovary"target="_blank" rel="noopener">https://github.com/twitter/cassovary</a>)</li>
<li>GitHub: GraphJet (<a href="https://github.com/twitter/GraphJet"target="_blank" rel="noopener">https://github.com/twitter/GraphJet</a>)</li>
<li>Twitter: Finagle (<a href="https://twitter.github.io/finagle/"target="_blank" rel="noopener">https://twitter.github.io/finagle/</a>)</li>
<li>Apache Avro (<a href="https://avro.apache.org/"target="_blank" rel="noopener">https://avro.apache.org/</a>)</li>
<li>AkitaOnRails: Chatting with Blaine Cook (Twitter) (<a href="https://www.akitaonrails.com/2008/6/17/chatting-with-blaine-cook-twitter"target="_blank" rel="noopener">https://www.akitaonrails.com/2008/6/17/chatting-with-blaine-cook-twitter</a>)</li>
<li>Fireship: Vector databases are so hot right now. WTF are they? (<a href="https://www.youtube.com/watch?v=klTvEwg3oJ4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=klTvEwg3oJ4</a>)</li>
<li>Slideshare: Restrição == Inovação (<a href="https://www.slideshare.net/akitaonrails/devconf-2019-so-carlos"target="_blank" rel="noopener">https://www.slideshare.net/akitaonrails/devconf-2019-so-carlos</a>)</li>
<li>Elastic: Vector Search (<a href="https://www.elastic.co/what-is/vector-search"target="_blank" rel="noopener">https://www.elastic.co/what-is/vector-search</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Já não é notícia nova, a esta altura acho que todo mundo já viu ou ouviu alguém comentando sobre o código dos algoritmos de recomendação do Twitter, que tio Elon Musk liberou, parcialmente, no último dia de março de 2023. No momento em que estava escrevendo o script deste episódio não se passou uma semana ainda. E até a gravação deste video ainda não foi liberada a segunda parte do código. Mas vamos ver o que temos até agora.</p>
<p>(&hellip;)</p>
<p>No mesmo dia que a primeira parte do código foi liberado, eu e diversos outros programadores e pesquisadores começamos a avaliar o código e surgiu muita coisa interessante. Eu mesmo fui twittando em tempo real o que achei de primeiras impressões. Meu foco foi mais na parte técnica e arquitetura. Os links pros meus tweets originais estão na descrição abaixo e neste video vou elaborar em mais detalhes o que não coube em pequenos tweets.</p>
<p>Antes de começar, preciso deixar alguns avisos muito claros aqui. Este código é incompleto. Ainda falta pelo menos uma segunda parte. E mesmo com essa parte, ainda não vamos ter certeza que tudo vai ser liberado. O que o Twitter soltou não é mais que uma pequena fração de todo o código necessário pra se fazer um verdadeiro clone de Twitter.</p>
<p>Esse código só cobre uma parte dos sistemas que devolvem os tweets recomendados que você vê na aba de &ldquo;Para você&rdquo;. Em termos coloquiais chamamos isso tudo de &ldquo;Algoritmo do Twitter&rdquo;, mas não é um único algoritmo: é uma coleção de dezenas de algoritmos e heurísticas que, funcionando juntos, geram recomendações que tem relevância pros seus interesses.</p>
<p>Mesmo sendo incompleto, estamos falando de mais de 4 mil e seissentos arquivos. Mais de 330 mil linhas de códigos, sendo mais da metade código feito na linguagem Scala, e quase 30% em Java. A próxima linguagem mais relevante depois dessas duas é Rust, na nova parte de machine learning chamada Navi, mas mesmo assim é só 2% do total desse código.</p>
<p>Mais importante, o sistema de build que eles utilizam é o Bazel. Pense NPM se você for de Javascript, ou algo parecido com Gradle se for de Java mesmo. Eu mesmo nunca vi Bazel sendo utilizado num projeto de verdade e cada sub-projeto tem um arquivo de BUILD em Bazel, mas eles omitiram os arquivos WORKSPACE, que são necessários pra conseguirmos realmente compilar e buildar cada projeto.</p>
<p>É possível reconstruir do zero, via tentativa e erro, mas é um trabalhinho bem chato que eu não tenho interesse em fazer e, até o momento, não vi ninguém se prontificando a fazer. Portanto tudo que eu, ou qualquer outro analisar, é baseado unicamente em análise estática do código, que é um jeito mais bonito de dizer que só temos como ler o código ou fazer compilações parciais. Por isso não acredite em tudo que ler em somente um pedaço do código. Deixa eu dar um exemplo.</p>
<p>Alguns pesquisadores foram mais interessados na parte das regras de negócio: os critérios que afetam o alcance de tweets. Em resumo, não teve nenhuma grande novidade bombástica além do que todo mundo mais ou menos já sabia só por observação. Vamos ver dois exemplos, o blog post do desenvolvedor Steven, da Vercel, e o blog post do Aakash Gupta. Ambos os links, novamente, estão na descrição abaixo.</p>
<p>No post do Steven, você vai ler que o algoritmo de ranqueamento vai determinar pesos em algumas ações em cima do seu tweet. Por exemplo, um like tem peso 0.5, um retweet é mais importante, tem peso 1, se alguém clicar no tweet, der reply ou like ou ficar mais que 2 minutos lendo, vai ter peso 11 e assim por diante. Mas, de onde ele tirou essa informação?</p>
<p>Ele coloca o link pro README no diretório projects/home/recap dentro do repositório the-algorithm-ml, que é o repositório menor de machine learning. A contagem de Scala e Java que falei acima foi no repositório principal the-algorithm, que é o maior; mas tem esse outro menor que é basicamente um projetinho Python, com 91 arquivos e umas 6 mil linhas de código pra PyTorch, que eles chamam de &ldquo;heavy ranker&rdquo;.</p>
<p>O problema é que o único lugar nesse código onde esses números de peso aparecem, é na documentação desse README. Olha só, se eu abrir o VS Code no projeto the-algorithm-ml e procurar por esse parâmetro &ldquo;is_negative_feedback_v2&rdquo;, que tem um peso negativo 74, só aparece no README, em nenhum lugar no código tem de fato esse valor.</p>
<p>Existe um arquivo &ldquo;local_prod.yaml&rdquo; que parece que configura esses parâmetros pra produção, e no servidor deles pode ser que esteja mesmo este número, mas no arquivo neste repositório, não tem nada. Portanto, não posso dizer que está nem certo, nem errado, só que é inconclusivo. Só que se você ler o post, vai achar que isso é um fato. E tenho certeza que um monte de gente vai referenciar esse blog post e repetir a informação sem checar o que significa. O Steven não errou nem mentiu, mas ele está descrevendo informação incompleta.</p>
<p>Mas não é só isso. Se for agora no post do tal Gupta, ele vai falar de outros parâmetros de peso. Por exemplo, ele fala que retweets dão um boost de 20 vezes. E de onde ele tira isso? É do repositório principal the-algorithm, no arquivo EarlybirdTensorflowBasedSimilarityEngine.scala no projeto cr_mixer. Lá tem esse trecho da função <code>getLinearRankingParams</code>. Ah, agora não é um README, é trecho de código. Então deve ser confiável, certo? Também não.</p>
<p>Vamos procurar quem chama essa função. Caímos nesse trecho da função <code>getRelevanceOptions</code> que, caso essa variável <code>useTensorflowRanking</code> seja falsa, daí vai chamar a função com os parâmetros que vimos antes, caso contrário, vai chamar essa outra função <code>getTensorflowBasedRankingParams</code>. Vamos ver o que ela define. Voltamos pro arquivo anterior e vemos que ela chama a função <code>getLinearRankingParams</code> abaixo, mas muda o tipo e, mais importante tem esse <code>applyBoosts</code> pra false.</p>
<p>O que isso significa? Será que então todos esses parâmetros com nome &ldquo;boost&rdquo; aqui embaixo vão ser ignorados? Como <code>offensiveBoost</code> ou <code>tweetHasTrendBoost</code>? Aí que tá, não temos como saber. Como disse antes, esse código é incompleto. Até o presente momento não sabemos nem se 100% desse código compila, não sabemos quais dependências estão faltando, não sabemos como buildar e, portanto, não sabemos como executar esse código e nem quais caminhos dentro são realmente executados pra cada tipo de dados e configurações.</p>
<p>E mesmo se conseguirmos fazer rodar, vamos voltar praquele README que mostrei agora pouco, da parte de machine learning com PyTorch. Olha o que diz: &ldquo;não podemos liberar os dados reais de treino por causa de privacidade&rdquo;. Faz sentido, claro, mas sem ter o mesmo material de treino, ou pelo menos o mesmo modelo gerado no final, não temos como reproduzir exatamente o que roda no Twitter e, portanto, não podemos garantir que é exatamente a mesma coisa rodando em produção nos servidores deles.</p>
<p>Portanto, do ponto de vista de auditoria, podemos confiar que, sim, esse código é certamente retirado do código real dentro do Twitter. É código demais pra falsificar, não seria prático. Mas os detalhes do que exatamente roda lá dentro, talvez a gente nunca saiba. Sempre é possível omitir camadas de filtros na fase de mixagem que adicionam ou removem tweets, ou omitir os arquivos de configuração reais que modificam todos esses pesos e boosts.  Não temos como realmente saber, por isso cuidado com afirmações sendo feitas só lendo esse código.</p>
<p>Antes de continuar, eu preciso falar da vergonha alheia que foi o comportamento geral das pessoas frente a esse código. Vamos estabelecer: eu não sou fã de Elon Musk, Dogecoin é shitcoin, e acho uma puta perda de tempo gente que idolatra o cara ou que perde tempo engajando. Ele é só mais um cara que existe. A existência dele não me afeta, assim como a existência de qualquer outro, pra mim, é insignificante. E é só isso.</p>
<p>O Twitter é uma empresa privada. Como qualquer empresa, ela não tem nenhuma obrigação de liberar código. Portanto, o que ela fez ao liberar, pra mim, é um excelente marketing. Uma cortina de fumaça pra desviar atenção, controlar a narrativa e deixar todo mundo coçando a cabeça tentando desvendar esse quebra cabeça; e ao mesmo tempo cutucar os críticos no governo e cutucar os concorrentes como um Zuckerberg da vida. No mínimo vai deixar muita gente chata ocupada tentando abrir uma caixa semi-transparente que não tem chave pra tranca.</p>
<p>A real motivação por trás disso não me interessa. Só me interessa que temos esse código. O que foi liberado, continua sendo um código excelente de explorar. Porém, pessoas idiotas parece que tem talento de encontrar a parte mais insignificante, e viralizar os memes mais idiotas. Se você pesquisar a palavra &ldquo;Elon&rdquo; no VS Code, a primeira coisa que vai aparecer é essa chave &ldquo;author_is_elon&rdquo;, logo em cima de outras chaves &ldquo;suspeitas&rdquo; como &ldquo;author_is_democrat&rdquo; ou &ldquo;author_is_republican&rdquo;. Uau, que vazamento inesperado! O que é isso, a prova que o Elon dá boost nos tweets dele? O que é isso de democratas e republicanos, tem manipulação neles também?</p>
<p>Até gente que se auto-denomina &ldquo;programador&rdquo; ficou fazendo meme disso. É uma vergonha alheia. Se você viu alguém fazendo meme disso, pode des-seguir, o cara é inútil. Deixa eu mostrar como faz. Vamos lá pro código. Esse &ldquo;author_is_elon&rdquo; é chave pra um Map que no final vai se chamar PredicateMap, que é gerado através desta Sequence, onde cada elemento é um tuple com uma string que serve de chave e um FeatureMap, que é uma função booleana. No caso desse trecho do Elon, ele recebe um candidato, provavelmente candidato de tweet pra recomendar, e checa se a tal feature de Author contém esse tal DDGStatsElonFeature. Ênfase em &ldquo;stats&rdquo;.</p>
<p>Bacana, e quem usa esse PredicateMap? Vamos procurar no código. Achamos aqui na linha 82 do objeto ServedEventsBuilder que extende essa trait ClientEventsBuilder. Nesse trecho ele tá iterando por cada predicado naquela PredicateMap e no final o que  tá gerando são counts pra cada predicado. É um contador. E quem vai usar essas contagens? Vamos de novo pesquisar, agora por esse objeto <code>ServedEventsBuilder</code>.</p>
<p>Pronto, chegamos neste <code>HomeScribeClientEventSideEffect</code>. É o que o Twitter chama de &ldquo;side-effect&rdquo;. Pelo que eu já tinha deduzido lendo o código, side-effect é um job de Kafka, um job assíncrono. E Scribe é justamente o servidor de logs open source que foi desenvolvido pelo Facebook. É só um servidor de log, não diferente de um Apache Flume ou Logstash, ou Rsyslog da vida. Esses códigos servem pra gerar métricas. É só isso.</p>
<p>&ldquo;Ah, mas tinha que ser o cuzão egocêntrico do Elon pra ter um código especial pra uma métrica só pra ele.&rdquo; Ué, mesmo se for. Se ele desenbolsou 40 bilhões pra comprar a porra toda, achei foi muito pouco código com o nome dele. Achei que fosse achar classes inteiras renomeadas pra DogeCoin ou algo assim espalhado por aí. Aliás, vocês estão assumindo que esse código foi feito por ordem do Elon.</p>
<p>Outra teoria muito mais plausível é que esse código foi feito justamente pelo povo que ele demitiu, pra monitorar os efeitos dos tweets do Elon antes da compra ter sido realizada. Muito mais plausível. Independente do que for, só vamos saber se, alguém de dentro do twitter, vazar os logs do repositório Git de verdade, com o timestamp do commit. E mesmo assim, commits de Git podem ser facilmente adulterados. Por isso é uma discussão meio inútil.</p>
<p>Todo mundo ficou fazendo meme com um código de log. E vocês se acham programadores? A discussão do porque esse trecho existe é irrelevante e só serve pra demonstrar como é um programador que gosta de fazer bikeshed: discutir coisas inúteis, sem nenhum valor, sem nenhum mérito. Você está perdendo seu tempo. Agora vamos entender como um programador de verdade olha pra esse código.</p>
<p>O objetivo a partir daqui é o seguinte: desmistificar a diferença de código de curso, código de tutorial e código de produção de empresa de verdade. Pra um iniciante que nunca trabalhou ou só trabalhou em projetos pequenos, a diferença vai ser gritante. A outra coisa é como um programador sênior pensa. Infelizmente, não tem como, num único video, com só 2 dias de análise, sem compilar, conseguir ter a visão completa. Então tudo que vou dizer são só minhas primeiras impressões.</p>
<p>Eu já disse isso em outros videos, mas aqui vale repetir: código de curso, código de tutorial, código em video no Youtube, são absurdamente diferentes de código de verdade de produção. Não tem nem comparação. A complexidade e tamanhos são impossíveis de caber em qualquer curso ou tutorial. É inviável. Literalmente, sem exagero, um curso vai te ensinar a fazer uma casinha de rato com caixa de sapato, mas todo projeto relevante de verdade é um Burj Khalifa, o arranha-céus mais alto do mundo que nunca foi construído, e toda a engenharia pra construir isso num terreno de areia, sem receita pré-pronta.</p>
<p>Outra coisa que já disse antes: software nunca acaba. Se seu produto realmente vale alguma coisa, você vai reescrever partes ou até ele inteiro, mais cedo ou mais tarde. E com esse código liberado pelo Twitter, vou finalmente poder ilustrar esses dois pontos. E esse vai ser o objetivo do video.</p>
<p>Eu peguei aleatoriamente o primeiro projeto no Github com nome de &ldquo;twitter clone&rdquo;. Tem dezenas por aí. Esse é feito em Next.js, com Typescript, Firebase, Tailwind, só &ldquo;tecnologia de ponta&rdquo;, como um amador poderia pensar. E ele faz tudo, olha só o Readme: usuários conseguem postar tweets, dar like, retweet, reply, adicionar bookmarks, adicionar imagens, seguir ou des-seguir outros usuários, updates de likes e tudo mais em tempo real. Porra, faz tudo.</p>
<p>Só que não. Isso não passa de uma caixa de sapatos frente ao Burj Khalifa. Vamos ver, com a ferramenta Tokei, vemos que esse projeto tem 158 arquivos e mais de 32 mil linhas de código. Pra um iniciante, sem dúvida, pode parecer gigantesco. Mas não é. Mesmo sendo só uma fração incompleta, o repositório do Twitter que estamos avaliando, tem mais de 4600 arquivos e mais de 330 mil linhas de código. 10 vezes mais código.</p>
<p>Mas esses projetos sequer são equivalentes. Esse tal &ldquo;clone&rdquo; é só o front-end e só as operações mais básicas. Ele jamais aguentaria a escala do Twitter em produção. O &ldquo;the-algorithm&rdquo; que foi liberado é só pra buscar os tweets que aparecem na aba &ldquo;Pra você&rdquo;. Notaram como esses clones omitem a aba &ldquo;pra você&rdquo;? Claro, porque justamente a inteligência pra buscar tweets recomendados, em tempo real, em escala, não é um problema trivial de resolver. Todo clone que você achar, ou vai omitir, ou vai ter uma versão super besta de recomendação. E nunca vão escalar pra ordem de centenas de milhões de pessoas. São todos, caixas de sapato.</p>
<p>E não tem nenhum problema nisso. O foco da maioria desses clones é ser um exercício pra iniciantes de front-end treinarem fazer interfaces parecidas com coisas que já existem. Nada mais, nada menos. Nenhum desses clones, seja clone de Twitter, clone de Instagram, clone de Pinterest, chega perto da unha dos pés das versões de verdade e são incapazes de fazer uma fração do que os originais fazem, ou de aguentar uma fração do tráfego. Essa é a realidade dos clones: são todos meros exercícios. Contanto que vocês entendam isso, não tem nenhum problema se exercitar neles. Todo mundo precisa começar do zero. E esse é o nível zero.</p>
<p>A razão dessa diferença é que é trivial fazer qualquer aplicativo funcionar só na sua máquina, com uma pessoa usando. Mesmo se você virar freelancer, normalmente trabalha em projetos que muito pouca gente usa. 10 pessoas. 100 pessoas. Mesmo mil pessoas, é extremamente pequeno. Empresas grandes como MercadoLivre, iFood, Nubank, lidam com milhões, dezenas de milhões de pessoas. No caso de Facebook, Google ou Apple, são bilhões de pessoas.</p>
<p>E nessa escala, as soluções são completamente diferentes de  todo curso e tutorial que você procurar. Eu afirmo que não existe nenhum curso que cobre casos de larga escala, simplesmente porque todos fazendo cursos nunca tiveram que lidar com esse problema. Eles também simplesmente não sabem que isso existe. Eu mesmo nunca tive que lidar com o caso excepcional de bilhões de pessoas.</p>
<p>É muito caso a caso, em cada empresa gigante, vai ser diferente. Nenhum curso conseguiria cobrir, mesmo se quisessem. Simplesmente não existe esse material disponível, e é uma das razões de porquê esse código do Twitter é relevante: porque não existe código de produção de empresa de verdade disponível pra podermos mostrar, todos são fechados.</p>
<p>Eu trabalhei em vários projetos, incluindo alguns dessa categoria grande. Mas não posso mostrar, porque nenhum de nós deve quebrar confidencialidade e propriedade intelectual de clientes. Por isso eu falo pra não confiar em nenhum programador que não tenha tido esse nível de experiência: eles nunca viram nada além da dimensão de caixa de sapato e acha que o resto do mundo é pequeno assim. E definitivamente não é. Só quem já subiu no topo do Burj Khalifa, sabe a diferença.</p>
<p>A tal aba &ldquo;Pra você&rdquo;, à primeira vista, parece super simples. Na aba &ldquo;seguindo&rdquo; mostra só tweets de pessoas que eu sigo, provavelmente em ordem cronológica. Fácil de fazer, um iniciante consegue imaginar um &ldquo;select top 100 * from tweets order by created_at desc&rdquo; da vida. Agora, no pra você, tem tweets de gente que eu sigo e de gente que eu não sigo, mas que as pessoas que eu sigo interagiram. Então se eu sigo o João e o João retwitou o tweet do Paulo, no meu &ldquo;pra você&rdquo; pode aparecer o tweet do Paulo. Só que não é tão simples assim.</p>
<p>E se quem eu sigo interagiu com 100 tweets? Quais deles deveriam aparecer pra mim? Das pessoas que eu sigo, quais delas são mais relevantes pra aparecer no topo primeiro? Por que eu vejo o like de algumas pessoas mas não de outras? Ah, será que como os posts de blogs tavam tentando explicar, é só botar um peso pra like, outro peso pra retweet e dar um &ldquo;group by&rdquo; ou &ldquo;order by&rdquo; da vida e pegar o topo? Também não.</p>
<p>Como é impossível cobrir o código todo que foi liberado, escolhi começar falando só de um pequeno trecho. Um banco de dados NoSQL de grafos, que inclusive já tinha sido liberado antes como open source, com licença Apache, chamado GraphJet, e a história por trás dele. Tudo isso foi descrito num paper de autoria de Aneesh Sharma e vários outros colaboradores, publicado em 2016.</p>
<p>Pra todo mundo que não fez ciências da computação entender, na faculdade a gente aprende sobre um troço chamado &ldquo;grafos&rdquo;. Na prática, você já deve ter visto e até desenhado. É quando se desenha caixinhas ou círculos que chamamos de nós e ligamos um nó com outro nó com um troço que chamamos de vértices. Podemos representar grafos como matrizes adjacentes ou listas adjacentes. Outra forma de representar grafos é com vetores.</p>
<p>Daí podemos classificar grafos baseados em características como número de nós ou número de vértices, edges ou bordas, direção das bordas. Tem diversos tipos de grafos como unidirecionais. Em Git, por exemplo, você já deve ter ouvido falar que é um DAG, um direct acyclic graph, ou grafo acíclico, que não é cíclico, e direto.</p>
<p>Dado um grafo, temos como navegar de um nó pra outro nó. Pense em nós como uma casa numa rua e os vértices como o caminho, talvez a rua, entre essas casas. Daí temos algoritmos pra navegar como o algoritmo do menor caminho entre dois nós, que é o hello world de grafos. Não só isso, ainda no assunto de classificação, temos nós que ficam nas bordas do grafo que tem poucos vértices e temos nós no meio dos grafos com muitos vértices.</p>
<p>Costumamos chamar esses nós de &ldquo;hubs&rdquo;. Na prática, numa rede social, chamamos de hubs, mas pessoas comuns chamam de &ldquo;influencers&rdquo;. Pessoas comuns são os nós das bordas, com poucos relacionamentos, que são os vértices.</p>
<p>Tudo na natureza pode ser representado como grafos. Como falei antes, evolução de desenvolvimento de software, pode ser representado como um DAG, um grafo de commits, com branches. Um sistema de logística e entregas é orientado num mapa com grafos, pra determinar o melhor caminho de entrega entre dois pontos. O DNA humano pode ser um grafo, onde os nós são as bases como adenina, citosina, guanina e timina, formando nucleobases. As sinapses do nosso cérebro são grafos.</p>
<p>Num ecossistema animal temos a hierarquia da cadeia alimentar, que pode ser representado como um grafo. No mercado temos grafos de empresas e relacionamentos de comprador e vendedor. E assim por diante. No caso do Twitter, que é uma rede social, os nós costumam ser as pessoas, e os vértices são os relacionamentos, quem nós seguimos, quem damos likes ou retweets, quem bloqueamos e assim por diante.</p>
<p>Se você nunca estudou ciências da computação, vai ter um pouco de dificuldade de entender o que vou explicar e certamente não vai entender nada do código do Twitter. Vou tentar simplificar o máximo que der, mas só consigo chegar até certo ponto, então, se interesse em depois ir procurar cursos como nas aulas do MIT sobre grafos. Pra operar em grafos, você vai precisar aprender coisas como Álgebra Linear e Cálculo, pelo menos o Cálculo básico.</p>
<p>E portanto sim, pra entender como uma rede social funciona e o código por trás, precisa entender esses aspectos da matemática. E pra chegar no ponto de recomendação, também precisa entender o básico de estatística e probabilidade, porque recomendação é justamente a probabilidade dos tweets que o sistema recomendar realmente te interessar a ponto de engajar, dar like ou reply, que é a base de machine learning.</p>
<p>Mesmo se você não for implementar do zero inteligência artificial, machine learning, ainda assim precisa entender essa matemática. Machine Learning é, nada mais, nada menos, do que uma coleção de algoritmos de classificação e probabilidade. Se não souber quando pode usar regressão linear, regressão logística, similaridade de cosseno, bayes ingênuo, não vai saber o que fazer.</p>
<p>Cada caso, cada tipo de conjunto de dados e o resultado que quer, depende de saber escolher quais desses algoritmos usar. E pra saber quais usar, precisa saber a matemática por trás. Senão vai usar regressão linear quando não podia e ter resultados errados, que é a coisa mais comum de ver amador fazer. Nem tudo que parece uma reta, é uma reta.</p>
<p>E no caso desse código do Twitter sim, eu encontrei que ele usa coisas como grafos, regressão logística e similaridade de cosseno. Mas estou me adiantando. Vamos voltar ao tal paper do Graphjet de 2016. Um problema, que não é trivial de resolver e não tem uma solução única, porque depende do comportamento dos usuários e dos dados disponíveis, é recomendação. Seja recomendação de produtos num ecommerce, recomendação de leitura num site de notícias, ou recomendação de tweets num Twitter.</p>
<p>Como usuários seguem usuários, naturalmente temos grafos, em particular grafos bipartidos, com usuários de um lado e tweets de outro. Os esforços de recomendação começaram no Twitter por volta de 2010, lembrando que o Twitter começou a operar em março de 2006.</p>
<p>Eu mesmo criei minha conta só um ano depois, em abril de 2007, durante a Railsconf 2008 em Portland, onde eu via todo mundo se cadastrando pra gente compartilhar onde ia ser o próximo happy hour. Lembrando que ainda não existia Messenger do Facebook, não existia Whatsapp, não existia Telegram. O único aplicativo de mensagem que todo mundo usava, era SMS.</p>
<p>De qualquer forma, quando eles começaram a experimentar a recomendar usuários novos pra gente seguir, por volta de 2010, foi com um sistema que chamaram carinhosamente de &ldquo;WTF&rdquo;, What the Fuck. Mentira, era &ldquo;Who To Follow&rdquo;. A arquitetura era como nessa figura. Segundo o paper, no seu núcleo tinha esse Cassovary, que é um motor de processamento de grafos in-memory, ou seja, que mantém os grafos em memória. Isso é importante, porque a maioria dos algoritmos conhecidos pra lidar com grafos assume ter o grafo todo em memória. Guarde essa informação.</p>
<p>Esse projeto Cassovary é open source e podemos encontrar no GitHub deles. Última atualização foi de 2021, e não é mais utilizado hoje. Continuando, esse Cassovary operava em snapshots de grafos de seguidores carregados do HDFS, que é o sistema de arquivos distribuídos do Hadoop. Quem já trabalhou com Big Data, Data Lakes, certamente já esbarrou em instalações de HDFS e no Twitter não era diferente. Só entenda que é um sistema de arquivos distribuído, como se você tivesse uma partição única espalhada em múltiplos servidores. É um nível acima de um simples RAID.</p>
<p>Os grafos de seguidores vinham de outro banco de dados chamado FlockDB que fazia a ingestão desses dados no HDFS. FlockDB acho que foi o primeiro banco de dados de grafos que o Twitter criou e soltou como open source. O que aconteceu foi: eles começaram como qualquer tech startup normal, sem saber o que o produto ia se tornar. No começo usavam um banco de dados normal, MySQL mesmo. Mas quando começaram a fazer sucesso e ver que o problema era maior que um banco relacional, começaram a pesquisar alternativas. E o primeiro resultado foi um banco de dados de grafos, o FlockDB.</p>
<p>O FlockDB dava mais performance pra operações de inserção, updates, já que não precisava das garantias ACID de um banco relacional. Tinha operações extras pra lidar com grafos, como navegar por nós e pelos vértices. E vocês tem que entender que carregar um grafo em memória pode ser pesado. Imagine sua própria conta: você tem dezenas de tweets, você segue dezenas de pessoas, essas pessoas tem dezenas de tweets.</p>
<p>Cada um desses tweets tem contagens de likes, retweets; alguns tweets são replies pra outros tweets. E pra calcular qual deles é mais relevante ou menos relevante, o ideal é ter toda essa estrutura em memória pra conseguir navegar por toda essa informação. Agora pensa num banco de dados relacional como MySQL. Como você resolveria esse problema? Qualquer solução que você imaginar em SQL, pode ter certeza, já foi testado e sabemos que não vai escalar. Essa é um dos poucos cenários que podemos afirmar que um banco de dados relacional não é adequado.</p>
<p>Segundo a página de GitHub do FlockDB, até 2010 o cluster deles armazenava mais de 13 bilhões de bordas e sustentava picos de tráfego de mais de 20 mil escritas por segundo, e mais de 100 mil operações de leitura por segundo. Mas rapidamente chegou um ponto onde isso já não era mais suficiente. Foi quando começaram a puxar esses dados pra HDFS e processando nesse novo projeto que chamaram de Cassovary, um segundo projeto em Java pra lidar com &ldquo;Big Graphs&rdquo;, grafos com bilhões de nós e vértices. Foi a época que as celebridades gigantes começaram a viver no Twitter. Pense nível Kardashians.</p>
<p>Voltando ao paper, a camada de armazenamento do Cassovary dava acesso aos grafos via queries, pesquisas baseadas em vértices. Pense um &ldquo;SQL&rdquo; especializado em grafos. E em cima disso um motor de recomendação computava as sugestões de &ldquo;Who To Follow&rdquo;, o WTF, ou &ldquo;quem seguir&rdquo;. Foi assim que nos primórdios do Twitter a ganhamos sugestões de novas contas pra poder seguir.</p>
<p>Um dos objetivos desse projeto era ver se era possível manter os grafos em memória, in-memory. A pergunta que eles não tinham resposta é: será que o crescimento do volume de dados desses grafos seria mais lento que a Lei de Moore? Se for, o preço de memória iria baratear mais rápido do que os dados cresciam, e daria pra computar tudo em memória. Que é mais simples do que ter que lidar com discos, particionamento, paging, cache e tudo mais.</p>
<p>Como o paper diz, considere um grafo com 10 bilhões de bordas: mesmo uma representação ingênua, simples, ocuparia algo na faixa de 80 gigabytes, que um único servidor consegue aguentar. Você já trabalhou com um servidor, este ano, que tem 80 gigabytes? Se sim, diga nos comentários que empresa é, pra todo mundo entender meu ponto.</p>
<p>Uma das limitações, mesmo naquela época, é que essa sugestões de recomendação de quem seguir, eram calculados em batch, uma vez por dia, não era em tempo real como é hoje. Ou seja, você só tinha recomendações novas uma vez por dia. De qualquer forma, via muita experimentação, muitos testes A/B, eles chegaram em dois conceitos que usam até hoje e que vale explicar. O primeiro se chama círculo de confiança e o segundo foi a adoção de &ldquo;SALSA&rdquo; que é Stochastic Approach for Link-Structure Analysis ou Análise de estrutura de links usando processos estocásticos.</p>
<p>Um exemplo de grafo que eu não mencionei, mas deveria ser óbvio, é a própria World Wide Web. Toda página na internet é um nó, as bordas, vértices, são links de uma página pra outra. No começo da web, já se tinha um número grande de páginas, o suficiente pra ser difícil de achar as coisas. Pense numa era antes de existir Google. A forma rudimentar que existia pra gente se achar era derivado do conceito antiquado de Páginas Amarelas.</p>
<p>Se você for muito novo talvez nunca tenha visto, mas antigamente, quando precisávamos achar um mecânico, um médico ou qualquer serviço, tínhamos um livrão físico de páginas amarelas que era um diretório com todos os serviços da cidade. Cada cidade tinha um livro diferente. Imaginem que novos negócios tinham dificuldade de serem encontrados, porque precisava esperar uma nova edição ser impressa e distribuída pra todo mundo. Páginas de classificados nos jornais meio que cobriam um pouco dessa diferença.</p>
<p>Serviços como Yahoo, Excite, Lycos e outros serviam esse papel na Web. Se eu criasse um site novo, pedia pra ser incluído num desses diretórios pra que as pessoas conseguissem me achar. Esse era o processo. Mas mesmo assim, imagine restaurantes. Qual das centenas de restaurantes numa cidade grande como São Paulo, seria relevante pra mim? Mesmo se tivesse a opção de filtrar por localização e tipo de cozinha. Ainda assim eu tinha que manualmente tentar várias. Era bem trabalhoso.</p>
<p>Portanto, resolver esse problema de relevância entre milhares de páginas web, era muito importante. E lá no meio pro fim dos anos 90 surgiram pelo menos dois estilos diferentes de lidar com isso. A maioria de vocês que estudou o mínimo de grafos talvez estejam familiarizado com o famoso paper de PageRank, literalmente ranking de páginas, inventado pelos fundadores do Google, Larry Page e Sergey Brin em 1996 quando ainda eram estudantes de Stanford.</p>
<p>Procurem no YouTube, tem dezenas de videos que explicam PageRank, mas a idéia básica é a seguinte. Começa pelo básico, o &ldquo;H&rdquo; de HTML é pra Hyperlink. Todo mundo esquece isso, mas o mais importante numa página web é que a página de alguém contenha um link pra sua página. Quanto mais sites diferentes tem links apontando pro seu site, mais relevante ele pode ser.</p>
<p>No PageRank, imaginamos um navegador aleatório, uma pessoa imaginária que vai clicando em páginas e nos links nessas páginas, aleatoriamente. Qual a probabilidade de, só navegando sem rumo, alguém cair na sua página? Esssa probabilidade é o seu score, sua pontuação. Se sua página é famosa, centenas de outras páginas contém link pra sua, a probabilidade desse navegador aleatório chegar na sua página é alta, portanto sua pontuação no ranking, no PageRank, é alta.</p>
<p>Esse foi o algoritmo usado na primeira versão do Google, o site que matou todos os diretórios estilo páginas amarelas. Porque agora era possível fazer um programa que sai vasculhando todas as páginas na web, avaliando os hyperlinks entre eles, e atribuindo pontuações nesse ranking. Daí, quando fazemos uma pesquisa, ele nos mostra primeiro as páginas mais bem pontuadas. É por isso que logo na primeira página, os primeiros links costumam ser os corretos.</p>
<p>Com isso eliminamos a necessidade de seres humanos ficarem manualmente classificando páginas em diretórios e atualizando a relevância na mão, que sempre foi demorado, trabalhoso, tedioso e com muita margem de erro. Essa talvez tenha sido a primeira categoria de trabalhadores da web que perderam seu trabalho pra um algoritmo, e não será a última. Tudo que é feito manualmente e pode ser automatizado, certamente será. Todo trabalho repetitivo é automatizável, sabemos isso desde o começo da Web.</p>
<p>Mas todo algoritmo tem seu ponto fraco e rapidamente as pessoas começaram a notar que se o lance é esse número de links pra sua página, que tal criar um monte de sites fantasma que fazem links pra uma página que você quer tornar relevante? E assim surgiram os primeiros web farms, fazendas de servidores fake, e uma galera que vendia relevância pro seu site, fazendo um monte de site falso linkar pra sua. Seja criando sites bestas em série ou invadindo sites dos outros pra colocar links.</p>
<p>Isso aconteceu só lá no começo dos anos 2000. Por isso o Google evoluiu e deixou de usar PageRank e passou a usar, por exemplo, TrustRank, que combate web spam identificando quem é site legítimo e quem não é, e descontando pontuação. Ao longo dos anos o Google sempre foi aprimorando esses algoritmos, não é um produto fixo. Parte do trabalho de agências web era ficar monitorando quando o Google lançava uma nova versão pra modificar as estratégias pra levar relevância pros seus clientes. Esses truques baratos não funcionam mais hoje.</p>
<p>Mas não era só isso. Mesmo sem spammer e gente maliciosa, as primeiras versões do PageRank ainda sofriam de um problema grave. Pra ilustrar, veja meu conteúdo, meu canal e meu blog, que é de um domínio muito específico de assuntos de tecnologia pra programadores. Talvez eu faça um video explicando em detalhes sobre o algoritmo do Twiter, exatamente o que estou fazendo agora. Pra programadores é relevante.</p>
<p>Porém, imagine que um bosta como um Felipe Neto também faça um video falando sobre o algoritmo do Twitter. Segundo o PageRank, que é um ranking global, quer você goste dele ou não, se pesquisar no Google de 1997 &ldquo;algoritmo do Twitter&rdquo;, esse video do Felipe Neto iria aparecer na frente do meu. Essa é a desvantagem de um algoritmo de ranking global como o PageRank original.</p>
<p>Mas como eu falei, as pesquisas em torno de grafos evoluíram rápido. Por exemplo, em 1999 surgiu o algoritmo HITS que significa Hyperlink-Induced Topic Search, de Jon Kleinberg. HITS opera num grafo direcionado, onde páginas web representam nós e hyperlinks são bordas direcionadas. Até aqui, igual PageRank, mas o HITS assinala duas pontuações pra cada página web: uma pontuação se é um &ldquo;hub&rdquo; e outra pontuação se é uma &ldquo;autoridade&rdquo;.</p>
<p>Hubs são páginas que linkam pra diversas outras páginas. Pense um site do Estadão ou da Folha. São hubs de notícias que linkam pra trocentas outras páginas fora. Ou influencers como o Nelipe Feto, é um hub. Já autoridades são páginas que recebem muitos links de muitos hubs. Pense a página da Receita Federal em época de imposto de renda. Todo mundo faz link pra ela, pra avisar que tá vencendo a data ou ensinando como preencher seu imposto. Mas todo mundo pode ser ao mesmo tempo um hub e uma autoridade, alguns são mais hubs, alguns são mais autoridades num domínio em específico. Por isso duas pontuações distintas.</p>
<p>Portanto, o canal de um Nelipe Feto da vida teria uma pontuação de Hub muito maior do que de autoridade, que é exatamente o perfil do influencer médio: serve pra direcionar tráfego pra todo mundo, mas ele mesmo não é autoridade de quase nada. Já caras como eu geram muito pouco tráfego pros outros, mas todo mundo me menciona e linka pra mim em assuntos de um domínio específico de programação e tecnologia.</p>
<p>Com isso, minha pontuação de autoridade, num video sobre &ldquo;algoritmo do Twitter&rdquo;, seria maior que do Nelipe e eu apareceria no topo da pesquisa, mesmo ele tendo muito mais tráfego e sendo muito mais famoso. Essa é a vantagem de algoritmos da família do HITS, se comparado ao PageRank original: ele torna esse jogo bem mais justo do que uma pontuação única global, e tenta melhorar a confusão entre influencer e autoridade. Só porque alguém tem muito seguidor, de jeito nenhum o torna autoridade de coisa nenhuma.</p>
<p>Porém, se antes, a forma ingênua era ter uma única pontuação global do PageRank, em HITS teríamos sub-grafos pro domínio ou tópico, por exemplo, programação, gastronomia, viagens, cinema e cada vértice teria duas pontuações, de hub ou autoridade, por tópico. Portanto dá pra imaginar que é mais caro computar HITS do que PageRank. Claro, o que temos hoje num Google é uma amálgama dessas técnicas e muitas outras que surgiram ao longo do tempo. O que nos leva de volta a SALSA.</p>
<p>SALSA poderia ser considerado uma evolução da idéia de HITS pra análise de links, mas uma das idéias é não precisar navegar por todos os vértices e pontuar o grafo inteiro. Em vez disso ele implementa a idéia de andarilho aleatório ou random walker. É que nem pesquisas de opinião pública. Todo mundo tá acostumado a ver agências de fake news, cof, quero dizer, imprensa tradicional, fazendo pesquisas de opinião na rua pra dizer &ldquo;ah, metade dos brasileiros considera o governo atual uma bosta&rdquo;. Mas como eles sabem disso? Eu não lembro de ninguém me perguntando isso.</p>
<p>Essa é a mágica da estatística. Podemos fazer sampling, ou seja, pesquisar só uma fração da população. Quanto mais aleatório for esse sampling, melhor. Um coisa é perguntar pra 100 pessoas só de São Paulo, outra coisa é perguntar pra 4 pessoas de cada estado. Assumindo que escolhemos bem esse grupo, dentro de uma margem de erro, podemos afirmar que a população em geral tem essa ou aquela opinião, economizando ter que perguntar pros mais de 200 milhões de cidadãos do país. É uma puta economia de 200 milhões pra 100. Entendem?</p>
<p>Mesma coisa num grafo. Eu não preciso necessariamente andar o grafo inteiro, posso escolher um grupo aleatoriamente, em vez de usar o modo iterativo original do HITS, economizando muito processamento e tendo resultados que caem em margens de erro estatisticamente aceitáveis. Pra otimizar mais isso, ele também reorganiza os nós e vértices num grafo bipartido, que ajuda a separar os papéis de hubs e autoridades.</p>
<p>Posso separar nossos nós em dois conjuntos distintos: um com páginas que linkam pra outras páginas, os hubs, e outro grupo com páginas que recebem links, as autoridades. Não vale a pena eu detalhar hoje, mas gravem esses dois conceitos de random walk e grafo bipartido pra estudar depois se tiverem interesse.</p>
<p>Expliquei tudo isso porque o paper do Twitter menciona que eles desenvolveram um algoritmo de recomendação baseado em SALSA, que é um algoritmo de random walk na mesma família de HITS, originalmente desenvolvido pra ranking de pesquisa web. O algoritmo constrói um grafo bipartido de uma coleção de websites de interesse (hubs de um lado e autoridades de outro), e cada passo no SALSA navega por duas bordas - um pra frente e outro pra trás. - agora você sabe o que esse parágrafo do paper está dizendo.</p>
<p>E ele continua dizendo: nós adaptamos SALSA pra recomendação de usuários da maneira mostrada nesta figura. O lado de hubs na esquerda é populado com o tal círculo de confiança do usuário, os usuários que ele escolheu manualmente seguir. As &ldquo;autoridades&rdquo; no lado direito são populados com usuários que esses hubs interagem. Depois que esse grafo bipartido é construído, rodamos múltiplas iterações de SALSA, que assinala pontuações.</p>
<p>Daí os vértices do lado direito são ranqueados por essa pontuação e tratados como recomendações de usuários. Os vértices do lado esquerdo também são ranqueados e interpretados como similaridade entre usuários. Baseado no princípio da homofilia, que é só um nome bonito pra dizer que pessoas com interesses parecidos tendem a se agrupar mais, podemos apresentar esse resultado como recomendações, que antigamente era uma seção chamada &ldquo;similares a você&rdquo;.</p>
<p>Resolvi dar uma detalhadinha a mais até aqui pra tentar explicar como foi o raciocínio por trás da primeira versão do motor de recomendação, mas agora vou tentar andar um pouco mais rápido, porque esse foi o sistema WTF que não existe mais. Sim, toda essa pesquisa, experimentação, durou pouco tempo. Apesar de ser um sistema bom, que funcionou bem, ele ainda não é o que conhecemos hoje. E em 2012 eles começaram a perseguir o tal projeto Cassovary que mencionei antes.</p>
<p>Pra ter recomendações ainda melhores, eles queriam usar mais sinais dos logs de comportamento dos usuários. Comportamento de likes, retweets, replies, follow e tudo mais. Mas isso torna os metadados do grafo ainda maiores e a premissa da primeira versão, que era tentar manter tudo em memória, já não cabia mais. Daí veio a decisão de tentar usar Hadoop, em particular Apache Pig que, pra quem não sabe, é tipo um compilador que produz sequências de programas MapReduce.</p>
<p>Hadoop é uma plataforma de tecnologias criadas pra lidar com o problema de Big Data no auge da Bolha da Internet no começo dos anos 2000. Pra muita gente ainda hoje, o conceito de Big Data não é muito palpável, porque ninguém lida com isso no dia a dia. Varia de produto pra produto, mas quando estamos gerando coisa como gigabytes por dia de dados, terabytes por mês, estamos em território de Big Data. O que você faz, com um banco de dados que tem centenas de terabytes? Deita e chora, porque não importa quão bom seja seus índices e queries, sua aplicação vai ficar absurdamente lenta, só pelo tamanho.</p>
<p>Nessa ordem de grandeza estamos falando que não é possível manter todos os dados numa única máquina, mesmo sendo um servidor grande, seja por limitação física ou de custo. Daí a única solução é a estratégia de Napoleão: particionar, dividir esse tanto de dados em múltiplos servidores, e daí conquistar. Por isso a base do Hadoop começa com o HDFS, que é o Hadoop File System, um sistema de arquivos distribuído.</p>
<p>Agora o problema é o seguinte: na faculdade você aprendeu a lidar com dados todos no mesmo lugar. Por exemplo, digamos que você tem uma lista de produtos e preços e queremos achar o produto mais barato, como faz? Simples, escolhe um algoritmo qualquer de ordenação como um Quicksort ou Mergesort da vida, e pega o primeiro elemento que vai ser o mais barato. Pra fazer isso, precisamos iterar elemento a elemento da lista pelo menos uma vez. Mas e agora, e se estamos com 5 servidores e essa lista foi dividida por 5?</p>
<p>Intuitivamente, poderíamos pensar, beleza, vou ter que repetir a ordenação uma vez em cada máquina. Daí no final vou ter uma lista parcial com os 5 produtos mais baratos achados em cada um dos 5 servidores, daí faço uma última ordenação nessa lista e finalmente acho o mais barato. Cada um dos servidores vai rodar em um quinto do tempo do que se fosse a lista completa, mas no final, ainda tem pelo menos mais uma operação de ordenação extra e aí tenho o resultado. Então seria tecnicamente um pouco mais &ldquo;lento&rdquo; rodar em vários servidores separados, mas vai ser menos caro.</p>
<p>Isso de rodar um algoritmo em pedaços dos dados, mapear os resultados parciais e depois reduzir na resposta final, é o que, a grosso modo, chamamos de MapReduce, que é parte fundamental de como fazer processamento de dados em Hadoop. Novamente, não vou detalhar MapReduce então anote pra pesquisar mais depois. MapReduce ficou famoso porque, depois do PageRank, é como o Google conseguiu operar em larga escala e deixou todo mundo no chinelo no começo dos anos 2000.</p>
<p>Hoje em dia é como toda grande empresa de tecnologia funciona, mas 20 anos atrás, essa forma de pensar em processamento de dados foi fundamental pra possibilitar termos produtos que aceitam quantidades massivas de pessoas e transações, literalmente ultrapassando a população total de diversos países. Tem mais gente no Twitter do que a população dos Estados Unidos, metade da população do mundo tá em produtos da Meta.</p>
<p>Por isso, pra segunda versão do motor de recomendação, o Twitter deixou o FlockDB e WTF pra trás e perseguiu o tal projeto Cassovary pra rodar em cima do HDFS do Hadoop, usando MapReduce. Mas eles tiveram vários problemas. Mesmo em 2012 já era sabido que grafos não performavam bem em Hadoop. Isso porque a maioria dos algoritmos conhecidos pra processar grafos são iterativos, lembra que falei pra guardar essa informação?</p>
<p>Pra ilustrar. Pense nos diferentes algoritmos de ordenação que aprendeu na faculdade. Alguns professores cometem o pecado de não explicar porque existem tantos e porque foram criados. Por exemplo, meu professor na faculdade mandou a gente decorar lá Quicksort, Mergesort, mas não explicou que mergesort foi criado pelo lendário Von Neumann pro problema de listas de dados que não cabiam na memória RAM da época, então começavam gravados numa fita. Daí o algoritmo vai percorrendo os elementos da fita, sem estourar a RAM, e vai gravando ordenado numa segunda fita. Se tiver essa imagem de fitas na cabeça, mergesort finalmente faz sentido. Sem isso, é um algoritmo que não faz sentido existir.</p>
<p>Em 2012 ainda não existia o equivalente mergesort pra grafos que não cabem em memória e precisam ficar divididos em partições de HDFS distribuídos em múltiplos servidores. Hoje em dia já existe um suporte maior. Pense um Apache Spark com GraphX. Mas 10 anos atrás, era um uso inédito ainda. O próprio paper descreve que eles estavam cientes dessas desvantagens. O paper menciona que o gargalo era na materialização dos vizinhos dos vértices, que requeria um enorme shuffling de dados, que é pegar dados de diversos servidores pra formar o grafo necessário pro processamento.</p>
<p>Nessa parte do paper ele referencia outro paper, o &ldquo;Discovering Similar Users on Twitter&rdquo;, onde é explicado como exatamente eles conseguiram passar as barreiras de usar Hadoop, adaptando o algoritmo de PageRank personalizado pra ambiente distribuído. Como já expliquei, esse algoritmo é largamente utilizado pra computar similaridade entre nós em um grafo, mas é iterativo e requer data shuffling frequente. Quando falo data shuffling é tipo reembaralhar dados. Pensa como isso é difícil quando seus dados estão distribuídos em diferentes servidores.</p>
<p>Pra resolver, os autores do paper propõe uma modificação ao algoritmo de PageRank personalizado chamado &ldquo;One Pass PPR&rdquo;, que reduz o número de iterações e minimiza esse reembaralhamento pra cada iteração. Nesse caso, os dados são particionados baseados nos IDs dos usuários e processados numa única passada sobre os dados, usando uma combinação de pontuações local e global. O algoritmo também usa técnicas de caching pra evitar recomputar resultados intermediários, reduzindo o número de iterações totais.</p>
<p>Além disso os autores também propõe técnicas de compressão pra reduzir o tamanho do grafo e possibilitar processamento mais rápido em sistemas distribuídos como Hadoop. É muito detalhe pra tentar explicar hoje, mas esse é o resumão desse outro paper que também recomendo darem uma olhada depois.</p>
<p>É nele que se resolve o problema de conseguir dizer que quem segue um Lebron James tem alta probabilidade de seguir um Kobe Bryant ou quem segue uma Lady Gaga costuma seguir uma Taylor Swift. E é onde nasce o framework que é utilizado até hoje e que vou retornar pra explicar, sobre a arquitetura de geração de candidatos e modelo de aprendizado baseado em regressão logística e similaridade de cosseno pra achar usuários similares no Twitter.</p>
<p>Voltando ao Cassovary, é só uma parte da solução, é quem dá acesso aos dados dos grafos em Hadoop. A solução geral é chamada de RealGraph, que é uma representação composta do grafo de seguidores e grafos de interação induzidos através de logs de comportamento. Essa representação é construída de múltiplos pipelines de funcionalidades que são extraídas, limpas, transformadas e juntadas a partir dos logs crús. O RealGraph é armazenado em HDFS. E esse nome &ldquo;RealGraph&rdquo; vamos encontrar no código em Scala do repositório que foi liberado, como podemos ver no VS Code. Toda vez que ver &ldquo;RealGraph&rdquo;, provavelmente é isso.</p>
<p>Mas esse segundo motor de recomendação ainda tinha um grande defeito, só servia pra recomendar usuários similares pra você seguir e só atualizava poucas vezes ao dia, não era nem perto de ser tempo real. Funcionava, mas ainda não é a aba &ldquo;pra você&rdquo; que tem no Twitter hoje. Pulamos do WTF de 2010, pro Cassovary, Hadoop e RealGraph de 2012, e agora saltamos pra 2014 e um novo paper que descreve algumas novas estratégias pra particionar e organizar esses grafos. Como podem ver, não era um problema trivial de resolver.</p>
<p>No caso de Twitter, e até em sistemas de mensageria em geral, existe uma particularidade nos dados: você nunca precisa do grafo inteiro dos usuários. Por exemplo, pouca gente tá interessado em ver tweets de mais de 1 ou 2 dias atrás das pessoas que segue. Então mesmo que seu influencer favorito tweet todos os dias, o que seria um volume grande de tweets pro grafo, podemos limitar uma janela de 1 ou 2 dias pra todo mundo e lidar com um sub-grafo muito menor.</p>
<p>Pensando assim não precisamos lidar com operações como &ldquo;deletar&rdquo; um tweet do grafo, por exemplo. Basta pensar em expiração. É mais ou menos o mesmo pensamento por trás de caches como Memcached ou Redis da vida, que usamos hoje. Não pense em como apagar dados velhos, estabeleça uma janela de tempo e deixa expirar sozinho, que é boa prática de qualquer tipo de caching.</p>
<p>Não só isso, mas não precisamos guardar o timestamp exato de cada tweet no grafo. Isso vem da observação que a maioria das interações, como likes, retweets, replies, costumam acontecer muito próximo do momento que o tweet foi criado. Pra maioria de nós é interessante saber que o tweet foi postado 1 hora atrás, 5 horas atrás, 1 dia atrás, mas não interessa exatamente quando. É menos um dado pra ter em memória e mais uma economia.</p>
<p>Pensando em tudo que eles aprenderam até agora, iniciariam um terceiro motor de recomendação em cima de outro projeto que eles também tornariam open source, chamado de GraphJet. Ele mantém um grafo bipartido que registra integrações de usuários a tweets durante as mais recentes &ldquo;n&rdquo; horas, com uma API super simples só com as operações que eles sabiam que realmente iam precisar, sem fazer overengineering de criar um banco de dados de grafos genérico demais. Finalmente tinham experiência suficiente pra conseguir especializar a solução.</p>
<p>O resto do paper entra em detalhes sobre como foi a estratégia de mapeamento de IDs, estratégias de gerenciamento de memória pra balancear reembaralhamento e distribuição dos dados. Em resumo, eles conseguiram chegar num modelo onde só o processamento pra encontrar usuários e tweets recomendados ficou na casa abaixo dos 10 milissegundos, que é perto do ideal pra conseguir ter resultados em real-time, onde a maior parte dos gargalos era a comunicação com os diversos sistemas de filas, como o Apache Kafka que usam até hoje.</p>
<p>Esse paper é bem bacana e recomendo lerem com muita calma e anotando cada termo que não conhecem pra pesquisar depois. Ele descreve as três gerações de motores de recomendação dentro do Twitter até 2016. Não parei pra ver se tem papers mais novos que continuam essa história, mas graças a isso o código liberado que tem vários trechos que mencionam RealGraph ou GraphJet, agora devem fazer mais sentido. Entenderam? Sem esse tipo de pesquisa, só lendo o código liberado, ninguém nunca ia entender do que se trata. Precisa do contexto histórico.</p>
<p>E isso é só a parte do motor de recomendação, existem diversas outras funcionalidades no Twitter, por exemplo, o motor de pesquisa, que é quando você faz pesquisas por palavras-chave e ele retorna tweets com esse conteúdo, que nem uma pesquisa no Google. Na figura da solução do Cassovary, que puxa dados do HDFS, que vieram do FlockDB, pra cima tem fetchers que mandam pra um serviço chamado Blender.</p>
<p>Blender foi um front-end escrito em Java em cima do Netty, que é a grosso modo o equivalente a um Node.js pra Java. Por baixo usa o lendário Apache Lucene como motor de procura. Lucene é feito em Java e a gente usa faz anos. Toda vez que um site tiver um campo de pesquisa, e os resultados forem bons, pode ter certeza que tem Lucene por trás. O produto mais famoso que é construído em cima do Lucene é o Elastic, também conhecido como Elasticsearch, ou o Solr da própria Apache. Full text search, correção ortográfica, auto complete, tudo isso já é problema resolvido por Lucene, Solr ou Elastic.</p>
<p>Só encontrei um post no blog de engenharia do Twitter e outro post de um ex-programador que trabalhou no Twitter por volta de 2012, mas além do Blender existia um outro sistema relacionado à parte de pesquisa em tempo real, que é diferente do sistema de recomendação, chamado Earlybird. E eu menciono isso porque &ldquo;Earlybird&rdquo; é outro termo que aparece bastante no código que foi liberado recentemente. Então agora você sabe.</p>
<p>Aproveitando que comecei a falar de projetos open source do Twitter, na parte do paper onde ele explica o desenvolvimento da solução GraphJet, falei que tem uma seção que fala da estratégia de IDs. Isso gerou outro projeto open source chamado Snowflake, que eu não conhecia também. Mas em dados distribuídos, IDs únicos é um problema. Por exemplo, numa tabela de banco de dados MySQL, rodando na sua máquina local, você coloca um inteiro auto incremental e esquece. Cada nova linha vai ganhando um ID como 1, 2, 3 &hellip; 10, 11, 12 &hellip; 1001, 1002 e assim por diante.</p>
<p>Ids incrementais tem diversos problemas, um deles é segurança, mas o outro é conseguir particionar esses IDs. E quando eu tenho um banco de dados distribuídos em 2 servidores? Vou só dizer que no primeiro servidor vai de 1 a 1 milhão, no segundo servidor vai de 1 milhão a 2 milhões? Isso não escala. Se você for mais esperto vai lembrar, ah, podemos usar coisas como UUID ou GUID da Microsoft, IDs de 64 bits ou 128 bits aleatórios. Ou até espaços menores de 48 bits, como MAC address de placas de rede.</p>
<p>Quanto maior o ID, mais espaço vai ocupar em disco e quando carregar em memória. E se vamos desperdiçar esse espaço, o ideal é que não seja aleatório e sirva algum propósito. Daí temos IDs semi-aleatórios. No caso da estratégia do Snowflake o ID é um número aleatório prefixado com um timestamp, um identificador do servidor onde está, e uma sequência numérica aleatória. Dessa forma consigo agrupar tweets que foram criados num determinado período de tempo, tenho a localização no cluster de servidores, mas também sei que não vai ter colisão desse ID com outro ID em outro servidor. Quer dizer, até pode ter colisão mas a probabilidade é hiper baixa, perto de zero, na prática.</p>
<p>Menciono isso porque se procurar no VS Code, olha só, Snowflake aparece em diversos lugares do código. E achamos trechos que usam a função <code>timeFromIdOpt</code> onde passamos um ID de Snowflake e ele retorna um timestamp, daí podemos fazer operações, como ver se foi criado antes ou depois de determinada data. Se estudar bancos de dados distribuídos, vai ver como existem diferentes estratégias de IDs únicos pra diferentes necessidades.</p>
<p>Um banco de dados como Cassandra usa outra estratégia, chamada TimeUUID, que é muito similar, já que também tem um componente de timestamp embutido no ID, o que facilita fazer queries de períodos de tempo e análise de time series, séries de tempo e processamento de eventos. Um DynamoDB da Amazon tem um identificador único chamado de &ldquo;chave de partição&rdquo; que é usado pra particionar dados em múltiplos servidores no cluster. A chave de partição é combinado com uma &ldquo;chave de ordenação&rdquo;, que é usado pra ordenar os dados dentro de uma partição.</p>
<p>Sem fugir demais do assunto, mas já que falei disso, preciso pelo menos explicar outro conceito. Em bancos de dados distribuídos, um problema seria se um servidor ficar mais cheio que outros servidores. Idealmente queremos balancear o volume de dados entre cada servidor. Uma das formas de fazer isso é o que chamamos de &ldquo;Consistent Hashing&rdquo;. Esse algoritmo mapeia chaves de partição, como o Snowflake do Twitter ou TimeUUID do Cassandra em nós virtuais num anel, um ring. Cada nó virtual representa um servidor físico no cluster.</p>
<p>À medida que novos nós são adicionados ou removidos do cluster, os ranges de partição são redistribuídos por todos os nós virtuais, garantindo distribuição uniforme das chaves de partição. Novamente, é um problema bem conhecido e bem resolvido nos mais diversos bancos de dados distribuídos. Eu lembro quando líamos papers da Amazon sobre isso lá por 2008 e 2009. Mas pra um estudante de ciência da computação hoje, continua sendo um problema fascinante de entender, já que o conceito é bem simples. Um ID é muito mais do que só uma etiqueta qualquer. Ela serve pra facilitar pesquisas e pra facilitar balanceamento de sistemas distribuídos.</p>
<p>E pra terminar essa parte de open source, tem outros dois projetos que são mencionados que acho importante explicar. O primeiro se chama Thrift e o segundo é Protocol Buffers ou Protobufs. Acho que muitos de vocês já devem ter ouvido falar pelo menos de Protobufs. Bem a grosso modo, pense micro-serviços ou endpoints de APIs hoje. A maioria de vocês deve estar acostumado a ter endpoints que se comunicam ou simplesmente cospem dados em formato JSON. É o hello world de APIs em todo tutorial e curso. Só que JSON não é a única forma e pra muitos casos, é a pior opção.</p>
<p>JSON é um formato excelente pra endpoints de APIs que seres humanos como eu ou você vamos manipular. É indicado pra coisas como APIs de ecommerce, onde queremos puxar coisas como principais ofertas, pra mostrar no nosso blog. Mas pra comunicação entre sistemas, por exemplo, o backend do ecommerce conversar com meios de pagamento ou logística. Ou mesmo pra comunicação entre micro-serviços dentro da mesma empresa, é um dos protocolos mais porcaria que existem.</p>
<p>Como um exemplo bem, mas bem tosco, imagine este pequeno JSON de um produto de ecommerce. Quem já trabalhou com ecommerce sabe que é bem maior que isso, mas pra ilustrar aqui no video é suficiente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;product_id&#34;: &#34;PS5-001&#34;,
  &#34;name&#34;: &#34;PlayStation 5&#34;,
  &#34;description&#34;: &#34;The PlayStation 5 is the next generation of gaming consoles, with advanced graphics, haptic feedback, and lightning-fast load times.&#34;,
  &#34;price&#34;: 499.99,
  &#34;availability&#34;: true,
  &#34;images&#34;: [
    {
      &#34;url&#34;: &#34;https://example.com/images/ps5-001-front.jpg&#34;,
      &#34;type&#34;: &#34;front&#34;
    },
    {
      &#34;url&#34;: &#34;https://example.com/images/ps5-001-back.jpg&#34;,
      &#34;type&#34;: &#34;back&#34;
    }
  ]
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>JSON é um protocolo texto, onde o schema vai embutido junto com cada registro. Como falei, isso facilita pra pessoas comuns conseguirem ler sem muita dificuldade. Agora imagine um schema de Protobuf assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>message Product {
  string product_id = 1;
  string name = 2;
  string description = 3;
  float price = 4;
  bool availability = 5;
  repeated Image images = 6;
}

message Image {
  string url = 1;
  string type = 2;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note, ele define exatamente que tipo de dados, portanto qual o tamanho exato, de cada campo de dados. Dessa forma não preciso de coisas como aspas, vírgulas, nada disso, posso concatenar um valor atrás do outro, porque o tamanho vai ser fixo. No final, o mesmo JSON, convertido em Protobuf vai gerar um linguição binário que, em hexadecimal, seria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>0a 07 50 53 35 2d 30 30 31 12 10 50 6c 61 79 53 74 61 74 69 6f 6e 20 35 1a 42 54 68 65 20 50 6c 61 79 53 74 61 74 69 6f 6e 20 35 20 69 73 20 74 68 65 20 6e 65 78 74 20 67 65 6e 65 72 61 74 69 6f 6e 20 6f 66 20 67 61 6d 69 6e 67 20 63 6f 6e 73 6f 6c 65 73 2c 20 77 69 74 68 20 61 64 76 61 6e 63 65 64 20 67 72 61 70 68 69 63 73 2c 20 68 61 70 74 69 63 20 66 65 65 64 62 61 63 6b 2c 20 61 6e 64 20 6c 69 67 68 74 6e 69 6e 67 2d 66 61 73 74 20 6c 6f 61 64 20 74 69 6d 65 73 2e 20 32 25 15 01 20 f3 26 9d 3a 40 28 01 32 2b 0a 1f 68 74 74 70 73 3a 2f 2f 65 78 61 6d 70 6c 65 2e 63 6f 6d 2f 69 6d 61 67 65 73 2f 70 73 35 2d 30 30 31 2d 66 72 6f 6e 74 2e 6a 70 67 22 04 66 72 6f 6e 74</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É isso que a nova API com Protobuf vai cuspir. Como disse antes, ao contrário do JSON, esse formato é 100% hostil pra um ser humano conseguir ler, porém, é 100% perfeito pra um computador ler. A fase de converter o JSON numa estrutura de dados é muito mais simples. Um protobuf é muito próximo de uma struct de C, só que mais genérico pra funcionar em qualquer linguagem. O JSON do exemplo é um string que pesa quase 200 bytes. Já esse linguição, que contém exatamente os mesmos dados, pesa menos de 80 bytes. Mesmo nesse exemplo super tosco, estamos falando de uma economia de 2.5 vezes na transferência de rede, sem contar a economia de processamento.</p>
<p>Lembra que falei que o Twitter lida com milhões de tweets todo dia? Imagine se internamente, em todo sistema, de pesquisa, banco de dados distribuído, sistema de recomendação, filas de processamento, ficasse trafegando tudo em JSON. Terabytes e terabytes de banda sendo desperdiçados. Por isso o Twitter adotou o equivalente a Protobufs do Google, o Thrift do Facebook. Thrift e Protobuf é quase a mesma coisa.</p>
<p>A única pergunta que você poderia ter é: por que o Twitter não usou o Protobuf do Google então? Em vez de reinventar a roda? De fato, Protobufs foram criados antes, sendo usados dentro do Google desde pelo menos 2001, mas só foi lançado publicamente em 2008. Nesse meio tempo surgiu o Facebook, que teve problemas similares pra resolver e inventaram o Thrift, que lançaram em Abril de 2007. Por isso o Twitter só tinha a opção do Thrift em 2007. Mas pra novos projetos, especialmente que interagem com componentes como PyTorch ou Tensorflow, que também veio do Google, eles passaram a usar Protobufs e você vai achar os dois no código.</p>
<p>Thrift e Protobufs são protocolos de serialização de dados. Esse tipo de protocolo é ideal pra comunicação entre micro-serviços. Só que diferente de JSON ou XML, a forma de fazer essa comunicação não é via HTTP, como num web service, e sim via RPC ou Remote Procedure Call, que formaliza como um serviço chama uma função, ou procedure, de outro serviço. No caso do Google eles tem o gRPC que é um framework de RPC que tem como objetivo prover comunicação eficiente e escalável entre serviços na rede.</p>
<p>Twitter não usou gRPC. Finagle é o equivalente de gRPC feito pelo Twitter, que usa Thrift pra serialização de dados em vez de Protobufs. Tem os mesmos objetivos de lidar com comunicação de alto throughput, baixa latência, na rede. Finagle é implementado em cima de Netty também, usando muita funcionalidade de programação assíncrona com Futures de Scala, que é o equivalente Promises de Javascript de hoje. E novamente, o Twitter Finagle foi lançado em 2011, o gRPC do Google só sairia por volta de 2015. O Twitter estava tendo que inventar tecnologias que não existiam ainda, por isso é um case fascinante.</p>
<p>Mas claro, Google sendo um nome muito maior e mais reconhecido que Twitter, matou os projetos Thrift e Finagle. Num projeto moderno, você deveria escolher Protobufs e gRPC ou alternativas mais modernas. Hoje, o Thrift ainda existe como Apache Thrift mas da Apache temos outras alternativas como Apache Avro, que foi criado mais pro cenário de streaming de dados serializados. Ou seja, quando você abre um streaming de um serviço pra outro serviço e trafega registros serializados com Avro entre eles.</p>
<p>Apache é um nome muito reconhecido pra quem é de Java e também no espaço de data sciences e machine learning. Quem lida com Kafka e é de Java, deve ter usado Avro como opção de serialização pra jobs em vez de Protobufs ou JSON. JSON é mais pra linguagens menores, como Javascript ou mesmo Ruby e PHP. Protobufs são mais usados em soluções que envolvam tecnologias do Google, como em Android. De qualquer forma, vale a pena estudar todas. Não ache que o mundo acaba em JSON ou XML. Tem coisa muito melhor e muito mais eficiente que é usado por sistemas realmente de grande volume. Pro ecommerce da esquina, de fato, tanto faz.</p>
<p>Falando em Java, acho que vale a pena eu relembrar a parte inicial dessa história. O que aconteceu justamente entre 2007 a 2010. Essa era a época que eu era mais participativo na comunidade Ruby on Rails e, claro, Twitter era um dos exemplos de maior sucesso de Rails. Mas aí veio a notícia bombástica: o Twitter declarou que estava migrando de Ruby pra Scala. E daí veio o meme que não morre de &ldquo;Rails não escala&rdquo;.</p>
<p>A forma como o Alex Payne, um dos principais desenvolvedores do Twitter descreveu, ou melhor, não descreveu os problemas que estavam enfrentando, tudo que eu expliquei até aqui, causou má interpretação e muita gente falando muita merda. Eu conversei com outro dos principais desenvolvedores daquela época, Blaine Cook, em junho de 2008. E mesma naquela época, eu também não tinha uma visão completa dos acontecimentos. Depois leiam a entrevista original que está no meu blog.</p>
<p>Por exemplo, da forma como Alex Payne falou, deu impressão que ele estava dizendo que Ruby on Rails não suportava conectar com múltiplos bancos de dados, o que não era verdade. Sim, não havia uma chavinha fácil, mas era basicamente uma linha extra que precisava ser colocada na classe pai de todos os ActiveRecords e dava pra selecionar onde fazer a conexão. Eu e vários outros escrevemos tutoriais de como fazer isso.</p>
<p>Mas a verdade é que o problema não era esse. Ninguém de fora do Twitter tinha muita noção do volume de dados que eles estavam tendo que lidar. E isso era 2007. Lembrem-se, a Amazon AWS tinha lançado não fazia um ano. Produtos que hoje são arroz com feijão, como SQS, DynamoDB, Route53 nem tinham sido lançados ainda. Firebase ainda não existia. Porra, coisas como Redis ainda não tinha sido inventado. GitHub ainda não tinha sido inventado.</p>
<p>A verdade é que particionar dados, organizar um MySQL em shards, e conseguir fazer uma arquitetura coesa em cima disso, não era trivial, e o Twitter estava numa fase de crescimento rápido. Não é que Ruby on Rails não escalava pra escala do Twitter. Nada em Python escalaria. Nada em PHP escalaria. Node.js ainda não tinha sido inventado também, mas também não escalaria. Entenda, havia poucas opções.</p>
<p>Possivelmente a opção mais completa teria sido começar a implementar um banco de dados de grafos em cima de Erlang, algo inspirado no banco de dados Mnesia que vem no OTP de Erlang. Porém, já viram Erlang? Hoje ele ainda é pouco usado, e em 2007 menos ainda. É verdade que grandes sistemas de telecomunicações já usavam fazia anos, mas era um salto bem grande pra uma tech startup tomar.</p>
<p>Quais as alternativas? Eles precisavam de uma linguagem pra escrever componentes como sistemas de filas, coisas no nível de Kafka, lembrando que Kafka só seria lançado em 2011. Coisas no nível de Cassandra, que também só seria liberado publicamente pelo Facebook em 2008. Neo4J talvez fosse o componente mais próximo de se encaixar nos problemas do Twitter, mas era super novo, tinha sido lançado em 2007.</p>
<p>Entendem? Imagine você sendo um desenvolvedor do Twitter, vendo milhares, centenas de milhares de tweets sendo postados todos os dias, e tendo que lidar com grafos de usuários e tweets, sendo que algoritmos de grafos nunca tinham sido testados com volumes desse tipo. Numa época onde toda tecnologia de Cloud e NoSQL e Devops ainda não tinha sido inventado, onde linguagens mais comuns como Ruby, Python, PHP, Javascript, não estavam ajudando e programar em C++ ou Java, parecia ser um passo pra trás.</p>
<p>Elixir não tinha sido inventado ainda. Go não tinha sido lançado ainda. Rust não tinha sido inventado ainda. LLVM ainda era uma grande novidade, já que a Apple tava só começando a mover suas ferramentas e linguagens de GCC pra Clang, como conto no episódio da Apple e GPL. Mas havia uma única linguagem que talvez pudesse fazer sentido, a linguagem Scala, que tinha sido lançado já em 2001, pelo visionário Martin Odersky.</p>
<p>Odersky é um desses gênios incompreendidos, tanto que tirando povo da área de Java, duvido que qualquer um aqui já tenha ouvido falar dele. Mas deveriam. O cara é Ph.D de ciência da computação pela ETH de Zurique e trabalhou sob a supervisão de ninguém menos que Niklaus Wirth, o inventor de várias linguagens como Modula-2 e Pascal, e o autor do livro de estruturas de dados e algoritmos que eu usei na faculdade.</p>
<p>Odersky participou e fez contribuições no mundo Java desde a versão 1.1, tendo trabalhado no compilador, na implementação de Generics, e algumas linguagens experimentais como a Pizza e Funnel, que influenciaram o design do Java.</p>
<p>Muito antes de programação funcional ficar hypezinho de hipster sem graça, como é hoje, no começo do século, ele já vislumbrava uma linguagens que destilasse o melhor da orientação a objetos, como usar traits, que são como protocolos de Objective-C, em vez de herança excessiva, mesclado com conceitos de programação funcional. Scala já tinha higher order functions, já usava tipo Opcional, evitando os famosos NullPointerException, pattern matching e tudo que se vê em linguagens mais modernas de hoje.</p>
<p>Então era de fato a linguagem mais moderna que se podia escolher em 2007, mas rodando em cima da JVM, que era a virtual machine mais madura que existia. Dava pra criar aplicações que só seriam possíveis em C++ com uma fração da dor de cabeça. Pra isso custaria um pouco mais caro em uso de recursos do que se fosse puramente nativo, mas a JVM naquele ponto oferecia um ecossistema que ninguém mais tinha.</p>
<p>E o objetivo do uso de Scala não era pra fazer front-end-zinho, era pra fazer os diversos componentes que eu expliquei que ainda não existiam. Começou com o projeto open source Starling, que é mais ou menos um mini Kafka, alguns anos antes do Kafka. Inventaram o RealGraph e GraphJet antes de um banco de dados de grafos maduro existir. Utilizaram coisas como Hadoop, que também faz parte do ecossistema Java. Nada disso seria possível sem ser, ou em Java ou em C++.</p>
<p>Hoje em dia eles teriam muito mais opções, como o próprio Elixir, Go ou Rust, e se quisessem arriscar em linguagens mais exóticas teriam D, Nim, Zig, talvez Swift, Kotlin. Mas de novo, era 2006 pra 2007 e o relógio tava correndo. Em retrospecto, foi uma decisão muito acertada. E é por isso que o código que foi liberado é 2 terços Scala e Java. Muito das pesquisas que o Twitter fez, junto com trabalhos do Facebook, Google, Apple, Amazon, gerou o atual ecossistema de linguagens, ferramentas e plataformas pra lidar com Big Data, Data Science, Machine Learning, culminando com Deep Learning e a atual geração de inteligência artificial que todo mundo tá babando. Mas foi assim que as coisas começaram, no início do século.</p>
<p>Agora que temos uma noção melhor do contexto histórico, dos projetos e papers de pesquisa gerados, podemos começar a entender melhor o código liberado. Esse código é um conjunto de diversos sub-projetos. Se abrirmos o primeiro README na raíz do repositório, eles descreveram o que são cada um desses projetos. Por exemplo, SimClusters-ann é o serviço que retorna candidatos de recomendações de tweets dado embeddings de um cluster de similaridade.</p>
<p>Esse serviço usa o algoritmo de similaridade aproximado de cossenos. Já vou explicar isso. Segundo o README, um job constrói o mapeamento entre SimClusters e Tweets. O job grava os top 400 tweets de um SimCluster e os top 100 SimClusters pra um tweet. Pontuação de favoritos e pontuação de seguidores são dois tipos de pontuação que um tweet pode ter. Os top 100 SimClusters baseados nessas pontuações é que chamam de Tweet SimCluster Embedding.</p>
<p>A similaridade de cosseno entre dois Embeddings apresenta o nível de relevância de dois tweets num espaço de SimClusters. A pontuação varia de 0 a 1. A alta pontuação de similaridade de cosseno, maior que 0.7 significa que os usuários que gostam de dois desses tweets compartilham os mesmos SimClusters.</p>
<p>Similaridade de cosseno é um conceito de Álgebra Linear pra medir quão similar são dois vetores em termos de direção e magnitude. Eu gosto desse conceito porque expliquei isso desde pelo menos 2015 até minha última palestra pública em 2019. Quem assistiu minha palestra &ldquo;Restrição == Inovação&rdquo; vai se lembrar disso, e vou aproveitar pra usar os slides que eu usava nessa palestra.</p>
<p>Comece visualizando a página inicial do Google, o campo onde se digita palavras-chaves. Podemos digitar &ldquo;apple&rdquo; e recebemos páginas indexadas da web que tem essa palavra. Um amador que acabou de aprender SQL poderia pensar que é alguma coisa parecido com &ldquo;select urls from pages where text like &lsquo;%apple%&rsquo;&rdquo;.</p>
<p>Essa é a solução que faríamos no começo dos anos 90, antes de Larry Page e Sergey Brin inventarem PageRank, antes de Jon Kleinberg inventar HITS. Essa linha de SQL de fato vai achar todas as páginas onde aparece a palavra &ldquo;apple&rdquo;, mas não vai saber como ordenar: qual delas é mais relevante e deveria estar no topo da lista? Pra saber relevância, precisamos de Álgebra Linear.</p>
<p>Tudo depende de como você enxerga o que é uma página web. Todo mundo enxerga um documento, uma coleção de palavras, que é o texto desse documento. Digamos que nosso banco de dados só tem 3 documentos indexados, como neste slide. Pra simplificar bastante, vamos assumir que nosso vocabulário é bem limitado e os documentos só tem as palavras apple e banana. O documento 1 tem 4 apples e 1 banana, documento 2 tem 3 bananas e 1 apple, e documento 3 tem 5 bananas e nenhum apple.</p>
<p>Podemos representar esses documentos como vetores, como  neste slide. Próximo passo, criamos um índice, que é uma lista que diz em quais documentos aparece a palavra apple e quais aparece banana. Pense em algo como o índice do seu banco de dados MySQL ou Postgres ou SQL Server da vida. Agora vamos digitar a palavra &ldquo;apple&rdquo; no campo de pesquisa desse nosso Google de mentira.</p>
<p>O truque começa em representar essa pesquisa, essa query, também com um vetor. No caso apple sendo 1 e banana sendo 0. É como se fosse um documento também. Pelo índice, podemos descartar o documento 3, já que apple não aparece nenhuma vez. Mas ainda temos o documento 1 e o documento 2. Qual deles é o mais relevante pra aparecer no topo da lista? De bater o olho sabemos que é o documento 1, porque aparecem a palavra apple 4 vezes, e no documento 2 só aparece 1 vez. Mas como um algoritmo conseguiria computar isso?</p>
<p>Pra isso desenhamos esses vetores num espaço vetorial, um Vector Space. No caso só temos duas dimensões, porque só temos duas palavras no nosso vocabulário. Apple é o eixo X e banana é o eixo Y. Então o vetor que representa o documento 1 seria assim: 4 unidades pra direita no eixo X, e 1 unidade pra cima no eixo Y. Já o documento dois seria só 1 unidade pra direita no eixo X mas 3 unidades pra cima no eixo Y. Finalmente o vetor da query é só uma unidade pra direita no eixo X, só 1 palavra apple, entenderam?</p>
<p>O lance com vetores é que agora temos ângulos entre o vetor de pesquisa e os vetores de documentos. E pra calcular a similaridade de cosseno usamos dot product, que é produto escalar. Funciona assim: vamos multiplicar apple com apple, banana com banana e somar os dois. Então vetor de pesquisa é 1 e 0, 1 apple e 0 bananas e vetor do documento 1 é 4 apples e 1 banana então a conta seria 1 vezes 4 mais 0 vezes 1, que dá 4, esse é o produto escalar.</p>
<p>Daí fazemos a mesma coisa entre vetor de pesquisa, de novo, e o vetor do documento 2, então o produto escalar vai ser, 1 apple vezes 1 apple mais 0 banana vezes 4 bananas, dando 1. Como o produto escalar 4 do documento 1 é maior que o 1 do documento 2, sabemos que o documento 1 tem mais similaridade com a pesquisa e, portanto, é o mais relevante pra aparecer no topo da lista.</p>
<p>Esse exemplo é super trivial porque só usamos duas palavras, então temos só duas dimensões. Mas vamos complicar. E se tivéssemos um novo documento indexado que tenha a palavra coco? O vetor dele poderia ser 1 apple, 0 banana e 2 cocos. Todos os outros vetores precisam ser atualizados pra conter coco, mas no caso tem 0 cocos então não muda nada. Pra representar o espaço vetorial, precisamos de uma nova dimensão Z. O vetor desse novo documento seria desenhado com 1 unidade pra direita no eixo X e 2 unidades pra baixo no eixo Z.</p>
<p>E podemos calcular o produto escalar entre a pesquisa e esse novo documento também. Em geometria, a gente consegue desenhar no papel até 3 dimensões, mas claro, o vocabulário de uma língua como português é muito mais que só 3 palavras. Num dicionário como o Aurélio, vai ter mais de 500 mil palavras. Portanto podemos ter potencialmente 500 mil dimensões nesse espaço vetorial. Na realidade vai ser a quantidade de palavras únicas que tem num documento, ou página web de verdade. O script deste episódio, por exemplo, tem mais de 3 mil palavras únicas. É um vetor de 3 mil dimensões.</p>
<p>Felizmente, produto escalar é uma operação muito rápida de se calcular em qualquer computador moderno, só somas e multiplicações simples. E é assim que funciona a base de todo tipo de algoritmo de relevância e recomendação. É isso que tem por baixo da biblioteca Apache Lucene que falei antes. E se olhar a documentação de um Elastic vai ver que menciona VSM, ou Vector Space Model, que é esse gráfico que vim mostrando, muito maior do que os 3 do exemplo. E é isso que esse projeto SimClusters vai fazer, só que com vetores de usuários e tweets.</p>
<p>No caso do Twitter, esse primeiro projeto se chama SimCluster-ANN. SimCluster é similarity cluster, e esse &ldquo;ANN&rdquo; provavelmente significa &ldquo;Approximate Nearest Neighbor&rdquo; ou vizinho mais próximo aproximado. Em machine learning e data mining, pesquisa de vizinho mais próximo é o problema de encontrar o ponto de dado mais próximo em um espaço de muitas dimensões, dado um certo ponto de pesquisa, onde soluções exatas são inviáveis pelo tamanho do conjunto de dados.</p>
<p>Algoritmos de vizinho próximo aproximado, como Locality-sensitive hashing ou LSH e métodos baseados em árvore como KD-trees, oferecem uma forma eficiente de resolver esse problema fazendo um trade off de algum grau de precisão por uma melhoria significativa em performance. Então é mais uma otimização do Twitter pra ganhar performance, mais ou menos como o random walk no algoritmo de SALSA que expliquei antes.</p>
<p>Nesse sub-projeto, o conceito importante são embedding spaces, que tem como objetivo responder a pergunta &ldquo;quais tweets e usuários são similares aos meus interesses?&rdquo;. Embedding funciona gerando representações numéricas dos interesses dos usuários e conteúdo de tweets. Daí podemos calcular a similaridade entre quaisquer dois usuários, tweets ou pares de usuários e tweets num espaço de embedding.</p>
<p>Justamente um dos espaços de embeddings mais úteis do Twitter são os SimClusters. SimClusters descobrem comunidades ancoradas por um grupo de usuários influentes usando um algoritmo customizado de factorização de matrizes. Existem umas 145 mil comunidades, que são atualizadas a cada três semanas. Usuários e tweets são representados em espaço de comunidades e podem pertencer a múltiplas comunidades. Elas podem ter algumas milhares até milhões de usuários. Segundo o blog de engenharia, essa imagem representa algumas das maiores comunidades.</p>
<p>Sem entrar em muito mais detalhes, vamos bater o olho em alguns outros projetos descritos naquele arquivo README. Veja um aqui chamado &ldquo;real-graph&rdquo;. Eu falei de RealGraph antes. Veja este &ldquo;Tweepcred&rdquo;, que é o algoritmo de PageRank pra calcular reputação dos usuários. Temos esse &ldquo;recos-injector&rdquo; que é descrito como um processador de eventos em streaming pra construir os streams de entrada pra serviços baseados em GraphJet. Já sabemos o que é GraphJet, mas &ldquo;recos&rdquo; eu acho que significa &ldquo;recomendações&rdquo;, então é um injetor de recomendações pra GraphJet.</p>
<p>Pulando lá pra baixo temos esse &ldquo;light-ranker&rdquo; que é um ranqueador mais leve usado pelo índice de pesquisa, o tal Earlybird que mencionei antes, que é um projeto legado e parece que ainda é usado; o componente construído em cima de Apache Lucene. O heavy-ranker está no outro repositório &ldquo;the-algorithm-ml&rdquo;, que o blog post do Steven, que falei lá no começo, menciona e segundo o README é o responsável pela aba &ldquo;Pra você&rdquo; e tem aquela lista de pesos.</p>
<p>Nesse ponto da cadeia, depois da fase de candidate sourcing, temos uns 1500 candidatos que podem ser relevantes. Pontuação serve pra prever a relevância de cada tweet candidato e é o sinal primário pra ranquear tweets na timeline. Nesse estágio, todos os candidatos são tratados igualmente. O ranqueamento sai de um rede neural de uns 48 milhões de parâmetros que é continuamente treinada com interações de tweets pra otimizar o engajamento positivo (como likes, replies e retweets), segundo o blog de engenharia do Twitter.</p>
<p>Sendo bem honesto, eu não tenho certeza de como exatamente isso funciona ainda. Olha o diagrama que tem no próprio arquivo README que lista os projetos. Os dados vem dessas caixinhas intituladas &ldquo;Grafo Social&rdquo;, &ldquo;engajamento de tweets&rdquo; e &ldquo;dados dos usuários&rdquo;. Daí temos features, como o GraphJet, SimClusters, TwHIN que é outro projeto no repositório the-algorithm-ml, e serve pra pré-treinar os tais embeddings.</p>
<p>Daí temos o RealGraph, TweepCred e Trust &amp; Safety, que parece ser um projeto de tensorflow pra treinar modelos pra filtrar coisas como conteúdo pornográfico, adulto, ou o que ele considera como &ldquo;tóxico&rdquo;. Não sabemos exatamente porque não foi liberado o modelo e nem os dados de treino. É um projeto bem pequeno e quem entende de Tensorflow poderia dizer um pouco mais, mas não tem muito pra se ver.</p>
<p>Continuando, temos essa coluna que ele chama de Home Mixer. Daí temos 3 fases: uma que ele chama de candidate sourcing, que é literalmente &ldquo;escolher os candidatos&rdquo;, candidatos de recomendação. Esses candidatos passam pelo tal heavy ranker, que parece ser o modelo de machine learning via PyTorch. O resultado disso passa pra etapa final de heurísticas e filtros.</p>
<p>Então, pra se chegar nos tweets recomendados na aba &ldquo;Pra você&rdquo;, na etapa de sourcing já acontece diversos filtros pra selecionar os candidatos de tweets que você pode gostar de ver. Esses &ldquo;filtros&rdquo; passam pelos projetos de SimCluster, RealGraph, T&amp;S, Tweepcred, os GraphJets e são mesclados pelo projeto cr_mixer. Isso vale comentar. Os tweets recomendados, mais ou menos, são metade de tweets gerados pelas pessoas que você segue, o que ele chama de &ldquo;In-Network&rdquo; e a outra metade é de tweets de pessoas que você não segue, que ele chama de &ldquo;Out-of-Network&rdquo; e quem puxa isso é esse projeto cr-mixer.</p>
<p>Dado esse conjunto inicial de tweets, In e Out of network, aí vem a parte de recomendação propriamente dito usando o light ranker, o projeto Earlybird, e daí o heavy ranker. Mas antes de mandar pra você, ainda passa por um pipeline de filtros. Pelo projeto home mixer propriamente dito, esse visibility-filters que é onde está implementado a política de censura do Twitter.</p>
<p>Por último, tem esse projeto Navi, que parece mais recente, escrito em Rust, mas é super pequeno e não faz muita coisa. Parece mais um adaptador pra falar com os componentes de Tensorflow e PyTorch e um tal de Onnx, que eu não conhecia, mas parece que significa Open Neural Network Exchange, que é um formato open source pra representar modelos de machine learning. Foi originalmente desenvolvido pela Microsoft e Facebook em 2017 pra facilitar interoperabilidade entre diferentes frameworks de deep learning, por exemplo, entre PyTorch e Tensorflow. Mas realmente não sei como ele é usado neste projeto ainda.</p>
<p>Se tiverem interesse em saber mais, cada um desses sub-projetos tem um README também, então recomendo que comecem por eles. Mas como disse no começo, se tentar rodar <code>bazel build</code> em qualquer diretório, ele vai reclamar da falta de um arquivo de Workspace. Então, até o Twitter liberar isso, ou alguém se dar o trabalho de gerar na mão, não temos como compilar e nem de tentar rodar nada.</p>
<p>Por isso que é meio chato, porque mesmo lendo essas coisas todas na documentação e no próprio código, não temos como testar. Basta um if que a passamos batido e talvez algum desses componentes nem rodem. É muito difícil saber o comportamento exato dum sistema grande desses sem ter uma massa de dados de entrada pra ver o que realmente é cuspido na saída.</p>
<p>De qualquer forma, do ponto de vista de organização, além da falta de arquivos pra fazer build, também senti muita falta de arquivos de testes automatizados. Não tem como saber se eles tiraram antes de abrir em público, se ainda vai ser liberado, ou se realmente não existem. Espero que existam, porque dar manutenção num sistema grande desses, sem testes, seria uma grande burrice.</p>
<p>Se eles tivessem liberado os arquivos de testes, nós teríamos uma idéia muito melhor de como esse sistema de comporta, mesmo sem poder compilar e rodar. Testes servem como se fosse uma documentação viva de um sistema. Teria sido muito mais fácil confirmar tudo que analisamos.</p>
<p>Mas no geral, minha opinião é que o código Scala parece muito bem feito, dado o contexto que se trata de um legado de mais de 10 anos. Eu esperaria algo muito mais bagunçado, muito mais macarrônico. Comparado com o código do Twitch, que eu analisei em outro video quando foi vazado, eu preferiria muito mais trabalhar neste código do que no outro.</p>
<p>O básico tá bem feito: métodos curtos, baixa complexidade ciclomática, nomenclatura bem feita, nada de variáveis &ldquo;x&rdquo; ou &ldquo;y&rdquo; que não dá pra saber do que se trata. Pouca coisa hard-coded, tudo organizado em arquivos de configuração, e até que bem comentados onde precisa. Não é comum ver projetos legados com esse nível de qualidade. Não é nem de longe perfeito, mas até que me surpreendeu.</p>
<p>Obviamente, esse repositório é só um espelho incompleto do repositório de verdade, que permanece fechado dentro do Twitter, sob confidencialidade. O código que foi liberado também não pode ser usado por nenhum projeto fechado sem liberar as modificações feitas em cima desse código. É isso que a licença AGPL que eles colocaram significa: eles não querem que ninguém adicione em outro projeto e mantenha fechado.</p>
<p>Depois que soltaram esse código, um bando de imbecis ficou spamando a seção de Issues, mas teve um ou outro pull request que eles aprovaram já, nada significativo. Só a remoção daquele código de métrica que tinha o &ldquo;author_is_elon&rdquo; que imbecis adoraram ficar fazendo meme. Não teve nenhuma contribuição significativa ainda e nem espero que vá ter, até que eles liberem os arquivos de Workspace do Bazel.</p>
<p>Eu não tenho muito senso de humor pra memes idiotas e gente que acha bacana ficar pichando uma seção que deveria ser séria como a área de Issues. Quer fazer meme idiota, fica no Reddit ou no 4chan. Não tem o mínimo de etiqueta, não participa. Participação desse tipo é só idiota, não tem nenhum valor e não contribui com absolutamente nada. É só certificado de idiota mesmo.</p>
<p>Ao longo de mais de 10 anos, o Twitter fez sua parte e contribuiu tecnicamente. Os papers que falei, os projetos open source, os diversos blog posts de engenharia, inspiraram muitos outros projetos a tentar arquiteturas e tecnologias que, na época, sequer existiam ou se existiam, ninguém sabia se funcionava. Independente da sua opinião política, que é irrelevante, o Twitter certamente contribuiu ordens de grandeza mais do que qualquer um dos engraçadinhos que não sabem respeitar um trabalho de engenharia.</p>
<p>Ter acesso a esse código não me torna mais fã do Elon Musk, mas só serve pra confirmar muitas coisas que eu já sabia sobre os primeiros engenheiros do Twitter: eles foram muito bons, especialmente pra época. E pra vocês que são iniciantes, aproveitem a oportunidade pra fuçar tanto o código quanto os papers que eu citei. É assim que um produto em produção evolui. Só nesse sistema de recomendações, vimos 3 gerações diferentes de soluções, cada uma gerando mais experiência pra fazer o próximo ser melhor. Código de produção de verdade, de um produto que tem uso, nunca acaba.</p>
<p>Você que é empreendedor querendo entender mais de programação, entenda: se seu código não está evoluindo, é porque seu produto é irrelevante. O Twitter existe desde 2006, e o código nunca chegou numa versão que todo mundo tirou a mão e falou &ldquo;tá perfeito, não precisa mexer mais&rdquo;. O mesmo acontece com qualquer produto, ecommerce, elearning, fintech, todos continuam evoluindo, pivotando, ajustando e consertando pra ficar cada vez melhor. O Twitter de hoje é bem diferente do Twitter de 2007, mesmo que só pelo front-end não pareça que tenha mudado muita coisa. A cara não muda tanto quanto o backend.</p>
<p>Dá pra ficar literalmente dias, indo de arquivo em arquivo, tentando desvendar quem chama quem, quem depende de quem, em que momentos, quais serviços rodam pra quais tipos de dados. Mas é super trabalhoso fazer isso sem conseguir rodar nada e sem testes. Eu não acho que vale a pena, mas na internet sempre tem alguém com mais boa vontade de fazer trabalho chato como esse. Com apenas 2 dias olhando pra esse código, eu só consigo descrever até aqui.</p>
<p>E com tudo isso, pelo menos espero que pra vocês que sejam iniciantes, tenham tido uma perspectiva diferente do que imaginavam que seria um código de verdade de produção. Mesmo sem entender nada, deve dar pra começar a entender melhor a dimensão. Repetindo: esse repositório liberado só tem parte do código do sistema de recomendação. Não tem o front-end web, não tem o app mobile, não tem as ferramentas de administração, não tem o ferramental de monitoramento, enfim, só estamos vendo uma fração do que é feito o Twitter.</p>
<p>Antes de terminar, enquanto estava escrevendo este episódio, o canal Fireship lançou também um video curto falando disso. Não tem nada demais, mas eles lançaram um segundo especialmente sobre &ldquo;Vector databases&rdquo;. Só assistindo esse video dele, duvido quem a maioria tenha entendido. Eu expliquei o que é isso durante este video, e se você já não tinha conhecimento prévio de Álgebra Linear, provavelmente entendeu mas não compreendeu.</p>
<p>Enfim, o Fireship menciona bancos de dados modernos de vetores como Pinecone e Weaviate e disse que vai fazer um curso sobre isso. Não sei se foi piada, como eles sempre fazem, ou se vão mesmo, mas vou ficar de olho pra saber. Problema de um curso desses é o famoso &ldquo;olha como se faz um hello world, é super simples&rdquo;, mas sem entender tudo que falei hoje, você vai usar sem entender. Mas assistam lá depois. Vou deixar o link na descrição abaixo.</p>
<p>E finalmente, isso responde outra pergunta que todo mundo tem: precisa saber matemática pra ser programador? E a resposta é não. Pra fazer coisa simples, sabendo matemática básica de regra de três e porcentagem, dá pra ir se virando. No dia a dia de software pequeno, se souber pelo menos usar uma calculadora, já funciona. Agora, sem saber Álgebra Linear, Cálculo, Estatística e Probabilidade, você nunca vai trabalhar em projetos como esse do Twitter, é uma impossibilidade. Matemática é opcional, inglês é opcional, tudo depende de quão pouco você quer ter de opções. Se ficaram com dúvidas, mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>machine-learning</category><category>ciencia-da-computacao</category><category>akitando</category></item><item><title>[Akitando #84] Entendendo "O Dilema Social" e Como Você é Manipulado</title><link>https://akitaonrails.github.io/2020/09/30/akitando-84-entendendo-o-dilema-social-e-como-voce-e-manipulado/</link><guid isPermaLink="true">https://akitaonrails.github.io/2020/09/30/akitando-84-entendendo-o-dilema-social-e-como-voce-e-manipulado/</guid><pubDate>Wed, 30 Sep 2020 19:15:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/mNHKNyhSn8I"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Se você está interessado no documentário “The Social Dilemma” do Netflix, não deixe de assistir o episódio de hoje, onde eu vou introduzir os pensamentos de machine learning que o documentário não explica e, mais do que isso, vou explicar como exatamente documentários como esse manipulam você.&lt;/p&gt;
&lt;p&gt;Aproveito pra explorar um pouco o Teorema de Bayes no contexto de raciocínio real e porque probabilidades sempre te enganam. E de brinde estou recomendando vários livros desta vez. Links abaixo.&lt;/p&gt;</description><content:encoded><![CDATA[

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

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Se você está interessado no documentário “The Social Dilemma” do Netflix, não deixe de assistir o episódio de hoje, onde eu vou introduzir os pensamentos de machine learning que o documentário não explica e, mais do que isso, vou explicar como exatamente documentários como esse manipulam você.</p>
<p>Aproveito pra explorar um pouco o Teorema de Bayes no contexto de raciocínio real e porque probabilidades sempre te enganam. E de brinde estou recomendando vários livros desta vez. Links abaixo.</p>
<p>Links:</p>
<ul>
<li>
<p>Adam Ruins Social Media (<a href="https://www.youtube.com/watch?v=5pFX2P7JLwA"target="_blank" rel="noopener">https://www.youtube.com/watch?v=5pFX2P7JLwA</a>)</p>
</li>
<li>
<p>A Plan for Spam (<a href="http://www.paulgraham.com/spam.html"target="_blank" rel="noopener">http://www.paulgraham.com/spam.html</a>)</p>
</li>
<li>
<p>Veritasium: The Bayesian Trap (<a href="https://www.youtube.com/watch?v=R13BD8qKeTg"target="_blank" rel="noopener">https://www.youtube.com/watch?v=R13BD8qKeTg</a>)</p>
</li>
<li>
<p>3Blue1Brown: Bayes Theorem (<a href="https://www.youtube.com/watch?v=HZGCoVF3YvM"target="_blank" rel="noopener">https://www.youtube.com/watch?v=HZGCoVF3YvM</a>)</p>
</li>
<li>
<p>Julia Galef: A Visual Guide to Bayesian Thinking (<a href="https://www.youtube.com/watch?v=BrK7X_XlGB8"target="_blank" rel="noopener">https://www.youtube.com/watch?v=BrK7X_XlGB8</a>)</p>
</li>
<li>
<p>AkitaOnRails: Somos Matematicamente Ignorantes (<a href="https://www.akitaonrails.com/2008/03/01/off-topic-somos-matematicamente-ignorantes"target="_blank" rel="noopener">https://www.akitaonrails.com/2008/03/01/off-topic-somos-matematicamente-ignorantes</a>)</p>
</li>
<li>
<p>TED: Dan Ariely - Controlamos nossas decisões? (<a href="https://www.ted.com/talks/dan_ariely_are_we_in_control_of_our_own_decisions?language=pt#t-552874"target="_blank" rel="noopener">https://www.ted.com/talks/dan_ariely_are_we_in_control_of_our_own_decisions?language=pt#t-552874</a>)</p>
</li>
<li>
<p>Experimento da Conformidade de Asch (<a href="https://www.youtube.com/watch?v=Ijop2hvPNFE"target="_blank" rel="noopener">https://www.youtube.com/watch?v=Ijop2hvPNFE</a>)</p>
</li>
<li>
<p>Adam Ruins Electric Cars (<a href="https://www.youtube.com/watch?v=MQLbakWESkw"target="_blank" rel="noopener">https://www.youtube.com/watch?v=MQLbakWESkw</a>)</p>
</li>
<li>
<p>No, Social Media Isn’t Destroying Civilization (<a href="https://www.jacobinmag.com/2020/09/the-social-dilemma-review-media-documentary"target="_blank" rel="noopener">https://www.jacobinmag.com/2020/09/the-social-dilemma-review-media-documentary</a>)</p>
</li>
<li>
<p>The Social Dilemma Fails to Blame the Individual (<a href="http://tripod.domains.trincoll.edu/arts/the-social-dilemma-fails-to-blame-the-individual/"target="_blank" rel="noopener">http://tripod.domains.trincoll.edu/arts/the-social-dilemma-fails-to-blame-the-individual/</a>)</p>
</li>
<li>
<p>Bayes Theorem: A Framework for Critical Thinking (<a href="https://neilkakkar.com/Bayes-Theorem-Framework-for-Critical-Thinking.html#the-4-rules-for-being-a-good-bayesian"target="_blank" rel="noopener">https://neilkakkar.com/Bayes-Theorem-Framework-for-Critical-Thinking.html#the-4-rules-for-being-a-good-bayesian</a>)</p>
</li>
</ul>
<p>Livros:</p>
<ul>
<li>Iludidos pelo acaso: A influência da sorte nos mercados e na vida (<a href="https://www.amazon.com.br/dp/8547000968/ref=cm_sw_r_tw_dp_x_V94CFb72Q7R6E"target="_blank" rel="noopener">https://www.amazon.com.br/dp/8547000968/ref=cm_sw_r_tw_dp_x_V94CFb72Q7R6E</a>)</li>
<li>A lógica do Cisne Negro (<a href="https://www.amazon.com.br/dp/8576842122/ref=cm_sw_r_tw_dp_x_V94CFbPC213KW"target="_blank" rel="noopener">https://www.amazon.com.br/dp/8576842122/ref=cm_sw_r_tw_dp_x_V94CFbPC213KW</a>)</li>
<li>Rápido e devagar (<a href="https://www.amazon.com.br/dp/853900383X/ref=cm_sw_r_tw_dp_x_894CFbE5C17GF"target="_blank" rel="noopener">https://www.amazon.com.br/dp/853900383X/ref=cm_sw_r_tw_dp_x_894CFbE5C17GF</a>)</li>
<li>A Mais Pura Verdade Sobre a Desonestidade (<a href="https://www.amazon.com.br/dp/8535258086/ref=cm_sw_r_tw_dp_x_l-4CFbE3D8KKZ"target="_blank" rel="noopener">https://www.amazon.com.br/dp/8535258086/ref=cm_sw_r_tw_dp_x_l-4CFbE3D8KKZ</a>)</li>
<li>Como Mentir com Estatística (<a href="https://www.amazon.com.br/dp/858057952X/ref=cm_sw_r_tw_dp_x_s-4CFbX23H67B"target="_blank" rel="noopener">https://www.amazon.com.br/dp/858057952X/ref=cm_sw_r_tw_dp_x_s-4CFbX23H67B</a>)</li>
<li>Previsivelmente irracional (<a href="https://www.amazon.com.br/dp/854310999X/ref=cm_sw_r_tw_dp_x_qa5CFbK95R1GJ"target="_blank" rel="noopener">https://www.amazon.com.br/dp/854310999X/ref=cm_sw_r_tw_dp_x_qa5CFbK95R1GJ</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Eu ando meio preguiçoso ultimamente, por isso tô demorando pra lançar novos episódios, mas podem esperar que uma hora eles chegam. Durante minha procrastinação eu tenho pensado em novos assuntos e quando estou literalmente coçando o saco assistindo alguma coisa no YouTube ou Netflix, aí vem a inspiração. E nesse caso foi por causa do documentário The Social Dilemma que saiu recentemente no Netflix e muitos de vocês ficaram me recomendando via DM no Insta pra eu assistir. Já vou dar a nota aqui: é um generoso 5 de 10.</p>
<p>Então o tema de hoje vai ser dissecar partes desse documentário e também sugerir um exercício de programação que eu fiz lá por 2003 e que talvez ajude vocês a entender um pouco desse documentário. Eu também vou tentar explicar como você é facilmente manipulado, como você não é tão racional como pensa, mas não do jeito que você pensa. A melhor forma de consertar suas fraquezas é entendendo que elas existem, e eu vou expor suas fraquezas hoje. De bônus vou resgatar alguns conteúdos que quem assistiu palestras minhas lá por 2008 ou 2009 vai se lembrar. E como muita gente sempre me pede pra recomendar livros, hoje vou recomendar vários. Tem bastante coisa, então pega a pipoca e vamos lá.</p>
<p>(&hellip;)</p>
<p>Eu já disse isso antes, mas eu tenho muito pouca paciência pra documentários. Pra ser mais honesto, anos atrás eu até gostava. Nos meus 20 e poucos anos eu assistia bastante. Mas a falta de interesse tem mais a ver que a essa altura da vida, pouca coisa consegue me impressionar. Aos 20 anos era até facil de me impressionar, aos 40 tá difícil, precisa ser algo realmente fora do comum pra mudar a expressão da minha cara. O mais normal é minha expressão virar a de tédio.</p>
<p>Da forma como eu vejo, um documentário é uma faca de dois gumes. Se você é ingênuo, pode achar que um documentário é uma apresentação de fatos, uma explicação da verdade. Mas não é, um documentário é um filme, baseado em realidade, mas que expõe exclusivamente a visão e narrativa do diretor e escritor. Portanto ele sempre vai enviesar a conclusão pro que ele acha que é o certo e ele não tem nenhuma obrigação de mostrar a informação completa, sempre vai ser parcial. Quando você entende isso, também entende que ficar assistindo uma visão enviesada de alguém por 2 horas é pedir demais da minha atenção.</p>
<p>De fato, exatamente a mesma coisa que esse documentário fala pode ser explicado em 5 minutos como neste video do canal Adam Ruins Everything que vou deixar linkado nas descrições abaixo. Aliás, eu recomendo muito esse canal, assisto faz muitos anos e esse video em particular é de uns 3 anos atrás pelo menos. E isso é outra coisa importante. O documentário trás uma narrativa que parece algo extremamente urgente que tá acontecendo agora, como se fosse começar uma guerra civil mês que vem se a gente não fizer alguma coisa. Mas, nada do que ele diz é muita novidade. Qualquer um que trabalha nesse indústria sabe de tudo isso faz pelo menos 5 a 10 anos.</p>
<p>Se você ainda não assistiu o documentário, ele explica em termos simples, pra população que não é de tech, sobre como as plataformas de redes sociais tem incentivos pra coletar dados sobre cada usuário pra conseguir recomendar os conteúdos que tem mais chances de manter você engajado o mais tempo possível. Todo mundo já passou por isso. Você clicou num artigo sobre Europa. Fuçou um grupo no Facebook sobre Itália. Mandou uma mensagem pra alguém sobre vinho. E dali a pouco numa página completamente aleatória, vem um banner com um ad sobre uma viagem pra vinículas na Sicília. E você pensa &ldquo;nossa, leu minha mente!&rdquo;</p>
<p>Eu vou voltar pro documentário daqui a pouco mas agora vem a primeira tangente que me ocorreu quando eu tava assistindo. Como não é uma palestra técnica nem nada, ele fala super super por cima que existe algum tipo de &ldquo;inteligência artificial&rdquo; que sabe tudo sobre nós e com isso é capaz de nos manipular pra nos manter online mais tempo.</p>
<p>Mas você, que teoricamente tá nesse canal porque se interessa por programação, já parou pra se perguntar como funciona esse tipo de sistema? Claro, dá pra fazer uma carreira inteira só resolvendo esse problema, e é exatamente o que engenheiros do Google ou Facebook passaram os últimos 20 anos construindo, e não é em meia hora que eu vou explicar tudo. Mas acho que consigo dar uma pequena luz pra quem é iniciante.</p>
<p>Em 2003 eu era programador Java, trabalhando no mundo corporativo de SAP como expliquei neste video acima. Tem uma rotina que eu fazia naquela época e que eu faço até hoje que é ler notícias de sites de tecnologia, seja Hacker News ou Slashdot ou outros. Desde quando eu tava na faculdade ia organizando todos que eu gostava como bookmarks. Todo dia eu abria dezenas de janelas com todos eles. Era um saco, porque abas não tinham sido inventadas ainda.</p>
<p>Depois inventaram abas e isso ajudou um pouco. Mas cada vez ia aumentando o número de sites que eu acompanhava. Daí em 1999 inventaram o RSS ou Really Simple Syndication. Syndication é a redistribuição de conteúdo, um termo que era usado mais em TV como um modelo de reprises de séries em múltiplos canais por exemplo. Chaves no SBT é um syndication.</p>
<p>Enfim, com o advento do RSS e alguns sites que permitiam agrupar o RSS de vários sites num lugar só, eu podia abrir um único site e ter acesso a todas as notícias e artigos que eu queria num mesmo lugar. O Atom só seria inventado dois anos depois em 2005. Mas em 2003 acho que o site que eu usava mais era o antigo Google Reader. Hoje em dia eu uso o Feedly. Muitos dos sites que eu acompanho até hoje vem desses bookmarks de antes de 1999.</p>
<p>Antigamente a gente precisava ativamente pesquisar e procurar sites, escolher um a um o que queria consumir. Com redes sociais hoje em dia é diferente, a plataforma usa crowdsourcing e machine learning, ou seja, ele avalia o que todo mundo anda escolhendo e faz uma timeline customizada com a curadoria de conteúdos que ela acha que vai nos manter online a maior parte do tempo e não necessariamente que sejam úteis.</p>
<p>Eu sou o tipo de cara que automaticamente tem preconceito com best sellers. Você quer a posição numa livraria onde tem a maior probabilidade dos piores livros? É aquela que tem na entrada de todas chamada &ldquo;mais vendidos&rdquo;. É o equivalente de uma timeline de rede social mas na vida real. Eu não acredito em seguir a maioria. Pra entretenimento sim, por isso eu consumo filmes blockbuster. Não porque eu procuro algo inteligente, mas porque eu quero mesmo algo pra desligar o cérebro e me divertir, e nesses casos a maioria costuma acertar mesmo.</p>
<p>Eu tenho minhas contas em todas as redes sociais mas dificilmente fico seguindo alguém. Eu não faço muito diferente do que fazia com meus bookmarks antigos. No Facebook é onde me dá mais trabalho, porque todo mundo que eu aceito deixar me seguir na sequência eu sou obrigado a des-seguir, porque senão polui minha timeline com lixo. Até hoje eu faço minha própria curadoria. É um saco e dá muito trabalho, mas como eu faço isso faz mais de 2 décadas, já virou rotina. E definitivametne eu não participo de nenhum grupo, nem adianta me convidar.</p>
<p>No começo dos anos 2000 eu seguia alguns sites de programadores que sabiam escrever de maneira muito eloquente e com linha de raciocínio muito boa. Eu não tinha muito interesse em saber quem eles eram ou o que falavam no mundo real, porque o resultado do trabalho me interessa mais do que a pessoa, sempre. Se pra gostar do trabalho depende de eu gostar da pessoa, quer dizer que o trabalho não é tão bom assim. Exemplos daquela época era tipo Martin Fowler, Joel Spolsky ou Paul Graham.</p>
<p>Eu não sabia disso na época, fui descobrir agora escrevendo o episódio, mas o Graham foi um que teve sorte de conseguir surfar a bolha das ponto com e pelo jeito se deu bem. Ele fundou a Viaweb que foi vendida pro Yahoo. E ele fez tudo com Common Lisp e justamente os artigos que ele escrevia no site dele era sobre a beleza de se programar em Lisp. Que eu achava muito legal mesmo sendo programador de Java. Lembrando, isso é 2003, uma década antes do povo ficar enchendo o saco falando de programação funcional como se fosse uma grande novidade.</p>
<p>O Graham depois fundou a YCombinator, uma das aceleradoras de startups mais famosas de Silicon Valley, de onde saíram algumas das marcas mais conhecidas como Stripe, Airbnb, Dropbox, Rappi, Gitlab, Docker, Twitch, Heroku e dezenas de outros. É meio que uma profecia que se auto-realiza. E é dele também o famoso site Hacker News, que meio que tomou o lugar do Slashdot entre os sites que eu mais acessava. Hacker News costuma agregar notícias interessantes, mas as discussões sempre são meh. Alguns poucos são úteis, a grande maioria é só perda de tempo, mas isso é verdade em todos os lugares que as pessoas podem comentar. Outra dica: engajar em comentários é perda de tempo.</p>
<p>Os artigos que eu lia dele ainda tão online então eu recomendo que vocês leiam uma hora também. Dentre eles tava o artigo &ldquo;A Plan for Spam&rdquo; ou &ldquo;Um Plano contra o Spam&rdquo;. Lembrem-se, o Gmail ainda não tinha sido lançado. A gente tinha Hotmail e cada provedor tinha seu serviço porcaria de e-mail. Se você acha que spam hoje é ruim, pensa como era spam no pico da bolha das ponto-com. Era uma quantidade irritante de lixo. A gente usava clientes de e-mail como Outlook e precisava instalar plugins pra filtrar Spam como o SpamAssassin. Era raro o provedor de e-mail ter um sistema bom anti-spam.</p>
<p>Pra piorar a gente ainda usava o antigo sistema de blacklists e whitelists. Dava pra bloquear endereços que eram conhecidos de propaganda ou spam e precisava carregar e dar manutenção em listas de palavras bloqueadas ou permitidas. Se no e-mail aparecesse &ldquo;aumente seu pênis&rdquo; obviamente era spam, e dava pra bloquear. Porém, spammers iam ficando mais sofisticados. Sabendo que a gente ia bloquear a palavra &ldquo;pênis&rdquo; eles começavam a mandar penis sem acento ou escrever palavras com erros de propósito, tipo penis com &ldquo;z&rdquo; ou dois &ldquo;n&rdquo;. Eu acho que é por isso que e-mails de spam tem tantos erros ortográficos, pra tentar fugir dos sistemas de spam primitivos.</p>
<p>Era uma guerra que não tinha como ganhar. Pra piorar você podia acabar apagando e-mails que não eram de spam, ou seja, falsos-positivos. Por isso o artigo do Graham me deixou impressionado quando eu li em 2003. Ele propõe um filtro baseado em treinamento estatístico, mais especificamente usando o método Bayesiano. O teorema de Bayes descreve a probabilidade de um evento, por exemplo, se um e-mail é um spam, baseado em conhecimento anterior de e-mails marcados que eram spam. Nos experimentos dele, pra cada mil e-mails de spam, ele deixava passar uns cinco que o filtro não pegava, e tinha zero falsos positivos.</p>
<p>Ou seja, o algoritmo dele era 99.5% eficiente. Comparado com os filtros antigos baseados em lista, que se tinham 95% de eficácia era muito, 99.5% é um salto enorme. E melhor ainda, era um sistema que em vez de ficar dando manutenção em lista de combinações de palavras, era refinado automaticamente à medida que a gente recebia mais spams. Se você ler o artigo dele vai entender mais detalhes, por exemplo, no sistema de listas de palavras, quanto mais você escreve menor as chances de ser pego, mas num sistema bayesiano, a única saída pra tentar passar spam é escrever o menos possível, nível menos que um tweet. Porque quanto mais informação, maiores as chances de ser filtrado.</p>
<p>Eu li esse artigo 17 anos trás, e foi a primeira vez que eu entendi o poder da estatística e probabilidade que eu tanto falo. Mais do que isso, entender Bayes meio que muda a forma como você enxerga o mundo. Vale a pena entender pelo menos a noção mesmo que você nunca pare pra entender as fórmulas. Aliás, a fórmula em si é esta aqui (&hellip;), A probabilidade do evento B, dado o evento A é igual à probabilidade do evento A dado o evento B, vezes a probabilidade de A, dividido pela probabilidade de B. Uma fórmula muito elegante e simples, que tem um peso similar ao famoso E = m c ao quadrado de Einstein, que todo mundo veste na camiseta, faz tatuagem mas ninguém sabe o que de fato significa. E dessa fórmula pra chegar na bomba atômica ainda precisa de muito mais física por cima.</p>
<p>Acho que a maioria das pessoas devia tatuar a fórmula de Bayes, porque ela é muito mais útil no nosso dia a dia do que E igual m c quadrado. E pra explicar eu vou roubar um trecho de um video de outro canal que eu gosto bastante, o Veritasium, entitulado The Bayesian Trap que vou deixar o link nas descrições abaixo. Mas ele conta a seguinte história: Imagine o seguinte, você acorda uma manhã e se sente um pouco doente, tossindo, com febre. Daí você resolve ir no médico e ele manda você fazer um teste.</p>
<p>Daí sai o resultado e ele é positivo pra uma certa doença que afeta 0.1% da população. E não é uma doença legal, não tem cura, você certamente não quer ter essa doença. Daí você pergunta pro médico &ldquo;puts, e qual é chance de eu estar com essa doença mesmo?&rdquo; E o médico responde &ldquo;bom, o teste corretamente identifica 99% das pessoas que tem a doença e dá só 1% de falsos positivos&rdquo;. E isso definitivamente soa bem ruim. Qual é a chance de você realmente ter a doença sabendo disso? E aqui vem o problema que eu falo que a gente é muito ruim com probabilidades porque a maioria das pessoas pensaria na hora que as chances de ter a doença devem ser perto de 99% já que o teste é preciso 99% das vezes.</p>
<p>E é aqui que entra a tal fórmula do teorema de Bayes pra ter uma nova perspectiva. Já aviso pra não se assustar com a fórmula que vou mostrar agora porque logo na sequência vou explicar porque você sequer precisa da fórmula, então se seguram por 2 minutos.</p>
<p>O teorema vai te dar a probabilidade de uma hipótese, digamos, se você tem mesmo a tal doença, baseado num evento, que você foi testado positivo. Pra calcular isso você precisa começar com uma probabilidade anterior da hipótese ser verdade, ou seja, a probabilidade de você ter a doença antes de fazer o teste, e multiplicar pela probabilidade do evento dado que a hipótese é verdadeira, ou seja, a probabilidade do teste dar positivo, dado que você tem a doença, e dividir isso pela probabilidade total do evento acontecer, ou seja, testar positivo.</p>
<p>A probabilidade anterior da hipótese ser verdadeira é normalmente a parte mais difícil da equação e muitas vezes não é melhor que um chute mesmo, guarde esse conceito. Mas nesse caso um ponto de partida adequado é a frequência da doença na população, que sabemos que é 0.1%. E se plugar o resto dos números, descobre que na verdade você tem só 9% de chance de realmente ter a doença depois de testar positivo.</p>
<p>Perceberam? Antes de aplicar a fórmula eu tenho certeza que a maioria de vocês pensaria que a chance de ter a doença seria mais perto de 99% por causa da precisão do teste. Mas aqui precisa ter o tal &ldquo;bom senso&rdquo; de verdade. Vamos pensar sem usar a fórmula, só usando real bom senso. Pensa numa população de 1000 pessoas. Como falamos que 0.1% da população tem a doença, então 1 pessoa nessas mil muito provavelmente tem a doença e se aplicar o teste nela, o teste muito provavelmente vai identificar como positivo.</p>
<p>Mas, se testássemos também as outras novecentas e noventa e nove pessoas, 1% ou 10 seriam falsamente identificadas como tendo a doença. Lembra? O teste tem 1% de erro. Então se você é uma das pessoas que tem o resultado positivo no teste e todo mundo é selecionado aleatoriamente, então você é parte de um grupo de 11 pessoas que teriam o teste positivo e só tem uma pessoa de fato que tá doente. Então é 1 chance em 11, ou seja, 9%.</p>
<p>Por isso se fala que é importante uma segunda opinião, um segundo teste feito de maneira independente. E digamos que esse segundo teste também volta positivo. Agora, qual é a chance de você de fato ter a doença? Vamos usar a mesma fórmula de Bayes, mas agora em vez de usar a probabilidade anterior de que 0.1% da população tem a doença, vamos plugar a probabilidade posterior, os 9% de chance de você ter a doença dado que o primeiro teste deu positivo.</p>
<p>Agora, se você calcular com os mesmos números de antes mas agora baseado em 2 testes positivos chegamos na probabilidade de 91%. E isso faz sentido porque 2 testes positivos rodados por 2 laboratórios independentes já é mais difícil de ser coincidência, mas mesmo assim 91% não é perto dos 99% que você achou no começo.</p>
<p>Recomendo que você assista o video completo do Derek no canal Veritasium e, como sempre, também recomendo o video do Grant do canal 3Blue1Brown que explica ainda melhor como o teorema funciona. Nesse video o Grant relembra o trabalho de Daniel Kahneman e Amos Tversky que inclusive deu o prêmio Nobel ao Kahneman e ficou famoso no seu livro Pensando Rápido e Devagar e o livro The Undoing Project de Michael Lewis. O trabalho deles foi pesquisando o julgamento humano e como esse julgamento costuma contradizer o que as leis da probabilidade de fato sugerem, e como nosso pensamento é irracional.</p>
<p>O teorema de Bayes não é uma fórmula pra ser aplicada só uma vez. Ela é importante porque refina sua confiança numa hipótese depois de ver novas evidências. Na visão de mundo de Bayes é impossível ter 100% de confiança em alguma coisa, você vai refinando sua crença baseado no refinamento das probabilidades dado novas evidências. Por isso a pergunta não é &ldquo;qual é a probabilidade da hipótese&rdquo; e sim &ldquo;qual é a probabilidade da hipótese dado este novo evento, ou evidência&rdquo;. Não é uma fórmula determinística pra conseguir 100% de verdade, mas pra ir se aproximando, validar ou invalidar uma crença. É uma forma de quantificar sua crença numa hipótese.</p>
<p>E é importante entender que mesmo com uma montanha de evidências, você nunca pode ter 100% de certeza nem com o teorema de Bayes. Exemplo clássico, se sua hipótese é que todas os cisnes são brancos, e você mora num lugar que só tem cisnes brancos, sua probabilidade vai se aproximando mais e mais de 100%, mas basta encontrar um cisne negro pra invalidar toda a hipótese. Essa é uma das teses do famoso livro da Lógica do Cisne Negro de Nicholas Taleb e o outro livro dele que eu acho mais interessante, o Iludidos pelo Acaso que fala sobre a aleatoriedade que não vemos no nosso dia a dia. A população em geral confunde quantidade de evidência com a qualidade das evidências. Se as evidências são ruins, sua conclusão necessariamente vai ser ruim, especialmente se a única ferramenta que você tem é inferência.</p>
<p>Eu sei que é difícil entender Bayes assim logo de cara. No YouTube mesmo tem diversos vídeos explicando e você vai achar dezenas de artigos no Google, é só procurar, mas vou deixar mais um exemplo simples pra tentar sedimentar essa noção. Como o Grant diz no seu video do 3Blue1Brown, ajuda muito se você não pensar em probabilidades como fórmulas, e sim como proporções. Quando a gente diz que um teste tem 99% de precisão, significa também que tem 1% de falsos positivos. No final você precisa ter um espaço de 100% de probabilidades. Se uma moeda tem 50% de chance de dar cara, também tem 50% de chance de dar coroa, e a probabilidade total é 100%.</p>
<p>O próximo exemplo eu peguei do canal da Julia Galef, que é muito parecido com os exemplos do Kahneman e Tversky, mas pode ajudar vocês agora. Considere um personagem fictício chamado Tom. A única coisa que eu vou dizer pra vocês sobre o Tom é que ele é muito tímido, sabe, pessoa introvertida, quieta e tals. Sem pegadinha, se você tivesse que chutar, qual carreira o Tom perseguiria, um phD de matemática ou uma escola de negócios, business? É pra escolher um ou outro. Vai, pensa por 2 segundos &hellip;.</p>
<p>Pensou? Se eu tivesse que apostar o que vocês escolheriam, eu apostaria que quase todos diriam que o Tom escolheria phD em matemática. Mesmo quem chutou negócios, não saberia dizer porque, talvez só pra não ser óbvio. Mas todo mundo sempre associa o estereótipo de pessoa tímida tendo mais a ver com matemática do que com negócios. Esse é o problema das suas crenças, ou nesse caso, preconceitos e estereótipos. Não pense na pessoa, pense primeiro nas probabilidades precedentes.</p>
<p>Vamos lá, usando as mesmas crenças que você deve ter e sem tentar procurar uma estatística oficial. Se você tivesse que chutar, qual a proporção TOTAL de estudantes de escolas de negócios versus estudantes de phd de matemática? Eu acho que todo mundo chutaria e não estaria muito errado que deve ter muito mais estudantes de negócios do que de phd de matemática, numa proporção talvez de 10 pra 1. Olhe ao seu redor, quantos phd de matemática você já conheceu pessoalmente, e quantos estudantes de business tipo MBA, administração ou algo assim você já conheceu? A menos que você seja estudante já da área de matemática, provavelmente vai ter visto mais pessoas de negócios.</p>
<p>Nosso universo é esse quadrado, com a proporção de 10 estudantes de negócios pra 1 de matemática. Num universo de, digamos 1000 estudantes, eu acho que 10%, ou seja 100 sejam de matemática e os outros 900 são de negócios. Dentre os 100 de matemática, temos 75 que são tímidos. Mas 15% dos 900 de negócios são 135 alunos. Ou seja, a probabilidade do Tom se tornar um estudante de negócios, mesmo sendo tímido, ainda é o dobro dele ir pra matemática. Que é exatamente o oposto do que sua intuição disse logo no começo do exercício.</p>
<p>E é exatamente esse erro que você comete todos os dias, toda vez que lê um tweet ou vê o noticiário e aparecem probabilidades e estimativas. Você ignora os precedentes. Você acha que tá pensando racionalmente, mas não tá. E essa é a lição mais importante do teorema de Bayes, mesmo que você nunca use as fórmulas.</p>
<p>Mas pros propósitos do assunto de spam, temos um controle maior da situação porque quem recebe os e-mails somos nós, e eu sei o que eu objetivamente considero spam ou não, e eu mesmo consigo separar os e-mails. E com Bayes, em vez de calcular a probabilidade de você ter uma doença, dado que um teste deu positivo, mude pra probabilidade de um e-mail ser spam dado os e-mails anteriores que eu marquei como spam. No começo, com uma probabilidade anterior que é um chute, você vai ter um resultado baixo. Mas à medida que você vai classificando mais e mais e-mails de spam, e treinando com essa fórmula, vai criando um modelo mais e mais preciso de que tipos de palavras e combinações realmente aparecem num spam, sem precisar manualmente criar listas de palavras.</p>
<p>No artigo do Graham ele fala em considerar não só o texto do corpo do e-mail mas também os cabeçalhos, o título e tudo que compõe o e-mail e fazendo isso podemos treinar esse modelo bayesiano pra ficar mais e mais preciso em identificar spams com muito pouco falso positivo. No caso dessa minha história, depois que eu li o artigo eu pensei num outro problema. Lembram que eu falei que por alguns anos eu vim acumulando vários sites que eu lia todos os dias e depois eu agreguei o RSS de todos num Google Reader da vida?</p>
<p>Mesmo naquela época acumulava sei lá, uns 500 artigos que eu precisava bater o olho toda semana. E desses, no máximo uma dúzia eu separava em abas pra ler depois. Por isso eu fico com tanta aba aberta. No meu Feedly de hoje, se eu deixo pra abrir uma vez por semana, acumula mais de mil artigos fácil. Eu gasto algumas horas batendo o olho e separando o que eu quero ler em abas. Na época eu pensei, a idéia toda do algoritmo do Graham seria ter uma pasta chamada Spam e no começo eu manualmente ia movendo os e-mails que eu sei que são Spam pra essa pasta. Com o tempo o algoritmo ia conseguir sozinho separar o que é spam do que não é.</p>
<p>A mesma coisa vale pro oposto, que é selecionar automaticamente conteúdo que eu gosto. Então eu resolvi fazer um programinha bem simples, em PHP na época, porque  achei uma biblioteca de Bayes e de RSS em PHP primeiro. Então fiz um experimento pra puxar algumas centenas de artigos de RSS. A idéia é que isso podia economizar muito meu tempo depois. Mas o experimento foi um fracasso porque PHP não era a linguagem adequada, pra processar todos os RSS que eu lia, na minha máquina de 2003, era muito demorado. E nos primeiros testes o número de falsos positivos ainda era muito grande, então ia precisar de muitas rodadas até o número de falsos positivos ficar adequado. Eu queria reescrever em Java, mas na época acabei deixando a idéia de lado.</p>
<p>Alguns anos depois começariam a surgir plataformas de redes sociais como conhecemos hoje. No ano seguinte o Facebook ia aparecer, quatro anos depois o Twitter, sete anos depois o Instagram. E em paralelo a isso a programação paralela ia começar a ganhar mais corpo com o advento de CPUs multi-core, como o AMD Athlon 64 que lançou em 2003. GPUs estavam engatinhando e a gente ainda usava computadores de 32-bits. Meu notebook Toshibão acho que era um Pentium 4 e eu tinha 1 Giga de RAM. Faltava processamento e faltou eu escrever em uma linguagem mais adequada pra esse caso de uso, como Java.</p>
<p>Enfim, alguns anos depois surgiria a Amazon AWS em 2006, nesse mesmo ano sairia coisas como o Apache Hadoop que é uma coleção de programas open source pra lidar com quantidades massivas de dados, o início do que chamamos de Big Data. E em cima dele surgiriam outras ferramentas como o Apache Mahout que é outra coleção de programas open source distribuídos e escaláveis de machine learning. E o Mahout é uma coleção focada em coisas como álgebra linear e probabilidade e adivinhem, dentre os vários classificadores está o Naive Bayes. Eu só fui descobrir sobre isso acho que por volta de 2012 ou 2013.</p>
<p>Naive que é ingênuo em inglês e é ingênuo porque o classificador Naive Bayes parte da premissa que as características de uma medição são independentes uns dos outros, e isso é ingênuo porque quase nunca é verdade. Isso facilita a implementação mas não tem problema no resultado, só quer dizer que talvez vamos processar a mesma características duas vezes.</p>
<p>Naive Bayes é uma algoritmo de classificação muito intuitivo. Ele começa fazendo a pergunta &ldquo;dado tais características, essa medição pertence à classe A ou B&rdquo;, tipo spam ou não-spam, doente ou não-doente, artigos interessantes ou artigos chatos? E responde tomando a proporção de todas as medições anteriores com as mesmas características da classe A multiplicado pela proporção de todas as medidas da classe A. Se esse número é maior que o cálculo correspondente pra classe B então dizemos que a medição pertence à classe A. Só isso.</p>
<p>Machine Learning é basicamente estatística e probabilidade, álgebra linear e cálculo. E o teorema de Bayes é tipo o Hello World de Machine Learning. E dentro da categoria de classificadores temos um Naive Bayes. E mesmo ele tem algumas implementações diferentes como gaussiano, multinomial e Bernouli. Uma das vantagens é que escala muito bem desde conjuntos de dados pequenos até Big Data. Então se você tem interesse no assunto de Machine Learning eu acho que começar entendendo o Teorema de Bayes e como ele foi primeiro aplicado ao problema de Spam é um bom ponto de partida.</p>
<p>E de bônus, muita gente pede um projetinho pra exercitar programação. E eis que minha história de 2003 pode servir pra alguma coisa. Eu fiz esse programinha super besta em PHP, que foi limitado ao pouco poder de processamento que eu tinha, provavelmente usando bibliotecas mal-escritas, e meu próprio programinha foi só um protótipo que eu montei em poucas horas e nunca refinei. Pois bem, eis o exercício, criar um programinha em qualquer linguagem, onde se possa cadastrar feeds de Atom ou RSS. Daí um background job tipo um cron vai ficar puxando novos artigos desses feeds de tempos em tempos e vai tudo pra algum tipo de Inbox.</p>
<p>Dai via uma interface gráfica ou via linha de comando eu posso ir puxando um artigo de cada vez desse inbox e marcando com um Like da vida ou como Spam, e assim ir treinando o sistema com um classificador Naive Bayes. Pra isso precisa fazer um parser como o Paul Graham explica no artigo dele. E isso vai fazer você estudar um pouco de Natural Language Processing ou NLP, porque entender o conceito de grams e n-grams pode ajudar. Isso provavelmente vai te levar a estudar um pouco de Teoria da Informação de Claude Shannon também.</p>
<p>No começo o programa não sabe nada sobre mim então todo artigo que me mostra ele não sabe se eu vou gostar ou não. Mas a partir do momento em que começar a acertar quando eu dou like com mais de 99% de certeza, ele pode automaticamente ir mandando artigos que acha que eu não vou gostar pra pasta de Spam, sempre que o background job puxar novos artigos do feed. Daí vai me mostrando só os que acha que eu iria dar Like. E à medida que deixar passar artigos que eu não gosto, eu posso marcar como Spam pra ir refinando. Não sei ainda se essa idéia funciona bem porque até hoje eu nunca parei pra fazer uma versão nova, talvez eu me anime a fazer também.</p>
<p>Você pode querer ser fancy e montar uma infraestrutura na AWS com Spark e Mahout mas não é obrigatório porque a idéia não é ter Big Data pra milhares de pessoas. Como qualquer computador médio de hoje em dia tem processamento de sobra, acho que mesmo um programinha simples local deve ser mais do que capaz de processar algumas centenas ou milhares de artigos vindos de feeds. Eu acho que é um projetinho que você consegue fazer em uma semana, talvez duas, nas horas vagas. Se você se diz de machine learning, esse exercício deveria ser como um hello world, porque hoje em dia existem bibliotecas, frameworks e serviços prontos que já fazem quase tudo que eu falei.</p>
<p>De qualquer forma, eu sempre considerei estatística e probabilidade uma matéria desconfortável no começo. Quando você tá acostumado com determinismo da física newtoniana por exemplo, você espera fórmulas que descrevem o mundo absolutamente. Só que probabilidades não funcionam assim. O mundo é probabilístico, tudo tem uma probabilidade de acontecer mas baseado no &ldquo;meu&rdquo; conhecimento do mundo, que é um conhecimento limitado e provavelmente falho. A probabilidade é intrínseca a mim e não aos eventos do mundo. Ou seja, duas pessoas analizando o mesmo evento, de forma independente, podem chegar a probabilidades bem diferentes. E isso soa bem desconfortável.</p>
<p>Por exemplo, se eu fosse ingênuo, poderia considerar que a probabilidade de existir Covid-19 é perto de zero porque eu não conheço ninguém perto de mim que tem. Pergunte a um médico na emergência de um hospital e ele vai dizer que pra ele é perto de 100%. As probabilidades são a medida da &ldquo;sua&rdquo; confiança numa determinada crença. Quando começamos a aprender introdução à probabilidade normalmente vemos exemplos bestas como jogar uma moeda pra tirar cara ou coroa, ou jogar dados, ou rodar uma roleta num cassino. São eventos independentes, e as probabilidades não são tão difíceis. Mas mesmo em eventos independentes nossa intuição é ruim. Por exemplo, 12 anos atrás eu escrevi um post no meu blog chamado Somos Matematicamente Ignorantes falando disso. Vou deixar linkado nas descrições.</p>
<p>Um dos exemplos desse artigo ilustra uma das idéias do livro Iludidos pelo Acaso do Taleb. Sempre é possível combinar dados aleatórios e encontrar alguma regularidade. Um exemplo muito conhecido é a comparação das coincidências nas vidas de Abraham Lincoln e John Kennedy, dois presidentes com sete letras em seus últimos nomes, eleitos com 100 anos de diferença, em 1860 e 1960. Ambos assassinados numa sexta-feira na presença de suas esposas, Lincoln no teatro Ford e Kennedy num automóvel feito pela Ford.</p>
<p>Os assassinos dos dois tem três nomes: John Wilkes Booth e Lee Harvey Oswald, com quinze letras em cada nome completo. Oswald atirou em Kennedy de um armazém e correu para um teatro, e Booth atirou em Lincoln em um teatro e correu para um tipo de armazém. Ambos os sucessores vice-presidentes eram Democratas sulistas e ex-senadores chamados Johnson (Andrew e Lyndon), com treze letras em seus nomes e nascidos com 100 anos de diferença, 1808 e 1908.</p>
<p>Bizarro né? Mas se compararmos outros atributos relevantes falhamos em encontrar coincidências. Lincoln e Kennedy nasceram e morreram em dia e mês, e em estados diferentes, e nenhuma das datas é separada de 100 anos. Suas idades eram diferentes, assim como os nomes das esposas. Claro, se alguma dessas fosse correspondente estariam na lista de coincidências “misteriosas”. Pessoas vivem décadas, com centenas ou milhares de eventos durante a vida. Você sempre vai encontrar coincidências nas vidas de quaisquer duas pessoas.</p>
<p>Duas pessoas se encontrando numa festa normalmente encontram coincidências chocantes entre elas. Quantas vezes você conhece uma pessoa numa festa e depois de conversar fica com aquela impressão de &ldquo;nossa, como temos coisas em comum&rdquo; e aí chega na falsa conclusão de &ldquo;é o destino, a gente foi feito pra se encontrar&rdquo;. Meh, porra nenhuma, fale com qualquer outra pessoa no mesmo lugar e você vai encontrar a mesma situação.</p>
<p>Você já viu artigos à la Buzzfeed de coincidências como essas como se fosse algo extraordinário. Coincidência é o que mais existe. E também eu acho que não tem nada mais clickbait do que fazer títulos de notícias usando números. A população em geral tem sérios problemas em entender esses números. Relembre da história do video do Veritasium que eu falei. Digamos que você, que fez o tal teste, é alguém famoso. E depois do primeiro teste positivo, algum papparazi descobre e publica a notícia com o título &ldquo;Celebridade Fulano fez teste que tem 99% de precisão e deu positivo!&rdquo; Noooooosa, todo mundo que lê o título assume &ldquo;puts, tadinho, tão novo e já vai morrer&rdquo;. E lembram da conta que fizemos com o teorema de Bayes? Sua chance de ter a doença na verdade é só 9% ainda e os jornais já começaram a escrever seu obituário.</p>
<p>Aqui vale uma nova tangente. Isso me lembra outro autor que eu gosto muito, o pesquisador de economia comportamental, Dan Ariely, e seu famoso livro Previsivelmente Irracional que explica como nós achamos que somos pessoas racionais, com pensamento lógico, mas como na realidade nós nos comportamos de maneira instintiva e irracional. Além desse livro também recomendo outro dele, que é A Mais Pura Verdade Sobre a Desonestidade. Mas do primeiro livro tem vários exemplos que ele apresentou no TED, na época em que o TED tinha conteúdo interessante. Aliás, fica dica, se pegar algo no TED que é de menos de 10 anos pra cá, suspeite. Mas vamos ver um trecho da palestra dele que mostra como nós somos ruins em entender estatísticas. Lembrando que esse video é de 2008.</p>
<p>(Dan Ariely video)</p>
<p>Isso é tão comum na realidade que sempre que eu explico esses conceitos faço questão de lembrar de um livro de 1954 entitulado &ldquo;Como Mentir com Estatística&rdquo;. Repetindo, probabilidades dependem do &ldquo;seu&rdquo; conhecimento sobre o mundo. O teorema de Bayes não é que nem uma fórmula de gravitação onde você tem medições que são fatos e o resultado é uma previsão precisa. Por exemplo, na história do teste da doença, a primeira rodada com a fórmula do teorema de bayes começa com um chute educado na probabilidade anterior, onde pegamos a probabilidade de infecção da população que no exemplo foi de 0.1%, ou 1 em cada 1000.</p>
<p>Nós minimizamos o efeito desse chute quando rodamos a segunda vez e agora substituimos a probabilidade anterior pela probabilidade posterior, de 9%. Se repetirmos o teste mais vezes vamos chegar mais e mais perto de uma confiabilidade perto de 100%. Mas se estivermos limitados a fazer só um teste, a próxima pergunta óbvia deveria ser: da onde vem esse 0.1%? Quando falamos população, estamos representando a região onde moramos? Ou esse número é da China? Qual foi o tamanho da amostra? Medimos 100 mil pessoas? 10 mil pessoas? Ou míseras 1000 pessoas? A confiabilidade do resultado depende diretamente da qualidade dos dados sendo medidos e do processo de medição.</p>
<p>Essa confiabilidade vai ficando melhor a cada nova evidência. Por outro lado, basta uma evidência em contrário pra invalidar a hipótese inteira. A moral da história é simples: se você tem 20 e poucos anos, evite ter crenças firmes de 100% em qualquer coisa. É muito cedo. Tirando coisas meio óbvias tipo leis da física, química, biologia, qualquer outra coisa como filosofia ou política, dependem totalmente das pessoas e do timing. E elas mudam, bem mais rápido do que você pensa. Tudo que parece urgente e fatal hoje na verdade não é tanto assim.</p>
<p>Tudo que você acha que é 99% na verdade é mais perto de 9%. Pela última vez, as probabilidades que você pensa na sua cabeça refletem o seu limitado conhecimento do mundo. Eu tenho mais de 40 e sei que ainda tem um monte que não sei. Eu não me apego a ideologias ou semi-deuses, eu sou promíscuo de ideologias, e eu nunca acho que o mundo vai acabar fácil assim. Não acabou em Roma, nem na 2a Guerra. Não acabou na Guerra Fria. &ldquo;ahhh mas agora é diferente&rdquo; nah, é a mesma coisa.</p>
<p>Com tudo isso dito, vamos voltar ao documentário. Um meme que eu sempre achei engraçado é aquele onde um casal tá conversando e a esposa fala como ouviu falar que as grandes corporações estão espionando eles o tempo todo. O marido ri, a esposa ri, a Alexa e a Siri riem juntos. Se o produto é de graça, você é o produto. Você paga pra pesquisar no Google? Você paga pra postar fotos no Instagram? Você paga pra conversar por Whatsapp? Então o produto é você, mais especificamente todos os dados que você gera por dia. Você não é o cliente dessas plataformas, você é o usuário. E de fato, só conheço duas indústrias que não falam em clientes e sim em usuários, e o outro é o tráfico de drogas.</p>
<p>Se você assistir o documentário, parece mesmo uma distopia Orweliana. Existe toda a teoria da conspiração de como as redes sociais cresceram ao ponto de aparentemente conseguir mudar o rumo de uma eleição pra presidente, e como ela pode ser usada pra causar caos e até instigar uma possível guerra civil. E o documentário pinta essas inteligências artificiais como o grande inimigo. Ex-funcionários, ex-executivos e ex-investidores aparecem com aquele ar de &ldquo;a gente não sabia, nós que criamos mas saiu do nosso controle&rdquo;, meio aquela coisa de &ldquo;ai nossa, eu pequei no passado e agora me converti e virei crente&rdquo;.</p>
<p>De repente, por causa disso, eles são os seres iluminados que viram a luz, e por isso eles sabem diferenciar o certo do errado, e o resto da população, coitados, são ignorantes. Mas eles que criaram as plataformas? São seres do bem, acima de todo mundo, que vão nos proteger de alguma forma. Qual forma? Fazendo lobby pra criar regulamentações, formas de controle e agências de censura, pra controlar o que as redes sociais podem ou não fazer, porque coitados, a população em geral é meio burra e se ninguém intervir eles vão continuar sendo explorados.</p>
<p>Eu lavo minhas mãos, eu não tenho paciência pra ser ativista. Por princípio eu sou contra regulamentações. Regulamentações é um tapa buraco, pra pintar de inimigo uma ovelha negra, e jogar embaixo do tapete o trabalho que não foi feito antes. Pra ver como um documentário tem viés veja que ele faz o possível pra pintar as plataformas de redes sociais como o inimigo que precisa ser domado. Essa é a ovelha negra. Basta ver como o diretor omitiu o mais óbvio, a parte mais importante. Por que a maioria da população é tão simples de ser manipulado? Porque nosso sistema educacional está falido faz décadas.</p>
<p>Com o avanço rápido que vemos na tecnologia parece que o mundo também avança rápido. Mas tirando tecnologia, muitas áreas estão super defasadas, e educação é um dos principais em atraso. É que nem na época da discussão de jogos considerados violentos como Mortal Kombat ou Carmagedon. Ninguém vira assassino e sai matando pessoas na rua porque jogou videogames. Ou na época que o Rock and Roll começou a fazer sucesso e um novo mercado de jovens passou a consumir música. Ou na época que a TV foi lançada e todo mundo temia lavagem cerebral. Pessoas educadas não são facilmente influenciáveis assim. Pessoas não-educadas ou pior, que acham que são educadas, são. Porque na falta de ferramentas pra decidir o que é certo ou errado, elas são obrigadas a ouvir alguma autoridade. Qualquer autoridade. Se antigamente essas autoridades eram, talvez, o padre da igreja, hoje é o streamer influencer.</p>
<p>Existem diversos experimentos que tentam explicar o que leva uma população inteira a apoiar algo que é considerado &ldquo;mau&rdquo; hoje. Por exemplo, será possível que a população inteira da Alemanha na 2a guerra realmente achava virtuoso matar judeus? Como isso era possível? Pra dar uma explicação muito simples, eu sempre gosto de apresentar o experimento da conformidade social de Solomon Asch. É curtinho, vamos lá.</p>
<p>(Asch)</p>
<p>Pessoas são animais sociais que parecem ter querer serem aceito por comunidades ou grupos, mesmo não concordando. Esse conceito é meio alienígena pra mim, que sou anti-social, mas é o que eu mais ouço e acho que faz sentido. O trabalho seguinte é mais interessante. Asch foi conselheiro na tese de doutorado de outro pesquisador famoso e controverso, Stanley Milgram, que fez experimentos sobre obediência à autoridade. Vamos ver.</p>
<p>(Milgram)</p>
<p>Quando se fala de psicologia social, o be-a-bá pra mim são Asch e Milgram. Nos temos modernos temos os economistas comportamentais como Dan Ariely que mostrei antes. E talvez vocês esbarrem em outro ponto de vista interessante, o famoso Efeito Lucifer de Philip Zimbardo, mas diferente de Asch e Milgram, cujos experimentos foram repetidos diversas vezes ao longo de décadas, sempre dando os mesmos resultados; o experimento de Zimbardo é cheio de falhas, então se forem ler, leiam com uma quantidade generosa de sal. Mas a conclusão é a mesma: nós achamos que somos racionais e que temos condições de pensar com lógica, mas na realidade não é muito difícil nos manipular.</p>
<p>O corolário dessa idéia é óbvio: não adianta trocar uma autoridade por outra autoridade, por exemplo, as empresas de redes sociais por grupos políticos. Qual o interesse de parte dos entrevistados do documentários? Eles querem se tornar essas autoridades. É uma estratégia antiga: crie algo que gera problemas, depois venda uma solução desse problema.</p>
<p>Você não quer que os jovens sejam influenciados pela torrente de fake news nas redes sociais? Agora é tarde demais, porque ninguém tem nem nunca vai ter a autoridade pra saber decidir o que é certo ou o que é errado absolutamente. O sistema educacional de praticamente todos os países do mundo ainda opera segundo modelo derivado da época da Revolução Industrial. Todo aluno aprende a obedecer uma figura de autoridade na sala de aula, uma figura de autoridade dentro de casa, e quando cresce e precisa pensar sozinho, ele procura outra figura de autoridade pra seguir, e normalmente erram feio. Os pais que estão apreensivos com as redes sociais hoje não são tão diferentes dos que estavam apreensivos que o rock and roll desvirtuasse a geração nos anos 60 e 70.</p>
<p>Quem assiste meus vídeos sabe como eu repito que não sigo influencers, tenho zero paciência, e também que vocês não devem acreditar em tudo que eu digo e me seguir cegamente, porque eu tenho zero paciência pra ser influencer. Eu sou só um cagador de regras dizendo o que eu sei. Considerem que eu tenho algumas vantagens que me ajudaram ao longo dos anos.</p>
<p>Ao contrário do que muita gente pode pensar se só conhece minha persona de YouTuber, é que eu sou muito introspecto e anti-social. Por mais estranho que possa parecer, eu me dou muito bem com minha própria companhia e me considero auto-suficiente. Sempre que me forço a ir contra minha natureza eu me dou mau. Minha persona pública, esta que vocês estão assistindo, é diferente da minha persona particular.</p>
<p>Toda pessoa pública tem personas, fachadas. Ninguém é 100% autêntico o tempo todo, é impossível. Isso não é um problema, é normal. E por isso eu foco no resultado do trabalho de alguém e não na pessoa. Um biopic, cinebiografia ou uma biografia em geral é uma obra de ficção que tem a intenção de manipular o público a se sentir inspirado pela história de vida. Por isso muita celebridade publica auto-biografias. Porque todo mundo prefere poder controlar a narrativa da sua história em vez de confiar que outra pessoa vai dizer o que ele queria depois que morrer.</p>
<p>Você deve analisar qualquer documentário ou biopic da mesma forma que se analisa qualquer filme. Vamos analisar uma técnica pequena mas efetiva pra dar de exemplo. Porque o diretor ou escritor não aparecem pra argumentar seus pontos de vista e sim trazem outras pessoas pra dizer o que ele quer passar? Porque colocar um terceiro falando trás credibilidade, especialmente se é alguém que trabalha ou trabalhou na indústria em questão. Quando aparece o título de ex-engenheiro do Google, dá impressão que ele representa o Google inteiro. O que a maioria não pensa é que o Google é uma organização de dezenas de milhares de pessoas, e estamos ouvindo a opinião de uma pessoa que nem trabalha mais lá.</p>
<p>Outra técnica pequena. Nenhum diretor edita cenas à toa. Cada cena que ele te mostra tem um propósito, o objetivo de causar alguma reação ou emoção. Porque todos os entrevistados estão sentados? Mais importante, por que no começo do documentário mostra um pseudo-bastidores, eles chegando e se sentando, gaguejando? Isso é pra causar empatia, pra você se conectar com os entrevistados. Se aparecer direto só o título deles como ex-investidor, ex-fundador, ex-engenheiro, você vai se sentir desconectado. Mas se mostra eles chegando e se sentando na sua frente, como se estivessem chegando na sua casa, sentando do seu lado, é como se fosse um amigo ou conhecido seu chegando pra dividir uma breja com você. Você baixa sua guarda e ouve  como se fosse um amigo.</p>
<p>Mesma coisa no fim, eles discutem como tentam educar os filhos criando limites pra usar apps e redes sociais em casa. A informação em si já é super conhecida, clichê até. O objetivo não é te explicar uma solução. É só mais uma técnica de empatia. Pra vocês que são pais se relacionarem e pensar &ldquo;nossa, ele é gente como a gente, com os mesmos problemas&rdquo;. Todo político faz isso em campanha. Eu sou anti-social, essas técnicas não funcionam comigo. Eu não consigo ter a reação de me identificar com eles, pelo contrário, eles simplesmente parecem mais suspeitos fazendo isso.</p>
<p>Outra técnica inteligente pro documentário não ficar expositivo demais e por consequência muito cansativo, é editar as entrevistas com a historinha de ficção de uma família enfrentando problemas do dia a dia por causa das redes sociais. O filho que tá sendo influenciado por canais extremistas. A hora do jantar que a família não sabe mais conversar. A filha que tá sofrendo com confusão da sua identidade pelo que se fala online, entrando em depressão. Aliás, essa é a parte mais apelativa do filme, acontece na seção central e tem como objetivo causar horror e pânico em quem está assistindo.</p>
<p>(&hellip;)</p>
<p>Se você não tava convencido até agora, essa é a parte projetada pra te convencer. E essa parte te leva ao midpoint do filme, a grande revelação ou virada de eventos. Quem tá causando esse aumento no suicídio de meninas?</p>
<p>A inteligência artificial. É revelado que estamos travando uma guerra impossível de vencer. Eles explicam como o poder de processamento aumentou exponencialmente nos últimos anos mas nossos cérebros não evoluíram quase nada nos últimos milênios. E a narrativa infere que as grandes corporações que criaram essas inteligências artificiais perderam o controle dessas plataformas e a população em geral vai perder a guerra e estar a mercê de ser controlada. E quem vai consertar isso? Provavelmente essas pessoas boazinhas que o filme passou uma hora fazendo você se empatizar. É assim que você vende uma autoridade.</p>
<p>Com isso vocês entendem porque eu não dou muita bola nem pra documentários e nem pra biografias. Eu aprecio a produção quando é bem feita, e consumo como  consumiria qualquer obra de ficção. Mas eu sempre me faço a pergunta mais óbvia: &ldquo;a quem essa mensagem beneficia?&rdquo; ou o famoso clichê &ldquo;siga a trilha do dinheiro&rdquo;. Pensamento crítico não é só criticar as coisas, a premissa é que você entende como funcionam as engrenagens dos sistemas do mundo. E a palavra chave aqui é &ldquo;entende&rdquo; e não &ldquo;acredita&rdquo;. Quanto menos crenças você tiver, menos difícil é entender o mundo. A maioria de nós começa tendo uma crença e usamos isso como o molde pra enxergar o mundo. Daí tudo que não encaixa nessa crença parece um defeito que precisa ser corrigido.</p>
<p>Eu penso diferente, eu observo o mundo e crio modelo mentais em cima da realidade, incluindo as parte que não gosto. Eu aceito que o que eu considero como defeitos são resultados naturais. Eu não gostar não vai mudar nada. E daí vou tentar entender porque essa parte que eu não gostei existe, qual propósito serve ou serviu, e mais importante, como isso pode me beneficiar. Por exemplo, se você assiste o documentário, acredita na visão de mundo que ele propõe, sem questionar muito, você deveria ficar extremamente desconfortável. Se você se acha mais esperto talvez pense &ldquo;ah, por isso que eu já cancelei minha conta de Facebook&rdquo;.</p>
<p>Toda vez que eu vejo um tweet desses eu giro os olhos. Porque eu acho meio idiota mesmo. Nenhuma corporação ou inteligência artificial tá interessado em você individualmente. O Google não acorda todo dia pensando &ldquo;vamos espionar o que o Fabio Akita tá fazendo neste minuto pra poder foder ele depois&rdquo;. A eficiência de recomendar conteúdos sob medida pra mim depende de ter mais e mais detalhes sobre mim. Mas eu sei como isso funciona, pra mim não faz diferença nenhuma. Sair da rede social não muda a rede social. Você acha que só porque você twitou que saiu e meia dúzia de pessoas deram like, você é virtuoso por alguma razão? É o mesmo tipo de bullshit de alguém que twita que comprou um Tesla pra salvar o meio ambiente. Bullshit, você comprou porque tem dinheiro e o carro de fato é interessante de dirigir. Só isso. Aliás, é outro video do canal do Adam Ruins Everything que recomendo.</p>
<p>Os entrevistados do documentário, ou influencers que se gabam de ter saído do Facebook mas continuam no YouTube ou Twitter, praticam o que se chama de &ldquo;sinalização virtuosa&rdquo;, que é a prática de declarar que fez uma ação aleatória que parece virtuosa e declarando publicamente pra que outras pessoas possam dar like nela dizendo &ldquo;nossa, que pessoa virtuosa&rdquo;. É tosco, porque na prática não mudou nada. Nunca siga uma pessoa por coisas assim, e sim pelos resultados mensuráveis de sucesso e consistência ao longo dos anos, o que uma pessoa late em rede social é descartável. Entretenimento de pipoca, como qualquer filme blockbuster.</p>
<p>A grande conclusão de tudo isso, que o documentário não fala, é que se os tais problemas descritos existem, eles não são inteiramente culpa das plataformas de redes sociais ou inteligências artificiais. Eles são ferramentas. Se quiser pode pensar que são como armas. Armas não atiram sozinhas, pessoas atiram. O que essas ferramentas possibilitaram foi só acelerar e amplificar o problema dos indivíduos. A culpa sempre é dos indivíduos. Todo mundo é adulto e não dizer &ldquo;ah, eu sou um coitado, ninguém me protegeu, e por isso eu fiz merda&rdquo;. Daí aparece outra corja dizendo &ldquo;é isso mesmo, ele não tinha como saber, mas eu sou um ser iluminado e eu sei o caminho da luz, e por isso vocês devem deixar que eu proteja todo mundo&rdquo;. Isso nunca dá certo.</p>
<p>E por hoje é isso, o objetivo de hoje era dar uma pincelada em diversos assuntos sobre entendimento do mundo, entendimento do comportamento das pessoas, e a fraqueza das suas próprias convicções, inspirado por esse documentário do Netflix. Munido de tudo que eu falei, se você entendeu pelo menos metade do que eu disse, acho que pode assistir o documentário e não sair dele mais manipulado do que quando entrou. É uma produção muito bem feitinha que, tirando o melodramático, é interessante de assistir quando não tiver nada pra fazer. Ou, se você tem mais o que fazer, a versão de 5 minutos do Adam Ruins Everything diz exatamente a mesma coisa, em menos tempo. E se você curtiu esse vídeo mande um joinha, assine o canal e clique no sininho pra não perder os próximos vídeos. Compartilhe com seus amigos pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>machine-learning</category><category>psicologia</category><category>cinema-e-cultura-pop</category><category>akitando</category></item></channel></rss>