[{"data":1,"prerenderedAt":3844},["ShallowReactive",2],{"lang-switch-post-\u002Fplaylists\u002Fhow-to-build-and-finetune-slm":3,"playlist-pt-how-to-build-and-finetune-slm":4,"playlist-posts-pt-how-to-build-and-finetune-slm":55},null,{"id":5,"title":6,"body":7,"cover":3,"description":45,"extension":46,"meta":47,"navigation":48,"order":49,"path":50,"seo":51,"status":52,"stem":53,"__hash__":54},"playlists\u002Fpt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Findex.md","Modelos Pequenos de Linguagem",{"type":8,"value":9,"toc":41},"minimark",[10,25,38],[11,12,13,14,24],"p",{},"Essa playlist nasce de um livro, não de aula ao vivo nem de notebook de curso: ",[15,16,20],"a",{"href":17,"rel":18},"https:\u002F\u002Fleanpub.com\u002Fhowtobuildandfine-tuneasmalllanguagemodel",[19],"nofollow",[21,22,23],"em",{},"How to Build and Fine-Tune a Small Language Model",", do J. Paul Liu. É um livro enxuto, mais um roteiro de projeto prático com código do que um tijolo teórico, e isso é elogio: cada capítulo já vem com estimativa de custo em dólar, tempo de treino, hardware mínimo. Ver um livro de ML te dizer \"isso aqui custa 50 dólares e leva uma hora\" antes de te jogar num mar de matemática é raro, e eu queria puxar esse fio com você.",[11,26,27,28,32,33,37],{},"O plano do livro inteiro é ambicioso, 12 capítulos que vão de \"por que treinar seu próprio modelo\" até deploy em produção com ética e tudo. Por enquanto eu tô cobrindo os dois primeiros: o ",[15,29,31],{"href":30},"\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fsmall-vs-giant-language-models","Capítulo 1",", que é a fundamentação de por que um modelo pequeno especializado ganha de um gigante generalista em boa parte dos casos reais, e o ",[15,34,36],{"href":35},"\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fbuilding-gpt-from-scratch-dom-casmurro","Capítulo 2",", que constrói um GPT character-level do zero, o mesmo exercício clássico do vídeo \"Let's Build GPT\" do Andrej Karpathy (o próprio livro credita isso, sem rodeio). Se render post sobre os capítulos seguintes eu volto aqui, mas por ora é isso.",[11,39,40],{},"Um detalhe engraçado antes de começar: o sumário do livro abre com uma seção chamada \"AI Use Disclaimer\". Ou seja, o autor usou IA pra ajudar a escrever um livro ensinando você a construir IA, e eu uso o tio Claudinho pra escrever esse blog sobre o livro. É IA até debaixo d'água, mas pelo menos ninguém aqui tá fingindo o contrário.",{"title":42,"searchDepth":43,"depth":43,"links":44},"",2,[],"Capítulo a capítulo do livro \"How to Build and Fine-Tune a Small Language Model\", do J. Paul Liu. Por que treinar seu próprio modelo em vez de terceirizar tudo pra API, e como montar um GPT do zero até ele imitar Machado de Assis.","md",{},true,5,"\u002Fpt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm",{"title":6,"description":45},"published","pt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Findex","_exzbaqHjh6UlxGo74Puc6U-Cy4Z12bmsP9snCJ9OUo",[56,232],{"id":57,"title":58,"body":59,"cover":3,"date":218,"description":219,"extension":46,"meta":220,"navigation":48,"order":221,"path":222,"playlist":223,"seo":224,"status":52,"stem":225,"tags":226,"__hash__":231},"posts\u002Fpt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fsmall-vs-giant-language-models.md","SLM contra LLM: o Motoboy e o Caminhão de Mudança",{"type":8,"value":60,"toc":212},[61,70,84,89,92,99,105,111,117,120,127,131,142,148,152,197,201,204,209],[11,62,63,64,69],{},"Pensa assim: você precisa entregar uma pizza no bairro vizinho. Você chama um caminhão de mudança pra isso? Óbvio que não, você chama um motoboy. O caminhão consegue carregar geladeira, sofá, guarda-roupa, uma mudança inteira, só que ele é caro, lento pra virar esquina, e você paga o frete completo mesmo que sua \"mudança\" seja uma caixa de pizza. É basicamente esse o argumento do Capítulo 1 do livro ",[15,65,67],{"href":17,"rel":66},[19],[21,68,23],{},", só que trocando \"mudança\" por \"modelo de linguagem\".",[11,71,72,73,78,79,83],{},"Um ",[74,75,77],"glossary-term",{"definition":76},"Large Language Model, modelo de linguagem gigante, tipo GPT-4\u002F5, Claude ou Gemini, com centenas de bilhões de parâmetros treinados pra saber de tudo um pouco","LLM"," é o caminhão de mudança. Ele escreve poesia, resolve matemática avançada, explica física quântica, sabe de quase tudo. Mas ele também custa caro pra rodar em volume, manda seus dados pros servidores de outra empresa, e você não consegue abrir o capô e trocar uma peça se ele erra sempre o mesmo tipo de pergunta. Um ",[74,80,82],{"definition":81},"Small Language Model, modelo pequeno, geralmente entre 125 milhões e 7 bilhões de parâmetros, especializado numa tarefa em vez de generalista","SLM"," é o motoboy. Ele não sabe fazer de tudo, mas pra entrega rápida, especializada, e barata, ele ganha do caminhão sempre.",[85,86,88],"h2",{"id":87},"por-que-pequeno-ganha-do-gigante-às-vezes","Por que \"pequeno\" ganha do \"gigante\" às vezes",[11,90,91],{},"O livro lista cinco motivos práticos, e eu prefiro recontar cada um com um exemplo de verdade em vez de ficar na teoria:",[11,93,94,98],{},[95,96,97],"strong",{},"Custo."," Rodar consulta via API de LLM gigante em volume alto custa de centenas a milhares de dólares por mês. Treinar ou ajustar o modelo gigante do zero? Milhões. O livro dá um exemplo concreto que eu achei ótimo: uma central de atendimento processando 10 mil tickets de suporte por dia gastaria de 500 a 1.000 dólares por mês numa API. Um modelo local rodando na sua própria máquina custa uns 50 dólares por mês, e olha que isso é só a conta de luz.",[11,100,101,104],{},[95,102,103],{},"Privacidade."," Prontuário médico, processo jurídico, dado de pesquisa não publicada, tudo isso não pode simplesmente viajar pro servidor de terceiro. Se um hospital manda nota de paciente pra API de fora, já era a HIPAA (a lei americana de privacidade em saúde). Modelo rodando local resolve isso na hora: o dado nunca sai de casa.",[11,106,107,110],{},[95,108,109],{},"Controle."," Se o LLM gigante erra sempre a mesma terminologia técnica do seu campo, você não tem como consertar isso, você só reclama e espera a próxima versão. Modelo seu, você ajusta.",[11,112,113,116],{},[95,114,115],{},"Disponibilidade e velocidade."," API cai, muda de preço, ou fica lenta na fila em horário de pico. Modelo local não depende da internet nem da decisão de negócio de ninguém além de você.",[11,118,119],{},"O livro dá dois exemplos de \"quando compensa\" que eu curti bastante: um geólogo que precisa extrair dado mineral de milhares de artigo científico vai se dar melhor com um modelo treinado no vocabulário de geologia (\"plagioclase feldspar\", \"phenocryst\") do que com um generalista. E uma fazenda usando um modelo de visão pequeno pra detectar doença de planta direto no local, sem depender de internet, ganha em velocidade e em não pagar API pra cada foto tirada.",[11,121,122,123,126],{},"Só que a régua não é \"SLM sempre vence\". Se você precisa de conhecimento geral amplo, raciocínio complexo em múltiplas etapas, ou informação atualizada em tempo real, o caminhão de mudança ainda ganha. O livro resume isso numa frase que eu concordo cem por cento: ",[95,124,125],{},"LLM gigante é generalista, SLM é especialista."," Se você consegue definir bem sua tarefa e tem dado relevante pra treinar, o modelo pequeno tende a te servir melhor. Se não consegue, o gigante generalista cobre a lacuna.",[85,128,130],{"id":129},"o-que-o-livro-vai-te-fazer-construir","O que o livro vai te fazer construir",[11,132,133,134,137,138,141],{},"Boa parte do Capítulo 1 é roteiro do livro inteiro, e eu vou resumir rápido porque isso ajuda a entender onde os dois posts que eu vou escrever se encaixam. O livro usa duas arquiteturas de referência: ",[95,135,136],{},"GPT-2"," (2019, da OpenAI, de 125 milhões a 1,5 bilhão de parâmetros, ponto de partida clássico) e ",[95,139,140],{},"MiniMind"," (mais recente, de 26 a 218 milhões de parâmetros, otimizado pra rodar em hardware menor com um pipeline de treino em três estágios: pré-treino, ajuste fino supervisionado, e otimização de preferência direta). São 12 capítulos ao todo, organizados em quatro fases: fundamentos (capítulos 1 a 3, onde você entende a arquitetura e faz seu primeiro ajuste fino em meia hora), treino do zero em profundidade (capítulos 4 a 7), o estudo de caso completo com o MiniMind (capítulos 8 a 10), e produção (capítulos 11 e 12, deploy de verdade).",[11,143,144,145,147],{},"Eu tô cobrindo só o ",[15,146,36],{"href":35}," por enquanto, que é a \"Fase 1\" do próprio livro: construir um GPT pequeno do zero pra entender o motor por dentro antes de ajustar fino qualquer coisa pronta.",[85,149,151],{"id":150},"fechando","Fechando",[153,154,155,169],"table",{},[156,157,158],"thead",{},[159,160,161,166],"tr",{},[162,163,165],"th",{"align":164},"left","O que eu já sabia",[162,167,168],{"align":164},"O que esse capítulo assentou",[170,171,172,181,189],"tbody",{},[159,173,174,178],{},[175,176,177],"td",{"align":164},"Modelo grande é mais poderoso",[175,179,180],{"align":164},"Mais poderoso não é sinônimo de \"melhor pra sua tarefa específica\"",[159,182,183,186],{},[175,184,185],{"align":164},"Treinar modelo custa caro",[175,187,188],{"align":164},"Só se for o modelo errado pro tamanho do problema: 50 dólares por mês local contra 500 a 1.000 dólares por mês de API é uma conta que qualquer um faz",[159,190,191,194],{},[175,192,193],{"align":164},"\"Fine-tuning\" é jargão vago",[175,195,196],{"align":164},"É literalmente pegar um modelo que já sabe \"linguagem em geral\" e ajustar só o suficiente pra ele saber sua tarefa específica",[85,198,200],{"id":199},"aplicação-prática","Aplicação Prática",[11,202,203],{},"O número mais concreto do capítulo é esse exemplo de central de atendimento, 10 mil tickets de suporte por dia. Não inventei nada aqui, são os dois valores que o próprio livro cita: API de LLM gigante fica entre 500 e 1.000 dólares por mês, modelo local fica em 50 dólares por mês (a conta de luz, já que o hardware você já tem ou aluga uma vez só). Botei os dois lado a lado pra você ver a escala da diferença de cara, usando o piso da faixa da API pra ser justo com o número menor.",[205,206],"slm-cost-bar-chart",{":bars":207,"y-label":208},"[{\"label\":\"API (LLM gigante)\",\"cost\":500,\"color\":\"#cc3300\"},{\"label\":\"Modelo local (SLM)\",\"cost\":50,\"color\":\"#0033cc\"}]","US$\u002Fmês",[11,210,211],{},"Dez vezes mais barato, todo mês, pro mesmo volume de tickets. É esse tipo de conta que separa \"seria legal ter meu próprio modelo\" de \"faz sentido financeiro ter meu próprio modelo\". No próximo post a gente constrói o motor por dentro.",{"title":42,"searchDepth":43,"depth":43,"links":213},[214,215,216,217],{"id":87,"depth":43,"text":88},{"id":129,"depth":43,"text":130},{"id":150,"depth":43,"text":151},{"id":199,"depth":43,"text":200},"2026-08-23","O Capítulo 1 do livro do J. Paul Liu defende que modelo gigante nem sempre é a resposta certa. Eu conto isso com a metáfora mais brasileira que existe (motoboy contra caminhão de mudança) e confiro os números reais de custo que o livro usa.",{},1,"\u002Fpt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fsmall-vs-giant-language-models","how-to-build-and-finetune-slm",{"title":58,"description":219},"pt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fsmall-vs-giant-language-models",[227,228,229,230],"slm","llm","custo","hardware","WR7W7JZxVygcYkgbNt3X4KDa5Y3tyo1p5Au0WpbfH_o",{"id":233,"title":234,"body":235,"cover":3,"date":218,"description":3833,"extension":46,"meta":3834,"navigation":48,"order":43,"path":3835,"playlist":223,"seo":3836,"status":52,"stem":3837,"tags":3838,"__hash__":3843},"posts\u002Fpt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fbuilding-gpt-from-scratch-dom-casmurro.md","Ensinando um GPT do Zero a Escrever Feito Machado de Assis",{"type":8,"value":236,"toc":3813},[237,265,268,272,286,324,327,335,342,346,371,389,392,437,466,469,530,551,555,580,583,642,658,662,732,762,773,777,922,947,972,979,983,1246,1273,1306,1334,1338,1353,1416,1435,1444,1451,1455,1458,1547,1575,1578,1607,1642,1651,1686,1705,1745,1763,1776,1780,1900,1949,1958,1961,1967,1974,1978,1998,2007,2012,2021,2026,2035,2040,2077,2086,2092,2101,2107,2121,2125,2249,2280,2300,2310,2330,2340,2348,3121,3162,3177,3218,3451,3487,3491,3618,3632,3636,3658,3728,3731,3735,3738,3746,3749,3751,3793,3795,3798,3806,3809],[11,238,239,240,245,246,250,251,256,257,264],{},"O Capítulo 2 do livro constrói um GPT minúsculo, uns 200 mil parâmetros, character-level, do zero. É literalmente o vídeo \"Let's Build GPT from Scratch\" do Andrej Karpathy reescrito em prosa, o próprio livro admite a fonte na cara. Eu segui o exercício com dois notebooks lado a lado: o ",[15,241,244],{"href":242,"rel":243},"https:\u002F\u002Fcolab.research.google.com\u002Fgithub\u002Fkarpathy\u002Fng-video-lecture\u002Fblob\u002Fmaster\u002Fgpt_dev.ipynb",[19],"notebook original que o próprio Karpathy cita como fonte do vídeo"," (",[247,248,249],"code",{},"gpt_dev.ipynb",", treinado em Shakespeare), e ",[15,252,255],{"href":253,"rel":254},"https:\u002F\u002Fcolab.research.google.com\u002Fdrive\u002F1NIpd8W15jVf8oFwNKkOrB0Z3B4qaRZtW?usp=sharing",[19],"o meu",", onde troquei Shakespeare por ",[15,258,261],{"href":259,"rel":260},"https:\u002F\u002Fwww.gutenberg.org\u002Febooks\u002F55752",[19],[21,262,263],{},"Dom Casmurro"," do Machado de Assis, texto de domínio público, pra ver se a mesma arquitetura minúscula aprende português também.",[11,266,267],{},"Esse post é o notebook inteiro passado a limpo com você, célula por célula, código completo, sem pular nada. Não é resumo, é dissecação.",[85,269,271],{"id":270},"baixando-e-conferindo-o-dataset","Baixando e conferindo o dataset",[273,274,278],"pre",{"className":275,"code":276,"language":277,"meta":42,"style":42},"language-python shiki shiki-themes github-light github-dark","!wget https:\u002F\u002Fwww.gutenberg.org\u002Fcache\u002Fepub\u002F55752\u002Fpg55752.txt\n","python",[247,279,280],{"__ignoreMap":42},[281,282,284],"span",{"class":283,"line":221},"line",[281,285,276],{},[273,287,289],{"className":275,"code":288,"language":277,"meta":42,"style":42},"with open('pg55752.txt', 'r') as file:\n    texto = file.read()\n\nprint(\"Tamanho do dataset\", len(texto))\nprint(\"===== Trecho do texto =====\")\nprint(texto[:1000])\n",[247,290,291,296,301,307,313,318],{"__ignoreMap":42},[281,292,293],{"class":283,"line":221},[281,294,295],{},"with open('pg55752.txt', 'r') as file:\n",[281,297,298],{"class":283,"line":43},[281,299,300],{},"    texto = file.read()\n",[281,302,304],{"class":283,"line":303},3,[281,305,306],{"emptyLinePlaceholder":48},"\n",[281,308,310],{"class":283,"line":309},4,[281,311,312],{},"print(\"Tamanho do dataset\", len(texto))\n",[281,314,315],{"class":283,"line":49},[281,316,317],{},"print(\"===== Trecho do texto =====\")\n",[281,319,321],{"class":283,"line":320},6,[281,322,323],{},"print(texto[:1000])\n",[11,325,326],{},"Saída real:",[273,328,333],{"className":329,"code":331,"language":332},[330],"language-text","Tamanho do dataset 400944\n===== Trecho do texto =====\nThe Project Gutenberg eBook of Dom Casmurro\n...\nTitle: Dom Casmurro\nAuthor: Machado de Assis\n","text",[247,334,331],{"__ignoreMap":42},[11,336,337,338,341],{},"400.944 caracteres, incluindo o cabeçalho padrão do Gutenberg (licença, título, autor) que vem junto do arquivo ",[247,339,340],{},".txt"," bruto, isso não foi limpo, é o texto exatamente como o site entrega.",[85,343,345],{"id":344},"vocabulário-cada-caractere-é-um-número","Vocabulário: cada caractere é um número",[273,347,349],{"className":275,"code":348,"language":277,"meta":42,"style":42},"caracteres = sorted(list(set(texto)))\ntamanho_vocabulario = len(caracteres)\nprint(\"Vocabulário:\", ''.join(caracteres), \"\\n\")\nprint(\"Tamanho do Vocabulário: \", tamanho_vocabulario)\n",[247,350,351,356,361,366],{"__ignoreMap":42},[281,352,353],{"class":283,"line":221},[281,354,355],{},"caracteres = sorted(list(set(texto)))\n",[281,357,358],{"class":283,"line":43},[281,359,360],{},"tamanho_vocabulario = len(caracteres)\n",[281,362,363],{"class":283,"line":303},[281,364,365],{},"print(\"Vocabulário:\", ''.join(caracteres), \"\\n\")\n",[281,367,368],{"class":283,"line":309},[281,369,370],{},"print(\"Tamanho do Vocabulário: \", tamanho_vocabulario)\n",[11,372,373,376,377,380,381,384,385,388],{},[247,374,375],{},"set(texto)"," pega cada caractere único que aparece no arquivo, ",[247,378,379],{},"sorted()"," ordena, e o tamanho desse conjunto é o vocabulário. Saída: ",[95,382,383],{},"112 caracteres",", contra os ",[95,386,387],{},"65"," do Shakespeare original, quase o dobro por causa de acento (á, ã, ç, é, í, ó, ô, õ, ú), aspas curvas, e uns símbolos tipográficos que sobraram do texto do Gutenberg.",[11,390,391],{},"Com o vocabulário definido, dá pra montar o tradutor caractere-número dos dois lados:",[273,393,395],{"className":275,"code":394,"language":277,"meta":42,"style":42},"string_to_integer = {ch:i for i, ch in enumerate(caracteres)}\ninteger_to_string = {i:ch for i, ch in enumerate(caracteres)}\n\nencode = lambda s: [string_to_integer[c] for c in s]\ndecode = lambda s: ''.join([integer_to_string[i] for i in s])\n\nprint(encode(\"Teste de encode\"))\nprint(decode(encode(\"Teste de decode\")))\n",[247,396,397,402,407,411,416,421,425,431],{"__ignoreMap":42},[281,398,399],{"class":283,"line":221},[281,400,401],{},"string_to_integer = {ch:i for i, ch in enumerate(caracteres)}\n",[281,403,404],{"class":283,"line":43},[281,405,406],{},"integer_to_string = {i:ch for i, ch in enumerate(caracteres)}\n",[281,408,409],{"class":283,"line":303},[281,410,306],{"emptyLinePlaceholder":48},[281,412,413],{"class":283,"line":309},[281,414,415],{},"encode = lambda s: [string_to_integer[c] for c in s]\n",[281,417,418],{"class":283,"line":49},[281,419,420],{},"decode = lambda s: ''.join([integer_to_string[i] for i in s])\n",[281,422,423],{"class":283,"line":320},[281,424,306],{"emptyLinePlaceholder":48},[281,426,428],{"class":283,"line":427},7,[281,429,430],{},"print(encode(\"Teste de encode\"))\n",[281,432,434],{"class":283,"line":433},8,[281,435,436],{},"print(decode(encode(\"Teste de decode\")))\n",[11,438,439,442,443,446,447,450,451,454,455,458,459,462,463,465],{},[247,440,441],{},"string_to_integer"," é um dicionário caractere → posição no vocabulário ordenado, ",[247,444,445],{},"integer_to_string"," é o inverso. ",[247,448,449],{},"encode"," troca cada letra de uma string pela sua posição, ",[247,452,453],{},"decode"," faz o caminho de volta. Rodar ",[247,456,457],{},"encode(\"Teste de encode\")"," devolve ",[247,460,461],{},"[49, 63, 77, 78, 63, 1, 62, 63, 1, 63, 72, 61, 73, 62, 63]",", uma lista de número pura, e ",[247,464,453],{}," de volta recupera o texto igualzinho.",[11,467,468],{},"Só falta jogar isso tudo pro formato que o PyTorch entende:",[273,470,472],{"className":275,"code":471,"language":277,"meta":42,"style":42},"#Armazenando o texto encodado em um torch.Tensor\nimport torch\ndata = torch.tensor(encode(texto), dtype=torch.long)\n\nprint(data.shape, data.dtype)\nprint(data[:1000])\n\n# Re-definindo a função decode para lidar com tensores PyTorch\ndef decode(s):\n    # Converte cada elemento para Python int se for um tensor\n    return ''.join([integer_to_string[i.item()] if isinstance(i, torch.Tensor) else integer_to_string[i] for i in s])\n",[247,473,474,479,484,489,493,498,503,507,512,518,524],{"__ignoreMap":42},[281,475,476],{"class":283,"line":221},[281,477,478],{},"#Armazenando o texto encodado em um torch.Tensor\n",[281,480,481],{"class":283,"line":43},[281,482,483],{},"import torch\n",[281,485,486],{"class":283,"line":303},[281,487,488],{},"data = torch.tensor(encode(texto), dtype=torch.long)\n",[281,490,491],{"class":283,"line":309},[281,492,306],{"emptyLinePlaceholder":48},[281,494,495],{"class":283,"line":49},[281,496,497],{},"print(data.shape, data.dtype)\n",[281,499,500],{"class":283,"line":320},[281,501,502],{},"print(data[:1000])\n",[281,504,505],{"class":283,"line":427},[281,506,306],{"emptyLinePlaceholder":48},[281,508,509],{"class":283,"line":433},[281,510,511],{},"# Re-definindo a função decode para lidar com tensores PyTorch\n",[281,513,515],{"class":283,"line":514},9,[281,516,517],{},"def decode(s):\n",[281,519,521],{"class":283,"line":520},10,[281,522,523],{},"    # Converte cada elemento para Python int se for um tensor\n",[281,525,527],{"class":283,"line":526},11,[281,528,529],{},"    return ''.join([integer_to_string[i.item()] if isinstance(i, torch.Tensor) else integer_to_string[i] for i in s])\n",[11,531,532,535,536,538,539,542,543,546,547,550],{},[247,533,534],{},"data"," vira um tensor de 400.944 números inteiros, o livro inteiro codificado numa tira só. Repara que ",[247,537,453],{}," foi reescrita aqui: a primeira versão só sabia lidar com lista de ",[247,540,541],{},"int"," do Python puro, essa segunda versão também aceita tensor do PyTorch (checando ",[247,544,545],{},"isinstance"," e chamando ",[247,548,549],{},".item()"," quando precisa), porque daqui pra frente tudo que sai do modelo vem em formato de tensor.",[85,552,554],{"id":553},"split-de-treinovalidação-e-o-tamanho-da-janela","Split de treino\u002Fvalidação e o tamanho da janela",[273,556,558],{"className":275,"code":557,"language":277,"meta":42,"style":42},"#Split dados para treino e para avaliação\nn = int(0.9*len(data))\ntreino = data[:n]\navaliacao = data[n:]\n",[247,559,560,565,570,575],{"__ignoreMap":42},[281,561,562],{"class":283,"line":221},[281,563,564],{},"#Split dados para treino e para avaliação\n",[281,566,567],{"class":283,"line":43},[281,568,569],{},"n = int(0.9*len(data))\n",[281,571,572],{"class":283,"line":303},[281,573,574],{},"treino = data[:n]\n",[281,576,577],{"class":283,"line":309},[281,578,579],{},"avaliacao = data[n:]\n",[11,581,582],{},"90% pra treino, 10% pra avaliação, corte simples por posição (sem embaralhar, então o modelo nunca vê o final do livro durante o treino).",[273,584,586],{"className":275,"code":585,"language":277,"meta":42,"style":42},"#Definição do bloco de contexto para as previsões do modelo\n# Isso significa que, para prever o próximo caractere,\n#o modelo considerará no máximo os 8 caracteres anteriores.\n#É um hiperparâmetro chave para a arquitetura do Transformer.\nbloco_contexto = 8\n\n# Este é um exemplo de como uma sequência de entrada x\n#e seu alvo y (que é a mesma sequência deslocada em um token)\n#seriam construídos a partir dos dados de treinamento.\ntreino[:bloco_contexto+1]\nprint(decode(treino[:bloco_contexto+1]))\n",[247,587,588,593,598,603,608,613,617,622,627,632,637],{"__ignoreMap":42},[281,589,590],{"class":283,"line":221},[281,591,592],{},"#Definição do bloco de contexto para as previsões do modelo\n",[281,594,595],{"class":283,"line":43},[281,596,597],{},"# Isso significa que, para prever o próximo caractere,\n",[281,599,600],{"class":283,"line":303},[281,601,602],{},"#o modelo considerará no máximo os 8 caracteres anteriores.\n",[281,604,605],{"class":283,"line":309},[281,606,607],{},"#É um hiperparâmetro chave para a arquitetura do Transformer.\n",[281,609,610],{"class":283,"line":49},[281,611,612],{},"bloco_contexto = 8\n",[281,614,615],{"class":283,"line":320},[281,616,306],{"emptyLinePlaceholder":48},[281,618,619],{"class":283,"line":427},[281,620,621],{},"# Este é um exemplo de como uma sequência de entrada x\n",[281,623,624],{"class":283,"line":433},[281,625,626],{},"#e seu alvo y (que é a mesma sequência deslocada em um token)\n",[281,628,629],{"class":283,"line":514},[281,630,631],{},"#seriam construídos a partir dos dados de treinamento.\n",[281,633,634],{"class":283,"line":520},[281,635,636],{},"treino[:bloco_contexto+1]\n",[281,638,639],{"class":283,"line":526},[281,640,641],{},"print(decode(treino[:bloco_contexto+1]))\n",[11,643,644,645,648,649,652,653,657],{},"Saída: ",[247,646,647],{},"The Proje",", os primeiros 9 caracteres do livro (depois do cabeçalho ser cortado pela indexação). ",[247,650,651],{},"bloco_contexto = 8"," define o ",[74,654,656],{"definition":655},"quantidade máxima de tokens anteriores que o modelo consegue olhar pra fazer uma previsão, um hiperparâmetro fixo da arquitetura","context window",", o modelo nunca vai enxergar mais que 8 caracteres pra trás nessa fase inicial de teste.",[85,659,661],{"id":660},"bug-1-misturando-treino-com-avaliação","Bug #1: misturando treino com avaliação",[273,663,665],{"className":275,"code":664,"language":277,"meta":42,"style":42},"#cria sequencia de entrada x\nx = treino[:bloco_contexto]\n\n#cria sequência de targets y\n#y é a mesma sequência que x mas deslocada 1 token para frente\n#O modelo prevê y[t] dado x[:t+1]\ny = avaliacao[1:bloco_contexto+1]\n\n#loop para simular como o modelo vê a sequência\nfor tensor in range(bloco_contexto):\n    contexto = x[:tensor+1]\n    target = y[tensor]\n    print(f\"Quando tensor é {contexto} o target é {target}\")\n",[247,666,667,672,677,681,686,691,696,701,705,710,715,720,726],{"__ignoreMap":42},[281,668,669],{"class":283,"line":221},[281,670,671],{},"#cria sequencia de entrada x\n",[281,673,674],{"class":283,"line":43},[281,675,676],{},"x = treino[:bloco_contexto]\n",[281,678,679],{"class":283,"line":303},[281,680,306],{"emptyLinePlaceholder":48},[281,682,683],{"class":283,"line":309},[281,684,685],{},"#cria sequência de targets y\n",[281,687,688],{"class":283,"line":49},[281,689,690],{},"#y é a mesma sequência que x mas deslocada 1 token para frente\n",[281,692,693],{"class":283,"line":320},[281,694,695],{},"#O modelo prevê y[t] dado x[:t+1]\n",[281,697,698],{"class":283,"line":427},[281,699,700],{},"y = avaliacao[1:bloco_contexto+1]\n",[281,702,703],{"class":283,"line":433},[281,704,306],{"emptyLinePlaceholder":48},[281,706,707],{"class":283,"line":514},[281,708,709],{},"#loop para simular como o modelo vê a sequência\n",[281,711,712],{"class":283,"line":520},[281,713,714],{},"for tensor in range(bloco_contexto):\n",[281,716,717],{"class":283,"line":526},[281,718,719],{},"    contexto = x[:tensor+1]\n",[281,721,723],{"class":283,"line":722},12,[281,724,725],{},"    target = y[tensor]\n",[281,727,729],{"class":283,"line":728},13,[281,730,731],{},"    print(f\"Quando tensor é {contexto} o target é {target}\")\n",[11,733,734,735,738,739,742,743,738,746,749,750,753,754,757,758,761],{},"Achei o bug aqui: ",[247,736,737],{},"x"," vem de ",[247,740,741],{},"treino",", mas ",[247,744,745],{},"y",[247,747,748],{},"avaliacao",". São dois pedaços de texto completamente diferentes do livro, então o alvo que o print mostra não tem nenhuma relação real com o contexto que veio antes dele. Compara com o notebook original, que usa ",[247,751,752],{},"train_data"," nas duas linhas: ",[247,755,756],{},"x = train_data[:block_size]"," e ",[247,759,760],{},"y = train_data[1:block_size+1]",", sem trocar de split no meio.",[11,763,764,765,768,769,772],{},"A saída bate com o erro: ",[247,766,767],{},"Quando tensor é tensor([49]) o target é 12"," não faz o menor sentido (12 é o código de um dígito ou símbolo qualquer do vocabulário, sem relação com \"T\" de \"The\"). O código não quebra (os dois splits têm o mesmo formato de tensor), só produz um exemplo didático sem sentido. Boa notícia: esse bug mora só nessa célula de ilustração manual, a função ",[247,770,771],{},"get_batch"," que vem a seguir nunca mistura splits.",[85,774,776],{"id":775},"empacotando-em-lotes-de-verdade","Empacotando em lotes de verdade",[273,778,780],{"className":275,"code":779,"language":277,"meta":42,"style":42},"torch.manual_seed(1337)\nbatch_size = 4 # Quantas sequências independentes serão processadas em paralelo\nblock_size = 8 # Qual tamanho máximo de contexto para predições\n\ndef get_batch(split):\n  #Criando um lote pequeno de dados de entrada x e targets y\n  dados = treino if split == 'treino' else avaliacao\n  ix = torch.randint(len(dados) - block_size, (batch_size,))\n  x = torch.stack([data[i:i+block_size] for i in ix])\n  y = torch.stack([dados[i+1:i+block_size+1] for i in ix])\n  return x, y\n\nxb, yb = get_batch('treino')\nprint(\"Inputs:\")\nprint(xb.shape)\nprint(xb)\nprint('----------')\nprint(\"Targets:\")\nprint(yb.shape)\nprint(yb)\n\nfor b in range(batch_size): #Dimensão batch\n  for t in range(block_size): #Dimensão time\n    contexto_time = xb[b, :t+1]\n    target_time = yb[b,t]\n    print(f\"Quando input é {contexto_time.tolist()} - o target é {target_time}\")\n",[247,781,782,787,792,797,801,806,811,816,821,826,831,836,840,845,851,857,863,869,875,881,887,892,898,904,910,916],{"__ignoreMap":42},[281,783,784],{"class":283,"line":221},[281,785,786],{},"torch.manual_seed(1337)\n",[281,788,789],{"class":283,"line":43},[281,790,791],{},"batch_size = 4 # Quantas sequências independentes serão processadas em paralelo\n",[281,793,794],{"class":283,"line":303},[281,795,796],{},"block_size = 8 # Qual tamanho máximo de contexto para predições\n",[281,798,799],{"class":283,"line":309},[281,800,306],{"emptyLinePlaceholder":48},[281,802,803],{"class":283,"line":49},[281,804,805],{},"def get_batch(split):\n",[281,807,808],{"class":283,"line":320},[281,809,810],{},"  #Criando um lote pequeno de dados de entrada x e targets y\n",[281,812,813],{"class":283,"line":427},[281,814,815],{},"  dados = treino if split == 'treino' else avaliacao\n",[281,817,818],{"class":283,"line":433},[281,819,820],{},"  ix = torch.randint(len(dados) - block_size, (batch_size,))\n",[281,822,823],{"class":283,"line":514},[281,824,825],{},"  x = torch.stack([data[i:i+block_size] for i in ix])\n",[281,827,828],{"class":283,"line":520},[281,829,830],{},"  y = torch.stack([dados[i+1:i+block_size+1] for i in ix])\n",[281,832,833],{"class":283,"line":526},[281,834,835],{},"  return x, y\n",[281,837,838],{"class":283,"line":722},[281,839,306],{"emptyLinePlaceholder":48},[281,841,842],{"class":283,"line":728},[281,843,844],{},"xb, yb = get_batch('treino')\n",[281,846,848],{"class":283,"line":847},14,[281,849,850],{},"print(\"Inputs:\")\n",[281,852,854],{"class":283,"line":853},15,[281,855,856],{},"print(xb.shape)\n",[281,858,860],{"class":283,"line":859},16,[281,861,862],{},"print(xb)\n",[281,864,866],{"class":283,"line":865},17,[281,867,868],{},"print('----------')\n",[281,870,872],{"class":283,"line":871},18,[281,873,874],{},"print(\"Targets:\")\n",[281,876,878],{"class":283,"line":877},19,[281,879,880],{},"print(yb.shape)\n",[281,882,884],{"class":283,"line":883},20,[281,885,886],{},"print(yb)\n",[281,888,890],{"class":283,"line":889},21,[281,891,306],{"emptyLinePlaceholder":48},[281,893,895],{"class":283,"line":894},22,[281,896,897],{},"for b in range(batch_size): #Dimensão batch\n",[281,899,901],{"class":283,"line":900},23,[281,902,903],{},"  for t in range(block_size): #Dimensão time\n",[281,905,907],{"class":283,"line":906},24,[281,908,909],{},"    contexto_time = xb[b, :t+1]\n",[281,911,913],{"class":283,"line":912},25,[281,914,915],{},"    target_time = yb[b,t]\n",[281,917,919],{"class":283,"line":918},26,[281,920,921],{},"    print(f\"Quando input é {contexto_time.tolist()} - o target é {target_time}\")\n",[11,923,924,927,928,931,932,935,936,938,939,942,943,946],{},[247,925,926],{},"ix"," sorteia ",[247,929,930],{},"batch_size"," posições aleatórias de início dentro do split escolhido, e ",[247,933,934],{},"torch.stack"," empilha ",[247,937,930],{}," janelas de ",[247,940,941],{},"block_size"," caracteres numa matriz só. Saída real: ",[247,944,945],{},"Inputs: torch.Size([4, 8])",", um lote de 4 sequências de 8 caracteres cada, processadas em paralelo. O loop de baixo só serve pra visualizar: pra cada sequência do lote, mostra as 8 previsões progressivas que ela contém (prever o 2º caractere sabendo o 1º, prever o 3º sabendo os 2 primeiros, e assim por diante), então um lote de 4×8 na verdade ensina o modelo em 32 exemplos de previsão de uma vez só.",[273,948,950],{"className":275,"code":949,"language":277,"meta":42,"style":42},"# Este output nos permite visualizar a forma exata\n#e os valores numéricos das sequências de entrada\n#que o modelo começará a processar.\nprint(xb) # Input em forma de tensor\n",[247,951,952,957,962,967],{"__ignoreMap":42},[281,953,954],{"class":283,"line":221},[281,955,956],{},"# Este output nos permite visualizar a forma exata\n",[281,958,959],{"class":283,"line":43},[281,960,961],{},"#e os valores numéricos das sequências de entrada\n",[281,963,964],{"class":283,"line":303},[281,965,966],{},"#que o modelo começará a processar.\n",[281,968,969],{"class":283,"line":309},[281,970,971],{},"print(xb) # Input em forma de tensor\n",[11,973,974,975,978],{},"Só reimprime ",[247,976,977],{},"xb",", sem novidade, útil como checkpoint visual antes de montar o modelo.",[85,980,982],{"id":981},"o-primeiro-modelo-e-o-bug-2-escondido-nele","O primeiro modelo, e o Bug #2 escondido nele",[273,984,986],{"className":275,"code":985,"language":277,"meta":42,"style":42},"import torch\nimport torch.nn as nn\nfrom torch.nn import functional as F\ntorch.manual_seed(1337)\n\nclass BigramLanguageModel(nn.Module):\n  def __init__(self, tam_vocabulario):\n    super().__init__()\n    #cada token lê diretamente os logits para o\n    #próximo token a partir de uma tabela de consulta\n    self.token_embeeding_table = nn.Embedding(tam_vocabulario, tam_vocabulario)\n\n  def forward(self, idx, targets=None):\n    # idx e targets são ambos tensores de\n    #inteiros de formato (B, T)\n    logits = self.token_embeeding_table(idx) # (B, T, C)\n\n    if targets is None:\n      loss = None\n    else:\n      B, T, C = logits.shape\n      logits = logits.view(B*T, C)\n      targets = targets.view(B*T)\n      loss = F.cross_entropy(logits, targets)\n\n    return logits, loss\n\n  def generate(self, idx, max_new_tokens):\n    # idx é um array de índices (B, T)\n    #no contexto atual\n    for _ in range(max_new_tokens):\n      #\"pega\" as predições\n      logits, loss = self(idx)\n      #foca apenas no último passo de tempo\n      logits = logits[:, -1, : ] #Vira B, C\n      # aplica softmax para obter probabilidades\n      probabilidades = F.softmax(logits, dim=-1) # (B, C)\n      # amostra da distribuição\n      idx_next = torch.multinomial(probabilidades, num_samples=1) # (B, T+1)\n    return idx\n\nmodelo = BigramLanguageModel(tamanho_vocabulario)\nsaida, loss = modelo(xb, yb)\nprint(saida.shape)\nprint(loss)\n\nprint(\"-----\")\n\nprint(decode(modelo.generate(idx = torch.zeros((1,1), dtype=torch.long), max_new_tokens=100)[0].tolist()))\n",[247,987,988,992,997,1002,1006,1010,1015,1020,1025,1030,1035,1040,1044,1049,1054,1059,1064,1068,1073,1078,1083,1088,1093,1098,1103,1107,1112,1117,1123,1129,1135,1141,1147,1153,1159,1165,1171,1177,1183,1189,1195,1200,1206,1212,1218,1224,1229,1235,1240],{"__ignoreMap":42},[281,989,990],{"class":283,"line":221},[281,991,483],{},[281,993,994],{"class":283,"line":43},[281,995,996],{},"import torch.nn as nn\n",[281,998,999],{"class":283,"line":303},[281,1000,1001],{},"from torch.nn import functional as F\n",[281,1003,1004],{"class":283,"line":309},[281,1005,786],{},[281,1007,1008],{"class":283,"line":49},[281,1009,306],{"emptyLinePlaceholder":48},[281,1011,1012],{"class":283,"line":320},[281,1013,1014],{},"class BigramLanguageModel(nn.Module):\n",[281,1016,1017],{"class":283,"line":427},[281,1018,1019],{},"  def __init__(self, tam_vocabulario):\n",[281,1021,1022],{"class":283,"line":433},[281,1023,1024],{},"    super().__init__()\n",[281,1026,1027],{"class":283,"line":514},[281,1028,1029],{},"    #cada token lê diretamente os logits para o\n",[281,1031,1032],{"class":283,"line":520},[281,1033,1034],{},"    #próximo token a partir de uma tabela de consulta\n",[281,1036,1037],{"class":283,"line":526},[281,1038,1039],{},"    self.token_embeeding_table = nn.Embedding(tam_vocabulario, tam_vocabulario)\n",[281,1041,1042],{"class":283,"line":722},[281,1043,306],{"emptyLinePlaceholder":48},[281,1045,1046],{"class":283,"line":728},[281,1047,1048],{},"  def forward(self, idx, targets=None):\n",[281,1050,1051],{"class":283,"line":847},[281,1052,1053],{},"    # idx e targets são ambos tensores de\n",[281,1055,1056],{"class":283,"line":853},[281,1057,1058],{},"    #inteiros de formato (B, T)\n",[281,1060,1061],{"class":283,"line":859},[281,1062,1063],{},"    logits = self.token_embeeding_table(idx) # (B, T, C)\n",[281,1065,1066],{"class":283,"line":865},[281,1067,306],{"emptyLinePlaceholder":48},[281,1069,1070],{"class":283,"line":871},[281,1071,1072],{},"    if targets is None:\n",[281,1074,1075],{"class":283,"line":877},[281,1076,1077],{},"      loss = None\n",[281,1079,1080],{"class":283,"line":883},[281,1081,1082],{},"    else:\n",[281,1084,1085],{"class":283,"line":889},[281,1086,1087],{},"      B, T, C = logits.shape\n",[281,1089,1090],{"class":283,"line":894},[281,1091,1092],{},"      logits = logits.view(B*T, C)\n",[281,1094,1095],{"class":283,"line":900},[281,1096,1097],{},"      targets = targets.view(B*T)\n",[281,1099,1100],{"class":283,"line":906},[281,1101,1102],{},"      loss = F.cross_entropy(logits, targets)\n",[281,1104,1105],{"class":283,"line":912},[281,1106,306],{"emptyLinePlaceholder":48},[281,1108,1109],{"class":283,"line":918},[281,1110,1111],{},"    return logits, loss\n",[281,1113,1115],{"class":283,"line":1114},27,[281,1116,306],{"emptyLinePlaceholder":48},[281,1118,1120],{"class":283,"line":1119},28,[281,1121,1122],{},"  def generate(self, idx, max_new_tokens):\n",[281,1124,1126],{"class":283,"line":1125},29,[281,1127,1128],{},"    # idx é um array de índices (B, T)\n",[281,1130,1132],{"class":283,"line":1131},30,[281,1133,1134],{},"    #no contexto atual\n",[281,1136,1138],{"class":283,"line":1137},31,[281,1139,1140],{},"    for _ in range(max_new_tokens):\n",[281,1142,1144],{"class":283,"line":1143},32,[281,1145,1146],{},"      #\"pega\" as predições\n",[281,1148,1150],{"class":283,"line":1149},33,[281,1151,1152],{},"      logits, loss = self(idx)\n",[281,1154,1156],{"class":283,"line":1155},34,[281,1157,1158],{},"      #foca apenas no último passo de tempo\n",[281,1160,1162],{"class":283,"line":1161},35,[281,1163,1164],{},"      logits = logits[:, -1, : ] #Vira B, C\n",[281,1166,1168],{"class":283,"line":1167},36,[281,1169,1170],{},"      # aplica softmax para obter probabilidades\n",[281,1172,1174],{"class":283,"line":1173},37,[281,1175,1176],{},"      probabilidades = F.softmax(logits, dim=-1) # (B, C)\n",[281,1178,1180],{"class":283,"line":1179},38,[281,1181,1182],{},"      # amostra da distribuição\n",[281,1184,1186],{"class":283,"line":1185},39,[281,1187,1188],{},"      idx_next = torch.multinomial(probabilidades, num_samples=1) # (B, T+1)\n",[281,1190,1192],{"class":283,"line":1191},40,[281,1193,1194],{},"    return idx\n",[281,1196,1198],{"class":283,"line":1197},41,[281,1199,306],{"emptyLinePlaceholder":48},[281,1201,1203],{"class":283,"line":1202},42,[281,1204,1205],{},"modelo = BigramLanguageModel(tamanho_vocabulario)\n",[281,1207,1209],{"class":283,"line":1208},43,[281,1210,1211],{},"saida, loss = modelo(xb, yb)\n",[281,1213,1215],{"class":283,"line":1214},44,[281,1216,1217],{},"print(saida.shape)\n",[281,1219,1221],{"class":283,"line":1220},45,[281,1222,1223],{},"print(loss)\n",[281,1225,1227],{"class":283,"line":1226},46,[281,1228,306],{"emptyLinePlaceholder":48},[281,1230,1232],{"class":283,"line":1231},47,[281,1233,1234],{},"print(\"-----\")\n",[281,1236,1238],{"class":283,"line":1237},48,[281,1239,306],{"emptyLinePlaceholder":48},[281,1241,1243],{"class":283,"line":1242},49,[281,1244,1245],{},"print(decode(modelo.generate(idx = torch.zeros((1,1), dtype=torch.long), max_new_tokens=100)[0].tolist()))\n",[11,1247,1248,1249,1252,1253,1256,1257,1260,1261,1264,1265,1268,1269,1272],{},"O modelo mais bobo possível: ",[247,1250,1251],{},"nn.Embedding(tam_vocabulario, tam_vocabulario)"," é uma tabela onde cada caractere aponta direto pra um vetor do tamanho do vocabulário inteiro (esses vetores viram os ",[247,1254,1255],{},"logits",", sem olhar nenhum contexto além do próprio caractere atual). ",[247,1258,1259],{},"forward"," calcula a ",[247,1262,1263],{},"loss"," via ",[247,1266,1267],{},"cross_entropy"," só quando existe ",[247,1270,1271],{},"targets",".",[11,1274,1275,1278,1279,1282,1283,742,1286,1295,1296,1299,1300,1302,1303,1305],{},[247,1276,1277],{},"generate"," é onde mora o bug: repara no ",[247,1280,1281],{},"for"," que faz a amostragem e calcula ",[247,1284,1285],{},"idx_next",[95,1287,1288,1289,1291,1292],{},"nunca gruda esse ",[247,1290,1285],{}," de volta em ",[247,1293,1294],{},"idx",". Falta a linha ",[247,1297,1298],{},"idx = torch.cat((idx, idx_next), dim=1)",". O notebook original tem essa linha, a minha versão perdeu ela na hora de digitar. Resultado: ",[247,1301,1277],{}," sempre devolve o ",[247,1304,1294],{}," original sem mudança nenhuma, então gerar \"100 novos tokens\" na prática gera zero.",[11,1307,1308,1309,757,1312,1315,1316,1318,1319,1322,1323,1326,1327,1329,1330,1333],{},"Saída real confirma o estrago: ",[247,1310,1311],{},"torch.Size([32, 112])",[247,1313,1314],{},"tensor(5.0673, ...)"," pra ",[247,1317,1263],{}," aparecem normais, mas depois do \"-----\" o ",[247,1320,1321],{},"print(decode(...))"," sai ",[95,1324,1325],{},"vazio",". Não é erro de execução, é o sintoma exato do bug: ",[247,1328,1294],{}," continua sendo o tensor ",[247,1331,1332],{},"[[0]]"," original, decodificar isso vira só o caractere de código 0 do vocabulário (uma quebra de linha), invisível no print.",[85,1335,1337],{"id":1336},"treinando-mal-o-bigram-por-só-100-passos","Treinando (mal) o bigram, por só 100 passos",[273,1339,1341],{"className":275,"code":1340,"language":277,"meta":42,"style":42},"#Criando um otimizador com pytorch\noptimizer = torch.optim.AdamW(modelo.parameters(), lr=1e-3)\n",[247,1342,1343,1348],{"__ignoreMap":42},[281,1344,1345],{"class":283,"line":221},[281,1346,1347],{},"#Criando um otimizador com pytorch\n",[281,1349,1350],{"class":283,"line":43},[281,1351,1352],{},"optimizer = torch.optim.AdamW(modelo.parameters(), lr=1e-3)\n",[273,1354,1356],{"className":275,"code":1355,"language":277,"meta":42,"style":42},"tamanho_batch = 32\nfor passo in range(100):\n  # amostra de um lote de dados\n  xb, yb = get_batch('treino')\n\n  #avaliando o loss\n  logits, loss = modelo(xb, yb)\n  optimizer.zero_grad(set_to_none=True)\n  loss.backward()\n  optimizer.step()\n\nprint(loss.item())\n",[247,1357,1358,1363,1368,1373,1378,1382,1387,1392,1397,1402,1407,1411],{"__ignoreMap":42},[281,1359,1360],{"class":283,"line":221},[281,1361,1362],{},"tamanho_batch = 32\n",[281,1364,1365],{"class":283,"line":43},[281,1366,1367],{},"for passo in range(100):\n",[281,1369,1370],{"class":283,"line":303},[281,1371,1372],{},"  # amostra de um lote de dados\n",[281,1374,1375],{"class":283,"line":309},[281,1376,1377],{},"  xb, yb = get_batch('treino')\n",[281,1379,1380],{"class":283,"line":49},[281,1381,306],{"emptyLinePlaceholder":48},[281,1383,1384],{"class":283,"line":320},[281,1385,1386],{},"  #avaliando o loss\n",[281,1388,1389],{"class":283,"line":427},[281,1390,1391],{},"  logits, loss = modelo(xb, yb)\n",[281,1393,1394],{"class":283,"line":433},[281,1395,1396],{},"  optimizer.zero_grad(set_to_none=True)\n",[281,1398,1399],{"class":283,"line":514},[281,1400,1401],{},"  loss.backward()\n",[281,1403,1404],{"class":283,"line":520},[281,1405,1406],{},"  optimizer.step()\n",[281,1408,1409],{"class":283,"line":526},[281,1410,306],{"emptyLinePlaceholder":48},[281,1412,1413],{"class":283,"line":722},[281,1414,1415],{},"print(loss.item())\n",[11,1417,1418,1419,1422,1423,1426,1427,1430,1431,1434],{},"Com 112 classes possíveis e chute aleatório, a perda esperada de um modelo sem treino nenhum é ",[247,1420,1421],{},"-ln(1\u002F112) ≈ 4,72",". O bigram sem treino começou em ",[95,1424,1425],{},"5,07"," (visto na célula anterior), na mesma ordem de grandeza. Depois de só 100 passos de treino grosseiro, a perda foi pra ",[95,1428,1429],{},"5,19"," (saída real: ",[247,1432,1433],{},"5.18871545791626","), na verdade subiu um pouco, porque um bigram não tem memória nenhuma pra usar direito ainda e 100 passos é muito pouco.",[273,1436,1438],{"className":275,"code":1437,"language":277,"meta":42,"style":42},"print(decode(modelo.generate(idx = torch.zeros((1,1), dtype=torch.long), max_new_tokens=500)[0].tolist()))\n",[247,1439,1440],{"__ignoreMap":42},[281,1441,1442],{"class":283,"line":221},[281,1443,1437],{},[11,1445,1446,1447,1450],{},"Mesmo bug de antes, mesma consequência: saída vazia. Esse modelo bigram nunca chegou a gerar texto de verdade nesse notebook, o modelo completo mais na frente (que tem o ",[247,1448,1449],{},"torch.cat"," certinho) é quem realmente aprende a escrever.",[85,1452,1454],{"id":1453},"o-truque-da-autoatenção-passo-a-passo","O truque da autoatenção, passo a passo",[11,1456,1457],{},"A ideia central do capítulo: cada caractere precisa \"conversar\" com os caracteres anteriores. O jeito mais lento é em loop, calculando a média de tudo que veio antes:",[273,1459,1461],{"className":275,"code":1460,"language":277,"meta":42,"style":42},"#O truque matemático na autoatenção\n# exemplo simplificado que ilustra como\n# a multiplicação de matrizes pode ser usada\n# para uma \"agregação ponderada\"\ntorch.manual_seed(42)\na = torch.tril(torch.ones(3,3))\na = a \u002F torch.sum(a, 1, keepdim=True)\nb = torch.randint(0,10,(3,2)).float()\nc = a @ b\nprint(\"a=\")\nprint(a)\nprint('--')\nprint('b=')\nprint(b)\nprint('--')\nprint('c=')\nprint(c)\n",[247,1462,1463,1468,1473,1478,1483,1488,1493,1498,1503,1508,1513,1518,1523,1528,1533,1537,1542],{"__ignoreMap":42},[281,1464,1465],{"class":283,"line":221},[281,1466,1467],{},"#O truque matemático na autoatenção\n",[281,1469,1470],{"class":283,"line":43},[281,1471,1472],{},"# exemplo simplificado que ilustra como\n",[281,1474,1475],{"class":283,"line":303},[281,1476,1477],{},"# a multiplicação de matrizes pode ser usada\n",[281,1479,1480],{"class":283,"line":309},[281,1481,1482],{},"# para uma \"agregação ponderada\"\n",[281,1484,1485],{"class":283,"line":49},[281,1486,1487],{},"torch.manual_seed(42)\n",[281,1489,1490],{"class":283,"line":320},[281,1491,1492],{},"a = torch.tril(torch.ones(3,3))\n",[281,1494,1495],{"class":283,"line":427},[281,1496,1497],{},"a = a \u002F torch.sum(a, 1, keepdim=True)\n",[281,1499,1500],{"class":283,"line":433},[281,1501,1502],{},"b = torch.randint(0,10,(3,2)).float()\n",[281,1504,1505],{"class":283,"line":514},[281,1506,1507],{},"c = a @ b\n",[281,1509,1510],{"class":283,"line":520},[281,1511,1512],{},"print(\"a=\")\n",[281,1514,1515],{"class":283,"line":526},[281,1516,1517],{},"print(a)\n",[281,1519,1520],{"class":283,"line":722},[281,1521,1522],{},"print('--')\n",[281,1524,1525],{"class":283,"line":728},[281,1526,1527],{},"print('b=')\n",[281,1529,1530],{"class":283,"line":847},[281,1531,1532],{},"print(b)\n",[281,1534,1535],{"class":283,"line":853},[281,1536,1522],{},[281,1538,1539],{"class":283,"line":859},[281,1540,1541],{},"print('c=')\n",[281,1543,1544],{"class":283,"line":865},[281,1545,1546],{},"print(c)\n",[11,1548,1549,1550,1552,1553,1556,1557,1560,1561,1564,1565,1568,1569,757,1571,1574],{},"Esse exemplo de aquecimento (3×3, números pequenos) mostra o truque puro: ",[247,1551,15],{}," é uma matriz triangular inferior normalizada por linha (cada linha soma 1), e multiplicar ",[247,1554,1555],{},"a @ b"," já devolve a média acumulada de ",[247,1558,1559],{},"b"," linha a linha, sem precisar de loop nenhum. Saída real confirma: a primeira linha de ",[247,1562,1563],{},"c"," é só ",[247,1566,1567],{},"b[0]"," (média de 1 elemento), a segunda é a média de ",[247,1570,1567],{},[247,1572,1573],{},"b[1]",", e assim por diante.",[11,1576,1577],{},"Agora com dado de verdade, em três versões que deveriam ser equivalentes:",[273,1579,1581],{"className":275,"code":1580,"language":277,"meta":42,"style":42},"# considere o seguinte exemplo simplificado:\ntorch.manual_seed(1337)\nB,T,C = 4,8,2 # batch, time, channels\nx = torch.randn(B,T,C)\nx.shape\n",[247,1582,1583,1588,1592,1597,1602],{"__ignoreMap":42},[281,1584,1585],{"class":283,"line":221},[281,1586,1587],{},"# considere o seguinte exemplo simplificado:\n",[281,1589,1590],{"class":283,"line":43},[281,1591,786],{},[281,1593,1594],{"class":283,"line":303},[281,1595,1596],{},"B,T,C = 4,8,2 # batch, time, channels\n",[281,1598,1599],{"class":283,"line":309},[281,1600,1601],{},"x = torch.randn(B,T,C)\n",[281,1603,1604],{"class":283,"line":49},[281,1605,1606],{},"x.shape\n",[273,1608,1610],{"className":275,"code":1609,"language":277,"meta":42,"style":42},"# Nós queremos x[b,t] = mean_{i\u003C=t} x[b,i]\nxbow = torch.zeros((B,T,C))\nfor b in range(B):\n  for t in range(T):\n    xprev = x[b,:t+1] # (t,C)\n    xbow[b,t] = torch.mean(xprev, 0)\n",[247,1611,1612,1617,1622,1627,1632,1637],{"__ignoreMap":42},[281,1613,1614],{"class":283,"line":221},[281,1615,1616],{},"# Nós queremos x[b,t] = mean_{i\u003C=t} x[b,i]\n",[281,1618,1619],{"class":283,"line":43},[281,1620,1621],{},"xbow = torch.zeros((B,T,C))\n",[281,1623,1624],{"class":283,"line":303},[281,1625,1626],{},"for b in range(B):\n",[281,1628,1629],{"class":283,"line":309},[281,1630,1631],{},"  for t in range(T):\n",[281,1633,1634],{"class":283,"line":49},[281,1635,1636],{},"    xprev = x[b,:t+1] # (t,C)\n",[281,1638,1639],{"class":283,"line":320},[281,1640,1641],{},"    xbow[b,t] = torch.mean(xprev, 0)\n",[11,1643,1644,1647,1648,1650],{},[95,1645,1646],{},"Versão 1",", o jeito lento: dois ",[247,1649,1281],{}," aninhados, calculando a média de tudo que veio antes de cada posição, um batch e um tempo de cada vez.",[273,1652,1654],{"className":275,"code":1653,"language":277,"meta":42,"style":42},"# versão 2: usando multiplicação de matrizes para\n# uma agregação ponderada\nwei = torch.tril(torch.ones(T, T))\nwei = wei \u002F wei.sum(1, keepdim=True)\nxbow2 = wei @ x # (B, T, T) @ (B, T, C) ----> (B, T, C)\ntorch.allclose(xbow, xbow2)\n",[247,1655,1656,1661,1666,1671,1676,1681],{"__ignoreMap":42},[281,1657,1658],{"class":283,"line":221},[281,1659,1660],{},"# versão 2: usando multiplicação de matrizes para\n",[281,1662,1663],{"class":283,"line":43},[281,1664,1665],{},"# uma agregação ponderada\n",[281,1667,1668],{"class":283,"line":303},[281,1669,1670],{},"wei = torch.tril(torch.ones(T, T))\n",[281,1672,1673],{"class":283,"line":309},[281,1674,1675],{},"wei = wei \u002F wei.sum(1, keepdim=True)\n",[281,1677,1678],{"class":283,"line":49},[281,1679,1680],{},"xbow2 = wei @ x # (B, T, T) @ (B, T, C) ----> (B, T, C)\n",[281,1682,1683],{"class":283,"line":320},[281,1684,1685],{},"torch.allclose(xbow, xbow2)\n",[11,1687,1688,1691,1692,1695,1696,1699,1700,1272],{},[95,1689,1690],{},"Versão 2",", o mesmo resultado matemático via multiplicação de matriz, igual ao aquecimento de cima. ",[247,1693,1694],{},"torch.allclose"," deveria devolver ",[247,1697,1698],{},"True"," (as duas versões calculam a mesma coisa). Saída real: ",[95,1701,1702],{},[247,1703,1704],{},"False",[273,1706,1708],{"className":275,"code":1707,"language":277,"meta":42,"style":42},"# versão 3: usando softmax\ntril = torch.tril(torch.ones(T, T))\nwei = torch.zeros((T,T))\nwei = wei.masked_fill(tril == 0, float('-inf'))\nwei = F.softmax(wei, dim=-1)\nxbow3 = wei @ x\ntorch.allclose(xbow, xbow3)\n",[247,1709,1710,1715,1720,1725,1730,1735,1740],{"__ignoreMap":42},[281,1711,1712],{"class":283,"line":221},[281,1713,1714],{},"# versão 3: usando softmax\n",[281,1716,1717],{"class":283,"line":43},[281,1718,1719],{},"tril = torch.tril(torch.ones(T, T))\n",[281,1721,1722],{"class":283,"line":303},[281,1723,1724],{},"wei = torch.zeros((T,T))\n",[281,1726,1727],{"class":283,"line":309},[281,1728,1729],{},"wei = wei.masked_fill(tril == 0, float('-inf'))\n",[281,1731,1732],{"class":283,"line":49},[281,1733,1734],{},"wei = F.softmax(wei, dim=-1)\n",[281,1736,1737],{"class":283,"line":320},[281,1738,1739],{},"xbow3 = wei @ x\n",[281,1741,1742],{"class":283,"line":427},[281,1743,1744],{},"torch.allclose(xbow, xbow3)\n",[11,1746,1747,1750,1751,1754,1755,1758,1759,1272],{},[95,1748,1749],{},"Versão 3",", o mesmo de novo, só que construindo os pesos via ",[247,1752,1753],{},"softmax"," de uma matriz mascarada com ",[247,1756,1757],{},"-inf"," no triângulo superior (isso vira importante daqui a pouco, é assim que o modelo vai aprender pesos que não são só médias fixas). De novo, saída real: ",[95,1760,1761],{},[247,1762,1704],{},[11,1764,1765,1766,1768,1769,1771,1772,1775],{},"Isso me deixou intrigado, então conferi o notebook original, e ele mostra exatamente o mesmo resultado, ",[247,1767,1704],{}," nas duas comparações, com o mesmo código. Não é bug de ninguém: ",[247,1770,1694],{}," usa uma tolerância padrão bem apertada, e a soma acumulada numa multiplicação de matriz não segue a mesma ordem de operações em ponto flutuante que a média calculada em loop, então os dois resultados ficam próximos, mas não idênticos bit a bit. É um lembrete que vale mais que muita teoria: ",[95,1773,1774],{},"matematicamente equivalente não é sinônimo de numericamente idêntico",", ponto flutuante tem opinião própria sobre a ordem das contas.",[85,1777,1779],{"id":1778},"self-attention-de-verdade","Self-attention de verdade",[273,1781,1783],{"className":275,"code":1782,"language":277,"meta":42,"style":42},"#versão 4: self-attention\ntorch.manual_seed(1337)\nB,T,C = 4,8,32 #batch, time, channels\nx = torch.randn(B,T,C)\n\n#Vendo uma única \"cabeça\" performando a self-attention\nhead_size = 16\nkey = nn.Linear(C, head_size, bias=False)\nquery = nn.Linear(C, head_size, bias=False)\nvalue = nn.Linear(C, head_size, bias=False)\n\nk = key(x)   # (B, T, 16)\nq = query(x) # (B, T, 16)\nwei =  q @ k.transpose(-2, -1) # (B, T, 16) @ (B, 16, T) ---> (B, T, T)\n\ntril = torch.tril(torch.ones(T, T))\n#wei = torch.zeros((T,T))\nwei = wei.masked_fill(tril == 0, float('-inf'))\nwei = F.softmax(wei, dim=-1)\n\nv = value(x)\nout = wei @ v\n#out = wei @ x\n\nout.shape\n",[247,1784,1785,1790,1794,1799,1803,1807,1812,1817,1822,1827,1832,1836,1841,1846,1851,1855,1859,1864,1868,1872,1876,1881,1886,1891,1895],{"__ignoreMap":42},[281,1786,1787],{"class":283,"line":221},[281,1788,1789],{},"#versão 4: self-attention\n",[281,1791,1792],{"class":283,"line":43},[281,1793,786],{},[281,1795,1796],{"class":283,"line":303},[281,1797,1798],{},"B,T,C = 4,8,32 #batch, time, channels\n",[281,1800,1801],{"class":283,"line":309},[281,1802,1601],{},[281,1804,1805],{"class":283,"line":49},[281,1806,306],{"emptyLinePlaceholder":48},[281,1808,1809],{"class":283,"line":320},[281,1810,1811],{},"#Vendo uma única \"cabeça\" performando a self-attention\n",[281,1813,1814],{"class":283,"line":427},[281,1815,1816],{},"head_size = 16\n",[281,1818,1819],{"class":283,"line":433},[281,1820,1821],{},"key = nn.Linear(C, head_size, bias=False)\n",[281,1823,1824],{"class":283,"line":514},[281,1825,1826],{},"query = nn.Linear(C, head_size, bias=False)\n",[281,1828,1829],{"class":283,"line":520},[281,1830,1831],{},"value = nn.Linear(C, head_size, bias=False)\n",[281,1833,1834],{"class":283,"line":526},[281,1835,306],{"emptyLinePlaceholder":48},[281,1837,1838],{"class":283,"line":722},[281,1839,1840],{},"k = key(x)   # (B, T, 16)\n",[281,1842,1843],{"class":283,"line":728},[281,1844,1845],{},"q = query(x) # (B, T, 16)\n",[281,1847,1848],{"class":283,"line":847},[281,1849,1850],{},"wei =  q @ k.transpose(-2, -1) # (B, T, 16) @ (B, 16, T) ---> (B, T, T)\n",[281,1852,1853],{"class":283,"line":853},[281,1854,306],{"emptyLinePlaceholder":48},[281,1856,1857],{"class":283,"line":859},[281,1858,1719],{},[281,1860,1861],{"class":283,"line":865},[281,1862,1863],{},"#wei = torch.zeros((T,T))\n",[281,1865,1866],{"class":283,"line":871},[281,1867,1729],{},[281,1869,1870],{"class":283,"line":877},[281,1871,1734],{},[281,1873,1874],{"class":283,"line":883},[281,1875,306],{"emptyLinePlaceholder":48},[281,1877,1878],{"class":283,"line":889},[281,1879,1880],{},"v = value(x)\n",[281,1882,1883],{"class":283,"line":894},[281,1884,1885],{},"out = wei @ v\n",[281,1887,1888],{"class":283,"line":900},[281,1889,1890],{},"#out = wei @ x\n",[281,1892,1893],{"class":283,"line":906},[281,1894,306],{"emptyLinePlaceholder":48},[281,1896,1897],{"class":283,"line":912},[281,1898,1899],{},"out.shape\n",[11,1901,1902,1903,1906,1907,1910,1911,757,1914,1917,1918,1921,1922,1925,1926,1929,1930,1933,1934,1936,1937,1940,1941,1944,1945,1948],{},"Aqui os pesos da média deixam de ser fixos (1\u002F2, 1\u002F3...) e viram ",[95,1904,1905],{},"aprendidos e dependentes do dado",": ",[247,1908,1909],{},"key",", ",[247,1912,1913],{},"query",[247,1915,1916],{},"value"," são três camadas lineares treináveis. ",[247,1919,1920],{},"Query"," pergunta \"o que eu tô procurando\", ",[247,1923,1924],{},"Key"," responde \"o que eu tenho pra oferecer\", o produto ",[247,1927,1928],{},"q @ k.transpose()"," decide o quanto cada posição anterior importa, ",[247,1931,1932],{},"masked_fill"," + ",[247,1935,1753],{}," transforma isso em pesos que somam 1 e nunca espiam o futuro, e ",[247,1938,1939],{},"wei @ v"," faz a média ponderada usando ",[247,1942,1943],{},"Value"," (o conteúdo real que cada posição carrega, não a chave que ela usa pra ser encontrada). Saída: ",[247,1946,1947],{},"torch.Size([4, 8, 16])",", um vetor de 16 dimensões por posição, já misturando informação do contexto.",[273,1950,1952],{"className":275,"code":1951,"language":277,"meta":42,"style":42},"wei[0]\n",[247,1953,1954],{"__ignoreMap":42},[281,1955,1956],{"class":283,"line":221},[281,1957,1951],{},[11,1959,1960],{},"Imprime a matriz de pesos de atenção do primeiro item do lote. Saída real (recortada):",[273,1962,1965],{"className":1963,"code":1964,"language":332},[330],"tensor([[1.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],\n        [0.1574, 0.8426, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],\n        [0.2088, 0.1646, 0.6266, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000],\n        ...\n",[247,1966,1964],{"__ignoreMap":42},[11,1968,1969,1970,1973],{},"Repara que a primeira linha é ",[247,1971,1972],{},"[1, 0, 0, ...]"," (a primeira posição só pode prestar atenção nela mesma, não tem passado) e cada linha seguinte distribui peso de um jeito bem diferente de \"média simples\", tipo a segunda linha dando 84% de peso pro segundo token e só 16% pro primeiro. Isso é o modelo decidindo, não uma fórmula fixa.",[85,1975,1977],{"id":1976},"por-que-dividir-pela-raiz-de-head_size","Por que dividir pela raiz de head_size",[273,1979,1981],{"className":275,"code":1980,"language":277,"meta":42,"style":42},"k = torch.randn(B,T, head_size)\nq = torch.randn(B,T, head_size)\nwei = q @ k.transpose(-2, -1) * head_size**-0.5\n",[247,1982,1983,1988,1993],{"__ignoreMap":42},[281,1984,1985],{"class":283,"line":221},[281,1986,1987],{},"k = torch.randn(B,T, head_size)\n",[281,1989,1990],{"class":283,"line":43},[281,1991,1992],{},"q = torch.randn(B,T, head_size)\n",[281,1994,1995],{"class":283,"line":303},[281,1996,1997],{},"wei = q @ k.transpose(-2, -1) * head_size**-0.5\n",[273,1999,2001],{"className":275,"code":2000,"language":277,"meta":42,"style":42},"k.var()\n",[247,2002,2003],{"__ignoreMap":42},[281,2004,2005],{"class":283,"line":221},[281,2006,2000],{},[11,2008,644,2009],{},[247,2010,2011],{},"tensor(1.0449)",[273,2013,2015],{"className":275,"code":2014,"language":277,"meta":42,"style":42},"q.var()\n",[247,2016,2017],{"__ignoreMap":42},[281,2018,2019],{"class":283,"line":221},[281,2020,2014],{},[11,2022,644,2023],{},[247,2024,2025],{},"tensor(1.0700)",[273,2027,2029],{"className":275,"code":2028,"language":277,"meta":42,"style":42},"wei.var()\n",[247,2030,2031],{"__ignoreMap":42},[281,2032,2033],{"class":283,"line":221},[281,2034,2028],{},[11,2036,644,2037],{},[247,2038,2039],{},"tensor(1.0918)",[11,2041,2042,757,2045,2048,2049,2052,2053,2056,2057,2059,2060,2063,2064,2066,2067,2069,2070,2073,2074,2076],{},[247,2043,2044],{},"k",[247,2046,2047],{},"q"," começam com variância perto de 1 (inicialização padrão). Sem o fator ",[247,2050,2051],{},"head_size**-0.5"," multiplicando ",[247,2054,2055],{},"wei",", a variância do produto ",[247,2058,1928],{}," explodiria proporcionalmente ao tamanho de ",[247,2061,2062],{},"head_size",", porque cada elemento de ",[247,2065,2055],{}," é a soma de ",[247,2068,2062],{}," multiplicações. Escalar por ",[247,2071,2072],{},"1\u002F√head_size"," mantém a variância de ",[247,2075,2055],{}," perto de 1 também, e isso importa porque:",[273,2078,2080],{"className":275,"code":2079,"language":277,"meta":42,"style":42},"torch.softmax(torch.tensor([0.1, -0.2, 0.3, -0.2, 0.5]), dim=-1)\n",[247,2081,2082],{"__ignoreMap":42},[281,2083,2084],{"class":283,"line":221},[281,2085,2079],{},[11,2087,644,2088,2091],{},[247,2089,2090],{},"tensor([0.1925, 0.1426, 0.2351, 0.1426, 0.2872])",", uma distribuição relativamente suave.",[273,2093,2095],{"className":275,"code":2094,"language":277,"meta":42,"style":42},"torch.softmax(torch.tensor([0.1, -0.2, 0.3, -0.2, 0.5])*8, dim=-1) # gets too peaky, converges to one-hot\n",[247,2096,2097],{"__ignoreMap":42},[281,2098,2099],{"class":283,"line":221},[281,2100,2094],{},[11,2102,644,2103,2106],{},[247,2104,2105],{},"tensor([0.0326, 0.0030, 0.1615, 0.0030, 0.8000])",", quase todo o peso (80%) grudado num valor só.",[11,2108,2109,2110,2112,2113,2117,2118,2120],{},"Mesmos números de entrada, só multiplicados por 8, e o ",[247,2111,1753],{}," já vira quase um \"escolhe só um vencedor\" (",[74,2114,2116],{"definition":2115},"vetor onde uma posição vale 1 e todas as outras valem 0, o oposto de uma distribuição suave de probabilidade","one-hot","). Se ",[247,2119,2055],{}," não fosse escalado, os pesos de atenção ficariam sempre nesse regime \"picudo\", o modelo prestaria atenção num único token e ignoraria o resto, em vez de aprender uma mistura de verdade.",[85,2122,2124],{"id":2123},"layer-normalization","Layer normalization",[273,2126,2128],{"className":275,"code":2127,"language":277,"meta":42,"style":42},"class LayerNorm1d:\n\n  def __init__(self, dim, eps=1e-5, momentum=0.1):\n    self.eps = eps\n    self.gamma = torch.ones(dim)\n    self.beta = torch.zeros(dim)\n\n  def __call__(self, x):\n    # calcular a passagem direta (forward pass)\n    xmean = x.mean(1, keepdim=True) # média do lote\n    xvar = x.var(1, keepdim=True) # Variância do lote\n    # normalizar para variância unitária\n    xhat = (x - xmean) \u002F torch.sqrt(xvar + self.eps)\n    self.out = self.gamma * xhat + self.beta\n    return self.out\n\n  def parameters(self):\n    return [self.gamma, self.beta]\n\ntorch.manual_seed(1337)\nmodule = LayerNorm1d(100)\n# tamanho de lote de 32 vetores de 100 dimensões\nx = torch.randn(32, 100)\nx = module(x)\nx.shape\n",[247,2129,2130,2135,2139,2144,2149,2154,2159,2163,2168,2173,2178,2183,2188,2193,2198,2203,2207,2212,2217,2221,2225,2230,2235,2240,2245],{"__ignoreMap":42},[281,2131,2132],{"class":283,"line":221},[281,2133,2134],{},"class LayerNorm1d:\n",[281,2136,2137],{"class":283,"line":43},[281,2138,306],{"emptyLinePlaceholder":48},[281,2140,2141],{"class":283,"line":303},[281,2142,2143],{},"  def __init__(self, dim, eps=1e-5, momentum=0.1):\n",[281,2145,2146],{"class":283,"line":309},[281,2147,2148],{},"    self.eps = eps\n",[281,2150,2151],{"class":283,"line":49},[281,2152,2153],{},"    self.gamma = torch.ones(dim)\n",[281,2155,2156],{"class":283,"line":320},[281,2157,2158],{},"    self.beta = torch.zeros(dim)\n",[281,2160,2161],{"class":283,"line":427},[281,2162,306],{"emptyLinePlaceholder":48},[281,2164,2165],{"class":283,"line":433},[281,2166,2167],{},"  def __call__(self, x):\n",[281,2169,2170],{"class":283,"line":514},[281,2171,2172],{},"    # calcular a passagem direta (forward pass)\n",[281,2174,2175],{"class":283,"line":520},[281,2176,2177],{},"    xmean = x.mean(1, keepdim=True) # média do lote\n",[281,2179,2180],{"class":283,"line":526},[281,2181,2182],{},"    xvar = x.var(1, keepdim=True) # Variância do lote\n",[281,2184,2185],{"class":283,"line":722},[281,2186,2187],{},"    # normalizar para variância unitária\n",[281,2189,2190],{"class":283,"line":728},[281,2191,2192],{},"    xhat = (x - xmean) \u002F torch.sqrt(xvar + self.eps)\n",[281,2194,2195],{"class":283,"line":847},[281,2196,2197],{},"    self.out = self.gamma * xhat + self.beta\n",[281,2199,2200],{"class":283,"line":853},[281,2201,2202],{},"    return self.out\n",[281,2204,2205],{"class":283,"line":859},[281,2206,306],{"emptyLinePlaceholder":48},[281,2208,2209],{"class":283,"line":865},[281,2210,2211],{},"  def parameters(self):\n",[281,2213,2214],{"class":283,"line":871},[281,2215,2216],{},"    return [self.gamma, self.beta]\n",[281,2218,2219],{"class":283,"line":877},[281,2220,306],{"emptyLinePlaceholder":48},[281,2222,2223],{"class":283,"line":883},[281,2224,786],{},[281,2226,2227],{"class":283,"line":889},[281,2228,2229],{},"module = LayerNorm1d(100)\n",[281,2231,2232],{"class":283,"line":894},[281,2233,2234],{},"# tamanho de lote de 32 vetores de 100 dimensões\n",[281,2236,2237],{"class":283,"line":900},[281,2238,2239],{},"x = torch.randn(32, 100)\n",[281,2241,2242],{"class":283,"line":906},[281,2243,2244],{},"x = module(x)\n",[281,2246,2247],{"class":283,"line":912},[281,2248,1606],{},[11,2250,2251,2252,2256,2257,2260,2261,757,2264,2267,2268,2271,2272,2275,2276,2279],{},"Uma implementação de ",[74,2253,2255],{"definition":2254},"técnica que recentraliza e reescala as ativações de cada exemplo individualmente, pra manter média perto de 0 e variância perto de 1 e estabilizar o treino de redes profundas","layer normalization"," do zero, sem usar ",[247,2258,2259],{},"nn.LayerNorm"," pronto, só pra entender a conta por dentro. ",[247,2262,2263],{},"xmean",[247,2265,2266],{},"xvar"," calculam média e variância, ",[247,2269,2270],{},"xhat"," normaliza, ",[247,2273,2274],{},"gamma","\u002F",[247,2277,2278],{},"beta"," deixam o modelo reaprender uma escala\u002Fdeslocamento diferente se precisar (começam em 1 e 0, ou seja, sem efeito, até o treino ajustar).",[273,2281,2283],{"className":275,"code":2282,"language":277,"meta":42,"style":42},"# média e desvio padrão de uma característica\n#em todas as entradas do lote\nx[:,0].mean(), x[:,0].std()\n",[247,2284,2285,2290,2295],{"__ignoreMap":42},[281,2286,2287],{"class":283,"line":221},[281,2288,2289],{},"# média e desvio padrão de uma característica\n",[281,2291,2292],{"class":283,"line":43},[281,2293,2294],{},"#em todas as entradas do lote\n",[281,2296,2297],{"class":283,"line":303},[281,2298,2299],{},"x[:,0].mean(), x[:,0].std()\n",[11,2301,644,2302,2305,2306,2309],{},[247,2303,2304],{},"(tensor(0.1469), tensor(0.8803))",", a primeira ",[95,2307,2308],{},"feature",", olhando as 32 entradas do lote, não fica exatamente em média 0\u002Fdesvio 1.",[273,2311,2313],{"className":275,"code":2312,"language":277,"meta":42,"style":42},"# média e desvio padrão de uma única entrada do\n# lote, considerando suas características\nx[0,:].mean(), x[0,:].std()\n",[247,2314,2315,2320,2325],{"__ignoreMap":42},[281,2316,2317],{"class":283,"line":221},[281,2318,2319],{},"# média e desvio padrão de uma única entrada do\n",[281,2321,2322],{"class":283,"line":43},[281,2323,2324],{},"# lote, considerando suas características\n",[281,2326,2327],{"class":283,"line":303},[281,2328,2329],{},"x[0,:].mean(), x[0,:].std()\n",[11,2331,644,2332,2335,2336,2339],{},[247,2333,2334],{},"(tensor(-9.5367e-09), tensor(1.0000))",", praticamente 0 e exatamente 1. Essa é a diferença chave entre layer norm e batch norm: aqui a normalização acontece ",[95,2337,2338],{},"por exemplo individual"," (olhando as 100 dimensões daquela linha), não por feature através do lote inteiro, por isso a segunda conta bate perfeito e a primeira não.",[85,2341,2343,2344,2347],{"id":2342},"bug-3-a-pegadinha-do-fold-e-o-modelo-completo","Bug #3: a pegadinha do ",[247,2345,2346],{},"Fold",", e o modelo completo",[273,2349,2351],{"className":275,"code":2350,"language":277,"meta":42,"style":42},"import torch\nimport torch.nn as nn\nfrom torch.nn import functional as Fold\n\n#hiperparâmetros\nbatch_size = 16 # Número de sequências independentes que serão processadas em paralelo\nblock_size = 32 # qual o contexto máximo de predições\nmax_iters = 5000 #máximo de iterações\neval_interval = 100\nlearning_rate = 1e-3\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\neval_iters = 200\nn_embd = 64\nn_head = 4\nn_layer = 4\ndropout = 0.0\n\ntorch.manual_seed(1337)\n\n#!wget https:\u002F\u002Fwww.gutenberg.org\u002Fcache\u002Fepub\u002F55752\u002Fpg55752.txt\nwith open('pg55752.txt', 'r') as f:\n  texto = f.read()\n\n#Apenas caracteres únicos(limpeza)\nchars = sorted(list(set(texto)))\ntam_vocabulario = len(chars)\n# criando um mapeamento de caracteres para inteiros\nstring_to_int = { ch:i for i,ch in enumerate(chars) }\nint_to_string = { i:ch for i,ch in enumerate(chars) }\n\n# encoder: toma uma string, devolve uma lista de inteiros\nencode = lambda s: [string_to_int[c] for c in s]\n\n# decoder: toma uma lista de inteiros, devolve uma string\ndecode = lambda l: ''.join([int_to_string[i] for i in l])\n\n#splitando o dataset\ndata = torch.tensor(encode(texto), dtype=torch.long)\nn = int(0.9*len(data))\ndados_treino = data[:n]\ndados_avaliacao = data[n:]\nprint(f\"Dispositivo: {device}\")\nprint(f\"Tamanho do texto: {len(texto)} caracteres\")\nprint(f\"Tamanho do vocabulario: {tam_vocabulario}\")\nprint(f\"Treino: {len(dados_treino)} | Avaliacao: {len(dados_avaliacao)}\")\n\n#carregamento dos dados\ndef get_batch(split):\n  #gerando um lote pequeno de dados de input x e targets y\n  dados = dados_treino if split == 'treino' else dados_avaliacao\n  ix = torch.randint(len(dados) - block_size, (batch_size,))\n  x = torch.stack([dados[i:i+block_size] for i in ix])\n  y = torch.stack([dados[i+1:i+block_size+1] for i in ix])\n  x, y = x.to(device), y.to(device)\n  return x, y\n\n@torch.no_grad()\ndef perda_estimada():\n  out = {}\n  modelo.eval()\n  for split in ['treino', 'avaliacao']:\n    losses = torch.zeros(eval_iters)\n    for k in range(eval_iters):\n      X, Y = get_batch(split)\n      logits, loss = modelo(X, Y)\n      losses[k] = loss.item()\n    out[split] = losses.mean()\n\n  modelo.train()\n  return out\n\nclass Head(nn.Module):\n  \"\"\" uma cabeça de self-attention \"\"\"\n\n  def __init__(self, head_size):\n    super().__init__()\n    self.key = nn.Linear(n_embd, head_size, bias=False)\n    self.query = nn.Linear(n_embd, head_size, bias=False)\n    self.value = nn.Linear(n_embd, head_size, bias=False)\n    self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))\n\n    self.dropout = nn.Dropout(dropout)\n\n  def forward(self, x):\n    B,T,C = x.shape\n    k = self.key(x)   # (B,T,C)\n    q = self.query(x) # (B,T,C)\n    # calcular pontuações de atenção (\"afinidades\")\n    wei = q @ k.transpose(-2,-1) * C**-0.5 # (B, T, C) @ (B, C, T) -> (B, T, T)\n    wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # (B, T, T)\n    wei = F.softmax(wei, dim=-1) # (B, T, T)\n    wei = self.dropout(wei)\n    # performar a média ponderada das valores\n    v = self.value(x) # (B,T,C)\n    out = wei @ v # (B, T, T) @ (B, T, C) -> (B, T, C)\n    return out\n\nclass MultiHeadAttention(nn.Module):\n  \"\"\" Múltiplas cabeças de self-attention em paralelo \"\"\"\n\n  def __init__(self, numero_cabecas, tamanho_cabeca):\n    super().__init__()\n    self.heads = nn.ModuleList([Head(tamanho_cabeca) for _ in range(numero_cabecas)])\n    self.proj = nn.Linear(n_embd, n_embd)\n    self.dropout = nn.Dropout(dropout)\n\n  def forward(self, x):\n    out = torch.cat([h(x) for h in self.heads], dim=-1)\n    out = self.dropout(self.proj(out))\n    return out\n\nclass FeedFoward(nn.Module):\n  \"\"\" Uma camada linear simples seguida de uma função de ativação ReLU \"\"\"\n\n  def __init__(self, n_embd):\n    super().__init__()\n    self.net = nn.Sequential(\n        nn.Linear(n_embd, 4 * n_embd),\n        nn.ReLU(),\n        nn.Linear( 4 * n_embd, n_embd),\n        nn.Dropout(dropout)\n    )\n\n  def forward(self, x):\n    return self.net(x)\n\nclass Block(nn.Module):\n  \"\"\"Bloco Transformer: comunicação seguida de computação\"\"\"\n\n  def __init__(self, n_embd, n_head):\n    # n_embd: embedding dimension, n_head:\n    #quantidade de cabeças desejadas\n    super().__init__()\n    head_size = n_embd \u002F\u002F n_head\n    self.sa = MultiHeadAttention(n_head, head_size)\n    self.ffwd = FeedFoward(n_embd)\n    self.ln1 = nn.LayerNorm(n_embd)\n    self.ln2 = nn.LayerNorm(n_embd)\n\n  def forward(self, x):\n    x = x + self.sa(self.ln1(x))\n    x = x + self.ffwd(self.ln2(x))\n    return x\n",[247,2352,2353,2357,2361,2366,2370,2375,2380,2385,2390,2395,2400,2405,2410,2415,2420,2425,2430,2434,2438,2442,2447,2452,2457,2461,2466,2471,2476,2481,2486,2491,2495,2500,2505,2509,2514,2519,2523,2528,2532,2536,2541,2546,2551,2556,2561,2566,2570,2575,2579,2584,2590,2595,2601,2606,2612,2617,2622,2628,2634,2640,2646,2652,2658,2664,2670,2676,2682,2688,2693,2699,2705,2710,2716,2722,2727,2733,2738,2744,2750,2756,2762,2767,2773,2778,2784,2790,2796,2802,2808,2814,2820,2826,2832,2838,2844,2850,2856,2861,2867,2873,2878,2884,2889,2895,2901,2906,2911,2916,2922,2928,2933,2938,2944,2950,2955,2961,2966,2972,2978,2984,2990,2996,3002,3007,3012,3018,3023,3029,3035,3040,3046,3052,3058,3063,3069,3075,3081,3087,3093,3098,3103,3109,3115],{"__ignoreMap":42},[281,2354,2355],{"class":283,"line":221},[281,2356,483],{},[281,2358,2359],{"class":283,"line":43},[281,2360,996],{},[281,2362,2363],{"class":283,"line":303},[281,2364,2365],{},"from torch.nn import functional as Fold\n",[281,2367,2368],{"class":283,"line":309},[281,2369,306],{"emptyLinePlaceholder":48},[281,2371,2372],{"class":283,"line":49},[281,2373,2374],{},"#hiperparâmetros\n",[281,2376,2377],{"class":283,"line":320},[281,2378,2379],{},"batch_size = 16 # Número de sequências independentes que serão processadas em paralelo\n",[281,2381,2382],{"class":283,"line":427},[281,2383,2384],{},"block_size = 32 # qual o contexto máximo de predições\n",[281,2386,2387],{"class":283,"line":433},[281,2388,2389],{},"max_iters = 5000 #máximo de iterações\n",[281,2391,2392],{"class":283,"line":514},[281,2393,2394],{},"eval_interval = 100\n",[281,2396,2397],{"class":283,"line":520},[281,2398,2399],{},"learning_rate = 1e-3\n",[281,2401,2402],{"class":283,"line":526},[281,2403,2404],{},"device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",[281,2406,2407],{"class":283,"line":722},[281,2408,2409],{},"eval_iters = 200\n",[281,2411,2412],{"class":283,"line":728},[281,2413,2414],{},"n_embd = 64\n",[281,2416,2417],{"class":283,"line":847},[281,2418,2419],{},"n_head = 4\n",[281,2421,2422],{"class":283,"line":853},[281,2423,2424],{},"n_layer = 4\n",[281,2426,2427],{"class":283,"line":859},[281,2428,2429],{},"dropout = 0.0\n",[281,2431,2432],{"class":283,"line":865},[281,2433,306],{"emptyLinePlaceholder":48},[281,2435,2436],{"class":283,"line":871},[281,2437,786],{},[281,2439,2440],{"class":283,"line":877},[281,2441,306],{"emptyLinePlaceholder":48},[281,2443,2444],{"class":283,"line":883},[281,2445,2446],{},"#!wget https:\u002F\u002Fwww.gutenberg.org\u002Fcache\u002Fepub\u002F55752\u002Fpg55752.txt\n",[281,2448,2449],{"class":283,"line":889},[281,2450,2451],{},"with open('pg55752.txt', 'r') as f:\n",[281,2453,2454],{"class":283,"line":894},[281,2455,2456],{},"  texto = f.read()\n",[281,2458,2459],{"class":283,"line":900},[281,2460,306],{"emptyLinePlaceholder":48},[281,2462,2463],{"class":283,"line":906},[281,2464,2465],{},"#Apenas caracteres únicos(limpeza)\n",[281,2467,2468],{"class":283,"line":912},[281,2469,2470],{},"chars = sorted(list(set(texto)))\n",[281,2472,2473],{"class":283,"line":918},[281,2474,2475],{},"tam_vocabulario = len(chars)\n",[281,2477,2478],{"class":283,"line":1114},[281,2479,2480],{},"# criando um mapeamento de caracteres para inteiros\n",[281,2482,2483],{"class":283,"line":1119},[281,2484,2485],{},"string_to_int = { ch:i for i,ch in enumerate(chars) }\n",[281,2487,2488],{"class":283,"line":1125},[281,2489,2490],{},"int_to_string = { i:ch for i,ch in enumerate(chars) }\n",[281,2492,2493],{"class":283,"line":1131},[281,2494,306],{"emptyLinePlaceholder":48},[281,2496,2497],{"class":283,"line":1137},[281,2498,2499],{},"# encoder: toma uma string, devolve uma lista de inteiros\n",[281,2501,2502],{"class":283,"line":1143},[281,2503,2504],{},"encode = lambda s: [string_to_int[c] for c in s]\n",[281,2506,2507],{"class":283,"line":1149},[281,2508,306],{"emptyLinePlaceholder":48},[281,2510,2511],{"class":283,"line":1155},[281,2512,2513],{},"# decoder: toma uma lista de inteiros, devolve uma string\n",[281,2515,2516],{"class":283,"line":1161},[281,2517,2518],{},"decode = lambda l: ''.join([int_to_string[i] for i in l])\n",[281,2520,2521],{"class":283,"line":1167},[281,2522,306],{"emptyLinePlaceholder":48},[281,2524,2525],{"class":283,"line":1173},[281,2526,2527],{},"#splitando o dataset\n",[281,2529,2530],{"class":283,"line":1179},[281,2531,488],{},[281,2533,2534],{"class":283,"line":1185},[281,2535,569],{},[281,2537,2538],{"class":283,"line":1191},[281,2539,2540],{},"dados_treino = data[:n]\n",[281,2542,2543],{"class":283,"line":1197},[281,2544,2545],{},"dados_avaliacao = data[n:]\n",[281,2547,2548],{"class":283,"line":1202},[281,2549,2550],{},"print(f\"Dispositivo: {device}\")\n",[281,2552,2553],{"class":283,"line":1208},[281,2554,2555],{},"print(f\"Tamanho do texto: {len(texto)} caracteres\")\n",[281,2557,2558],{"class":283,"line":1214},[281,2559,2560],{},"print(f\"Tamanho do vocabulario: {tam_vocabulario}\")\n",[281,2562,2563],{"class":283,"line":1220},[281,2564,2565],{},"print(f\"Treino: {len(dados_treino)} | Avaliacao: {len(dados_avaliacao)}\")\n",[281,2567,2568],{"class":283,"line":1226},[281,2569,306],{"emptyLinePlaceholder":48},[281,2571,2572],{"class":283,"line":1231},[281,2573,2574],{},"#carregamento dos dados\n",[281,2576,2577],{"class":283,"line":1237},[281,2578,805],{},[281,2580,2581],{"class":283,"line":1242},[281,2582,2583],{},"  #gerando um lote pequeno de dados de input x e targets y\n",[281,2585,2587],{"class":283,"line":2586},50,[281,2588,2589],{},"  dados = dados_treino if split == 'treino' else dados_avaliacao\n",[281,2591,2593],{"class":283,"line":2592},51,[281,2594,820],{},[281,2596,2598],{"class":283,"line":2597},52,[281,2599,2600],{},"  x = torch.stack([dados[i:i+block_size] for i in ix])\n",[281,2602,2604],{"class":283,"line":2603},53,[281,2605,830],{},[281,2607,2609],{"class":283,"line":2608},54,[281,2610,2611],{},"  x, y = x.to(device), y.to(device)\n",[281,2613,2615],{"class":283,"line":2614},55,[281,2616,835],{},[281,2618,2620],{"class":283,"line":2619},56,[281,2621,306],{"emptyLinePlaceholder":48},[281,2623,2625],{"class":283,"line":2624},57,[281,2626,2627],{},"@torch.no_grad()\n",[281,2629,2631],{"class":283,"line":2630},58,[281,2632,2633],{},"def perda_estimada():\n",[281,2635,2637],{"class":283,"line":2636},59,[281,2638,2639],{},"  out = {}\n",[281,2641,2643],{"class":283,"line":2642},60,[281,2644,2645],{},"  modelo.eval()\n",[281,2647,2649],{"class":283,"line":2648},61,[281,2650,2651],{},"  for split in ['treino', 'avaliacao']:\n",[281,2653,2655],{"class":283,"line":2654},62,[281,2656,2657],{},"    losses = torch.zeros(eval_iters)\n",[281,2659,2661],{"class":283,"line":2660},63,[281,2662,2663],{},"    for k in range(eval_iters):\n",[281,2665,2667],{"class":283,"line":2666},64,[281,2668,2669],{},"      X, Y = get_batch(split)\n",[281,2671,2673],{"class":283,"line":2672},65,[281,2674,2675],{},"      logits, loss = modelo(X, Y)\n",[281,2677,2679],{"class":283,"line":2678},66,[281,2680,2681],{},"      losses[k] = loss.item()\n",[281,2683,2685],{"class":283,"line":2684},67,[281,2686,2687],{},"    out[split] = losses.mean()\n",[281,2689,2691],{"class":283,"line":2690},68,[281,2692,306],{"emptyLinePlaceholder":48},[281,2694,2696],{"class":283,"line":2695},69,[281,2697,2698],{},"  modelo.train()\n",[281,2700,2702],{"class":283,"line":2701},70,[281,2703,2704],{},"  return out\n",[281,2706,2708],{"class":283,"line":2707},71,[281,2709,306],{"emptyLinePlaceholder":48},[281,2711,2713],{"class":283,"line":2712},72,[281,2714,2715],{},"class Head(nn.Module):\n",[281,2717,2719],{"class":283,"line":2718},73,[281,2720,2721],{},"  \"\"\" uma cabeça de self-attention \"\"\"\n",[281,2723,2725],{"class":283,"line":2724},74,[281,2726,306],{"emptyLinePlaceholder":48},[281,2728,2730],{"class":283,"line":2729},75,[281,2731,2732],{},"  def __init__(self, head_size):\n",[281,2734,2736],{"class":283,"line":2735},76,[281,2737,1024],{},[281,2739,2741],{"class":283,"line":2740},77,[281,2742,2743],{},"    self.key = nn.Linear(n_embd, head_size, bias=False)\n",[281,2745,2747],{"class":283,"line":2746},78,[281,2748,2749],{},"    self.query = nn.Linear(n_embd, head_size, bias=False)\n",[281,2751,2753],{"class":283,"line":2752},79,[281,2754,2755],{},"    self.value = nn.Linear(n_embd, head_size, bias=False)\n",[281,2757,2759],{"class":283,"line":2758},80,[281,2760,2761],{},"    self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))\n",[281,2763,2765],{"class":283,"line":2764},81,[281,2766,306],{"emptyLinePlaceholder":48},[281,2768,2770],{"class":283,"line":2769},82,[281,2771,2772],{},"    self.dropout = nn.Dropout(dropout)\n",[281,2774,2776],{"class":283,"line":2775},83,[281,2777,306],{"emptyLinePlaceholder":48},[281,2779,2781],{"class":283,"line":2780},84,[281,2782,2783],{},"  def forward(self, x):\n",[281,2785,2787],{"class":283,"line":2786},85,[281,2788,2789],{},"    B,T,C = x.shape\n",[281,2791,2793],{"class":283,"line":2792},86,[281,2794,2795],{},"    k = self.key(x)   # (B,T,C)\n",[281,2797,2799],{"class":283,"line":2798},87,[281,2800,2801],{},"    q = self.query(x) # (B,T,C)\n",[281,2803,2805],{"class":283,"line":2804},88,[281,2806,2807],{},"    # calcular pontuações de atenção (\"afinidades\")\n",[281,2809,2811],{"class":283,"line":2810},89,[281,2812,2813],{},"    wei = q @ k.transpose(-2,-1) * C**-0.5 # (B, T, C) @ (B, C, T) -> (B, T, T)\n",[281,2815,2817],{"class":283,"line":2816},90,[281,2818,2819],{},"    wei = wei.masked_fill(self.tril[:T, :T] == 0, float('-inf')) # (B, T, T)\n",[281,2821,2823],{"class":283,"line":2822},91,[281,2824,2825],{},"    wei = F.softmax(wei, dim=-1) # (B, T, T)\n",[281,2827,2829],{"class":283,"line":2828},92,[281,2830,2831],{},"    wei = self.dropout(wei)\n",[281,2833,2835],{"class":283,"line":2834},93,[281,2836,2837],{},"    # performar a média ponderada das valores\n",[281,2839,2841],{"class":283,"line":2840},94,[281,2842,2843],{},"    v = self.value(x) # (B,T,C)\n",[281,2845,2847],{"class":283,"line":2846},95,[281,2848,2849],{},"    out = wei @ v # (B, T, T) @ (B, T, C) -> (B, T, C)\n",[281,2851,2853],{"class":283,"line":2852},96,[281,2854,2855],{},"    return out\n",[281,2857,2859],{"class":283,"line":2858},97,[281,2860,306],{"emptyLinePlaceholder":48},[281,2862,2864],{"class":283,"line":2863},98,[281,2865,2866],{},"class MultiHeadAttention(nn.Module):\n",[281,2868,2870],{"class":283,"line":2869},99,[281,2871,2872],{},"  \"\"\" Múltiplas cabeças de self-attention em paralelo \"\"\"\n",[281,2874,2876],{"class":283,"line":2875},100,[281,2877,306],{"emptyLinePlaceholder":48},[281,2879,2881],{"class":283,"line":2880},101,[281,2882,2883],{},"  def __init__(self, numero_cabecas, tamanho_cabeca):\n",[281,2885,2887],{"class":283,"line":2886},102,[281,2888,1024],{},[281,2890,2892],{"class":283,"line":2891},103,[281,2893,2894],{},"    self.heads = nn.ModuleList([Head(tamanho_cabeca) for _ in range(numero_cabecas)])\n",[281,2896,2898],{"class":283,"line":2897},104,[281,2899,2900],{},"    self.proj = nn.Linear(n_embd, n_embd)\n",[281,2902,2904],{"class":283,"line":2903},105,[281,2905,2772],{},[281,2907,2909],{"class":283,"line":2908},106,[281,2910,306],{"emptyLinePlaceholder":48},[281,2912,2914],{"class":283,"line":2913},107,[281,2915,2783],{},[281,2917,2919],{"class":283,"line":2918},108,[281,2920,2921],{},"    out = torch.cat([h(x) for h in self.heads], dim=-1)\n",[281,2923,2925],{"class":283,"line":2924},109,[281,2926,2927],{},"    out = self.dropout(self.proj(out))\n",[281,2929,2931],{"class":283,"line":2930},110,[281,2932,2855],{},[281,2934,2936],{"class":283,"line":2935},111,[281,2937,306],{"emptyLinePlaceholder":48},[281,2939,2941],{"class":283,"line":2940},112,[281,2942,2943],{},"class FeedFoward(nn.Module):\n",[281,2945,2947],{"class":283,"line":2946},113,[281,2948,2949],{},"  \"\"\" Uma camada linear simples seguida de uma função de ativação ReLU \"\"\"\n",[281,2951,2953],{"class":283,"line":2952},114,[281,2954,306],{"emptyLinePlaceholder":48},[281,2956,2958],{"class":283,"line":2957},115,[281,2959,2960],{},"  def __init__(self, n_embd):\n",[281,2962,2964],{"class":283,"line":2963},116,[281,2965,1024],{},[281,2967,2969],{"class":283,"line":2968},117,[281,2970,2971],{},"    self.net = nn.Sequential(\n",[281,2973,2975],{"class":283,"line":2974},118,[281,2976,2977],{},"        nn.Linear(n_embd, 4 * n_embd),\n",[281,2979,2981],{"class":283,"line":2980},119,[281,2982,2983],{},"        nn.ReLU(),\n",[281,2985,2987],{"class":283,"line":2986},120,[281,2988,2989],{},"        nn.Linear( 4 * n_embd, n_embd),\n",[281,2991,2993],{"class":283,"line":2992},121,[281,2994,2995],{},"        nn.Dropout(dropout)\n",[281,2997,2999],{"class":283,"line":2998},122,[281,3000,3001],{},"    )\n",[281,3003,3005],{"class":283,"line":3004},123,[281,3006,306],{"emptyLinePlaceholder":48},[281,3008,3010],{"class":283,"line":3009},124,[281,3011,2783],{},[281,3013,3015],{"class":283,"line":3014},125,[281,3016,3017],{},"    return self.net(x)\n",[281,3019,3021],{"class":283,"line":3020},126,[281,3022,306],{"emptyLinePlaceholder":48},[281,3024,3026],{"class":283,"line":3025},127,[281,3027,3028],{},"class Block(nn.Module):\n",[281,3030,3032],{"class":283,"line":3031},128,[281,3033,3034],{},"  \"\"\"Bloco Transformer: comunicação seguida de computação\"\"\"\n",[281,3036,3038],{"class":283,"line":3037},129,[281,3039,306],{"emptyLinePlaceholder":48},[281,3041,3043],{"class":283,"line":3042},130,[281,3044,3045],{},"  def __init__(self, n_embd, n_head):\n",[281,3047,3049],{"class":283,"line":3048},131,[281,3050,3051],{},"    # n_embd: embedding dimension, n_head:\n",[281,3053,3055],{"class":283,"line":3054},132,[281,3056,3057],{},"    #quantidade de cabeças desejadas\n",[281,3059,3061],{"class":283,"line":3060},133,[281,3062,1024],{},[281,3064,3066],{"class":283,"line":3065},134,[281,3067,3068],{},"    head_size = n_embd \u002F\u002F n_head\n",[281,3070,3072],{"class":283,"line":3071},135,[281,3073,3074],{},"    self.sa = MultiHeadAttention(n_head, head_size)\n",[281,3076,3078],{"class":283,"line":3077},136,[281,3079,3080],{},"    self.ffwd = FeedFoward(n_embd)\n",[281,3082,3084],{"class":283,"line":3083},137,[281,3085,3086],{},"    self.ln1 = nn.LayerNorm(n_embd)\n",[281,3088,3090],{"class":283,"line":3089},138,[281,3091,3092],{},"    self.ln2 = nn.LayerNorm(n_embd)\n",[281,3094,3096],{"class":283,"line":3095},139,[281,3097,306],{"emptyLinePlaceholder":48},[281,3099,3101],{"class":283,"line":3100},140,[281,3102,2783],{},[281,3104,3106],{"class":283,"line":3105},141,[281,3107,3108],{},"    x = x + self.sa(self.ln1(x))\n",[281,3110,3112],{"class":283,"line":3111},142,[281,3113,3114],{},"    x = x + self.ffwd(self.ln2(x))\n",[281,3116,3118],{"class":283,"line":3117},143,[281,3119,3120],{},"    return x\n",[11,3122,3123,3124,3127,3128,3130,3131,3134,3135,3138,3139,3142,3143,3146,3147,3149,3150,3153,3154,3157,3158,3161],{},"Achei o terceiro bug logo na segunda linha: ",[247,3125,3126],{},"from torch.nn import functional as Fold",". ",[247,3129,2346],{},", não ",[247,3132,3133],{},"F",". Só que toda a classe ",[247,3136,3137],{},"Head"," usa ",[247,3140,3141],{},"F.softmax",", sem o menor erro. Por quê? Porque uma célula bem anterior (a do primeiro bigram) já tinha rodado ",[247,3144,3145],{},"from torch.nn import functional as F",", e esse ",[247,3148,3133],{}," continua vivo na memória do kernel do Colab, mesmo sem eu reexecutar aquela célula. Se isso fosse um script ",[247,3151,3152],{},".py"," rodado do zero, ia estourar ",[247,3155,3156],{},"NameError"," na hora. É a pegadinha clássica de notebook: a ordem que você ",[21,3159,3160],{},"executa"," as células importa mais do que a ordem que elas aparecem na tela.",[11,3163,3164,3165,1910,3168,1910,3171,1910,3174,1272],{},"Saída real dessa célula confirma que o resto rodou liso: ",[247,3166,3167],{},"Dispositivo: cuda",[247,3169,3170],{},"Tamanho do texto: 400944 caracteres",[247,3172,3173],{},"Tamanho do vocabulario: 112",[247,3175,3176],{},"Treino: 360849 | Avaliacao: 40095",[11,3178,3179,3180,3182,3183,3186,3187,3189,3190,3193,3194,3197,3198,3201,3202,3205,3206,3209,3210,3213,3214,3217],{},"O resto da célula empilha as peças: ",[247,3181,3137],{}," é uma cabeça de atenção (o que já vimos ali em cima, só que agora reaproveitável). ",[247,3184,3185],{},"MultiHeadAttention"," roda várias ",[247,3188,3137],{}," em paralelo e concatena o resultado. ",[247,3191,3192],{},"FeedFoward"," é uma rede de duas camadas lineares com ",[247,3195,3196],{},"ReLU"," no meio, processando cada posição sozinha (sem olhar as vizinhas). ",[247,3199,3200],{},"Block"," junta tudo com ",[95,3203,3204],{},"conexão residual"," (o ",[247,3207,3208],{},"x = x + ..."," em vez de ",[247,3211,3212],{},"x = ...",", deixa o gradiente fluir direto pelas camadas) e ",[95,3215,3216],{},"layer norm"," antes de cada sub-camada.",[273,3219,3221],{"className":275,"code":3220,"language":277,"meta":42,"style":42},"#um super simples modelo bigram\nclass BigramLanguageModel(nn.Module):\n  def __init__(self):\n    super().__init__()\n    # cada token lê diretamente os logits para o próximo token a partir de uma\n    # tabela de consulta\n    self.token_embeeding_table = nn.Embedding(tam_vocabulario, n_embd)\n    self.position_embeeding_table =nn.Embedding(block_size, n_embd)\n    self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])\n    self.ln_f = nn.LayerNorm(n_embd)\n    self.lm_head = nn.Linear(n_embd, tam_vocabulario)\n\n  def forward(self, idx, targets=None):\n    B, T = idx.shape\n\n    # idx e targets são ambos tensores de inteiros de formato (B, T)\n    tok_emb = self.token_embeeding_table(idx) # (B, T,C)\n    pos_emb = self.position_embeeding_table(torch.arange(T, device=device)) # (T, C)\n    x = tok_emb + pos_emb # (B, T,C)\n    x = self.blocks(x) # (B, T,C)\n    x = self.ln_f(x) # (B, T,C)\n    logits = self.lm_head(x) # (B, T, vocab_size)\n\n    if targets is None:\n      loss = None\n    else:\n      B,T,C = logits.shape\n      logits = logits.view(B*T, C)\n      targets = targets.view(B*T)\n      loss = F.cross_entropy(logits, targets)\n\n    return logits, loss\n\n  def generate(self, idx, max_new_tokens):\n    # idx é um array de índices (B, T)\n    # no contexto atual\n    for _ in range(max_new_tokens):\n      # corta idx para os últimos tokens de tamanho block_size\n      idx_cond = idx[:, -block_size:]\n      #captura predições\n      logits, loss = self(idx_cond)\n      #foca apenas no último passo de tempo\n      logits = logits[:, -1, : ] # (B, C)\n      #aplica softmax para capturar probabilidades\n      probabilidades = F.softmax(logits, dim=-1) # (B, C)\n      # amostra da distribuição\n      idx_next = torch.multinomial(probabilidades, num_samples=1) # (B, 1)\n      # anexa o índice amostrado à sequência em formação\n      idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)\n    return idx\n",[247,3222,3223,3228,3232,3237,3241,3246,3251,3256,3261,3266,3271,3276,3280,3284,3289,3293,3298,3303,3308,3313,3318,3323,3328,3332,3336,3340,3344,3349,3353,3357,3361,3365,3369,3373,3377,3381,3386,3390,3395,3400,3405,3410,3414,3419,3424,3428,3432,3437,3442,3447],{"__ignoreMap":42},[281,3224,3225],{"class":283,"line":221},[281,3226,3227],{},"#um super simples modelo bigram\n",[281,3229,3230],{"class":283,"line":43},[281,3231,1014],{},[281,3233,3234],{"class":283,"line":303},[281,3235,3236],{},"  def __init__(self):\n",[281,3238,3239],{"class":283,"line":309},[281,3240,1024],{},[281,3242,3243],{"class":283,"line":49},[281,3244,3245],{},"    # cada token lê diretamente os logits para o próximo token a partir de uma\n",[281,3247,3248],{"class":283,"line":320},[281,3249,3250],{},"    # tabela de consulta\n",[281,3252,3253],{"class":283,"line":427},[281,3254,3255],{},"    self.token_embeeding_table = nn.Embedding(tam_vocabulario, n_embd)\n",[281,3257,3258],{"class":283,"line":433},[281,3259,3260],{},"    self.position_embeeding_table =nn.Embedding(block_size, n_embd)\n",[281,3262,3263],{"class":283,"line":514},[281,3264,3265],{},"    self.blocks = nn.Sequential(*[Block(n_embd, n_head=n_head) for _ in range(n_layer)])\n",[281,3267,3268],{"class":283,"line":520},[281,3269,3270],{},"    self.ln_f = nn.LayerNorm(n_embd)\n",[281,3272,3273],{"class":283,"line":526},[281,3274,3275],{},"    self.lm_head = nn.Linear(n_embd, tam_vocabulario)\n",[281,3277,3278],{"class":283,"line":722},[281,3279,306],{"emptyLinePlaceholder":48},[281,3281,3282],{"class":283,"line":728},[281,3283,1048],{},[281,3285,3286],{"class":283,"line":847},[281,3287,3288],{},"    B, T = idx.shape\n",[281,3290,3291],{"class":283,"line":853},[281,3292,306],{"emptyLinePlaceholder":48},[281,3294,3295],{"class":283,"line":859},[281,3296,3297],{},"    # idx e targets são ambos tensores de inteiros de formato (B, T)\n",[281,3299,3300],{"class":283,"line":865},[281,3301,3302],{},"    tok_emb = self.token_embeeding_table(idx) # (B, T,C)\n",[281,3304,3305],{"class":283,"line":871},[281,3306,3307],{},"    pos_emb = self.position_embeeding_table(torch.arange(T, device=device)) # (T, C)\n",[281,3309,3310],{"class":283,"line":877},[281,3311,3312],{},"    x = tok_emb + pos_emb # (B, T,C)\n",[281,3314,3315],{"class":283,"line":883},[281,3316,3317],{},"    x = self.blocks(x) # (B, T,C)\n",[281,3319,3320],{"class":283,"line":889},[281,3321,3322],{},"    x = self.ln_f(x) # (B, T,C)\n",[281,3324,3325],{"class":283,"line":894},[281,3326,3327],{},"    logits = self.lm_head(x) # (B, T, vocab_size)\n",[281,3329,3330],{"class":283,"line":900},[281,3331,306],{"emptyLinePlaceholder":48},[281,3333,3334],{"class":283,"line":906},[281,3335,1072],{},[281,3337,3338],{"class":283,"line":912},[281,3339,1077],{},[281,3341,3342],{"class":283,"line":918},[281,3343,1082],{},[281,3345,3346],{"class":283,"line":1114},[281,3347,3348],{},"      B,T,C = logits.shape\n",[281,3350,3351],{"class":283,"line":1119},[281,3352,1092],{},[281,3354,3355],{"class":283,"line":1125},[281,3356,1097],{},[281,3358,3359],{"class":283,"line":1131},[281,3360,1102],{},[281,3362,3363],{"class":283,"line":1137},[281,3364,306],{"emptyLinePlaceholder":48},[281,3366,3367],{"class":283,"line":1143},[281,3368,1111],{},[281,3370,3371],{"class":283,"line":1149},[281,3372,306],{"emptyLinePlaceholder":48},[281,3374,3375],{"class":283,"line":1155},[281,3376,1122],{},[281,3378,3379],{"class":283,"line":1161},[281,3380,1128],{},[281,3382,3383],{"class":283,"line":1167},[281,3384,3385],{},"    # no contexto atual\n",[281,3387,3388],{"class":283,"line":1173},[281,3389,1140],{},[281,3391,3392],{"class":283,"line":1179},[281,3393,3394],{},"      # corta idx para os últimos tokens de tamanho block_size\n",[281,3396,3397],{"class":283,"line":1185},[281,3398,3399],{},"      idx_cond = idx[:, -block_size:]\n",[281,3401,3402],{"class":283,"line":1191},[281,3403,3404],{},"      #captura predições\n",[281,3406,3407],{"class":283,"line":1197},[281,3408,3409],{},"      logits, loss = self(idx_cond)\n",[281,3411,3412],{"class":283,"line":1202},[281,3413,1158],{},[281,3415,3416],{"class":283,"line":1208},[281,3417,3418],{},"      logits = logits[:, -1, : ] # (B, C)\n",[281,3420,3421],{"class":283,"line":1214},[281,3422,3423],{},"      #aplica softmax para capturar probabilidades\n",[281,3425,3426],{"class":283,"line":1220},[281,3427,1176],{},[281,3429,3430],{"class":283,"line":1226},[281,3431,1182],{},[281,3433,3434],{"class":283,"line":1231},[281,3435,3436],{},"      idx_next = torch.multinomial(probabilidades, num_samples=1) # (B, 1)\n",[281,3438,3439],{"class":283,"line":1237},[281,3440,3441],{},"      # anexa o índice amostrado à sequência em formação\n",[281,3443,3444],{"class":283,"line":1242},[281,3445,3446],{},"      idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)\n",[281,3448,3449],{"class":283,"line":2586},[281,3450,1194],{},[11,3452,3453,3454,3457,3458,3461,3462,935,3465,3468,3469,3472,3473,3476,3477,3479,3480,3483,3484,3486],{},"Apesar do nome (deixei o comentário original \"bigram\" mesmo não sendo mais um bigram de verdade), essa é a classe completa: ",[247,3455,3456],{},"token_embeeding_table"," mais ",[247,3459,3460],{},"position_embeeding_table"," (soma dos dois dá pra cada posição saber \"quem eu sou\" e \"onde eu tô\"), ",[247,3463,3464],{},"blocks",[247,3466,3467],{},"n_layer"," blocos Transformer em sequência, ",[247,3470,3471],{},"ln_f"," normaliza no final, ",[247,3474,3475],{},"lm_head"," projeta de volta pro tamanho do vocabulário. E o ",[247,3478,1277],{}," dessa vez ",[95,3481,3482],{},"tem"," a linha ",[247,3485,1298],{},", corrigindo o bug do primeiro bigram, é por isso que só esse modelo consegue gerar texto de verdade.",[85,3488,3490],{"id":3489},"rodando-de-verdade-hiperparâmetros-treino-geração","Rodando de verdade: hiperparâmetros, treino, geração",[273,3492,3494],{"className":275,"code":3493,"language":277,"meta":42,"style":42},"modelo = BigramLanguageModel()\nm = modelo.to(device)\n\n# imprimir o número de parâmetros no modelo\nprint(sum(p.numel() for p in m.parameters())\u002F1e6, 'M Número de parâmetros')\n\n#criando um otimizador PyTorch\noptimizer = torch.optim.AdamW(modelo.parameters(), lr=learning_rate)\n\nfor  iter in range(max_iters):\n  # de tempos em tempos, avalie a perda nos conjuntos de treino e validação\n  if iter % eval_interval == 0 or iter == max_iters - 1:\n    losses = perda_estimada()\n    print(f\"step {iter}: train loss {losses['treino']:.4f}, val loss {losses['avaliacao']:.4f}\")\n\n  # amostra de um lote de dados\n  xb, yb = get_batch('treino')\n\n  #avaliando o loss\n  logits, loss = modelo(xb, yb)\n  optimizer.zero_grad(set_to_none=True)\n  loss.backward()\n  optimizer.step()\n\n# gerar a partir do modelo\ncontext = torch.zeros((1,1), dtype=torch.long, device=device)\nprint(decode(modelo.generate(context, max_new_tokens=2000)[0].tolist()))\n",[247,3495,3496,3501,3506,3510,3515,3520,3524,3529,3534,3538,3543,3548,3553,3558,3563,3567,3571,3575,3579,3583,3587,3591,3595,3599,3603,3608,3613],{"__ignoreMap":42},[281,3497,3498],{"class":283,"line":221},[281,3499,3500],{},"modelo = BigramLanguageModel()\n",[281,3502,3503],{"class":283,"line":43},[281,3504,3505],{},"m = modelo.to(device)\n",[281,3507,3508],{"class":283,"line":303},[281,3509,306],{"emptyLinePlaceholder":48},[281,3511,3512],{"class":283,"line":309},[281,3513,3514],{},"# imprimir o número de parâmetros no modelo\n",[281,3516,3517],{"class":283,"line":49},[281,3518,3519],{},"print(sum(p.numel() for p in m.parameters())\u002F1e6, 'M Número de parâmetros')\n",[281,3521,3522],{"class":283,"line":320},[281,3523,306],{"emptyLinePlaceholder":48},[281,3525,3526],{"class":283,"line":427},[281,3527,3528],{},"#criando um otimizador PyTorch\n",[281,3530,3531],{"class":283,"line":433},[281,3532,3533],{},"optimizer = torch.optim.AdamW(modelo.parameters(), lr=learning_rate)\n",[281,3535,3536],{"class":283,"line":514},[281,3537,306],{"emptyLinePlaceholder":48},[281,3539,3540],{"class":283,"line":520},[281,3541,3542],{},"for  iter in range(max_iters):\n",[281,3544,3545],{"class":283,"line":526},[281,3546,3547],{},"  # de tempos em tempos, avalie a perda nos conjuntos de treino e validação\n",[281,3549,3550],{"class":283,"line":722},[281,3551,3552],{},"  if iter % eval_interval == 0 or iter == max_iters - 1:\n",[281,3554,3555],{"class":283,"line":728},[281,3556,3557],{},"    losses = perda_estimada()\n",[281,3559,3560],{"class":283,"line":847},[281,3561,3562],{},"    print(f\"step {iter}: train loss {losses['treino']:.4f}, val loss {losses['avaliacao']:.4f}\")\n",[281,3564,3565],{"class":283,"line":853},[281,3566,306],{"emptyLinePlaceholder":48},[281,3568,3569],{"class":283,"line":859},[281,3570,1372],{},[281,3572,3573],{"class":283,"line":865},[281,3574,1377],{},[281,3576,3577],{"class":283,"line":871},[281,3578,306],{"emptyLinePlaceholder":48},[281,3580,3581],{"class":283,"line":877},[281,3582,1386],{},[281,3584,3585],{"class":283,"line":883},[281,3586,1391],{},[281,3588,3589],{"class":283,"line":889},[281,3590,1396],{},[281,3592,3593],{"class":283,"line":894},[281,3594,1401],{},[281,3596,3597],{"class":283,"line":900},[281,3598,1406],{},[281,3600,3601],{"class":283,"line":906},[281,3602,306],{"emptyLinePlaceholder":48},[281,3604,3605],{"class":283,"line":912},[281,3606,3607],{},"# gerar a partir do modelo\n",[281,3609,3610],{"class":283,"line":918},[281,3611,3612],{},"context = torch.zeros((1,1), dtype=torch.long, device=device)\n",[281,3614,3615],{"class":283,"line":1114},[281,3616,3617],{},"print(decode(modelo.generate(context, max_new_tokens=2000)[0].tolist()))\n",[11,3619,3620,3621,3624,3625,3628,3629,1272],{},"Essa é a célula que treina de verdade: ",[247,3622,3623],{},"0,215792 M"," parâmetros (215.792, pra ser exato), 5.000 iterações, avaliando a perda de treino e validação a cada 100 passos. Primeira linha da saída: ",[247,3626,3627],{},"step 0: train loss 4.8673, val loss 4.8625",", perto do chute aleatório esperado. Última: ",[247,3630,3631],{},"step 4999: train loss 1.6304, val loss 2.6720",[85,3633,3635],{"id":3634},"os-dois-treinos-lado-a-lado","Os dois treinos, lado a lado",[11,3637,3638,3639,1910,3642,1910,3645,1910,3648,1910,3651,1910,3654,3657],{},"Mesmos hiperparâmetros nos dois notebooks (",[247,3640,3641],{},"batch_size=16",[247,3643,3644],{},"block_size=32",[247,3646,3647],{},"n_embd=64",[247,3649,3650],{},"n_head=4",[247,3652,3653],{},"n_layer=4",[247,3655,3656],{},"max_iters=5000","), a única coisa que muda é o texto:",[153,3659,3660,3672],{},[156,3661,3662],{},[159,3663,3664,3666,3669],{},[162,3665],{"align":164},[162,3667,3668],{"align":164},"Shakespeare (original)",[162,3670,3671],{"align":164},"Dom Casmurro (meu)",[170,3673,3674,3685,3695,3706,3717],{},[159,3675,3676,3679,3682],{},[175,3677,3678],{"align":164},"Caracteres no dataset",[175,3680,3681],{"align":164},"1.115.394",[175,3683,3684],{"align":164},"400.944",[159,3686,3687,3690,3692],{},[175,3688,3689],{"align":164},"Tamanho do vocabulário",[175,3691,387],{"align":164},[175,3693,3694],{"align":164},"112",[159,3696,3697,3700,3703],{},[175,3698,3699],{"align":164},"Parâmetros do modelo",[175,3701,3702],{"align":164},"0,2097 M",[175,3704,3705],{"align":164},"0,2158 M",[159,3707,3708,3711,3714],{},[175,3709,3710],{"align":164},"Perda de treino (passo 4999)",[175,3712,3713],{"align":164},"1,6645",[175,3715,3716],{"align":164},"1,6304",[159,3718,3719,3722,3725],{},[175,3720,3721],{"align":164},"Perda de validação (passo 4999)",[175,3723,3724],{"align":164},"1,8286",[175,3726,3727],{"align":164},"2,6720",[11,3729,3730],{},"A perda de treino ficou parecida nos dois, mas a de validação do Dom Casmurro ficou bem pior. Duas razões reais, sem inventar: primeiro, eu treinei com menos de um terço do volume de texto (400 mil caracteres contra 1,1 milhão), e o conjunto de validação também encolheu proporcionalmente (uns 40 mil caracteres contra uns 111 mil), então o modelo teve menos exemplo pra generalizar. Segundo, o vocabulário maior (112 contra 65) significa mais classes possíveis pra acertar a cada posição, e a morfologia do português (conjugação verbal rica, muito mais variação de sufixo que o inglês arcaico do Shakespeare) dá mais trabalho pra um modelo desse tamanho absorver. Não é bug de código, é a arquitetura idêntica batendo num dataset mais difícil com menos munição.",[85,3732,3734],{"id":3733},"o-que-sai-da-geração","O que sai da geração",[11,3736,3737],{},"Depois dos 5.000 passos, gerando a partir de um contexto vazio, o modelo Dom Casmurro cospe isto (sem pontuação nem sentido de frase completa, mas repare):",[3739,3740,3741],"blockquote",{},[11,3742,3743],{},[21,3744,3745],{},"...abnoha (força-lhe o sappagorla, affendado escontraga-se, não vei nelles, prima Justina, como ella devi enferiu o não dizia uma paertada continueira... de Escobar cegos de enho qué está acconterno gostaria...",[11,3747,3748],{},"\"Justina\" e \"Escobar\" são nomes de personagens reais do livro. O modelo não sabe quem é Escobar, não entende o enredo, é 215 mil parâmetros de puro padrão estatístico de caractere. Mas ele aprendeu que essas sequências de letra aparecem juntas com frequência suficiente pra reproduzir nomes próprios inteiros no meio do ruído. É a mesma coisa que acontece na versão em inglês, que já aprendeu a colocar \"KING RICHARD III:\" e \"QUEEN VINCENTIO:\" em maiúsculo no formato certo de fala de peça de teatro, sem entender uma palavra do que a personagem tá dizendo depois.",[85,3750,151],{"id":150},[153,3752,3753,3761],{},[156,3754,3755],{},[159,3756,3757,3759],{},[162,3758,165],{"align":164},[162,3760,168],{"align":164},[170,3762,3763,3771,3779],{},[159,3764,3765,3768],{},[175,3766,3767],{"align":164},"Self-attention é \"um jeito de comparar tokens\"",[175,3769,3770],{"align":164},"É literalmente uma média ponderada aprendida, e a \"mágica\" é só matriz triangular mais softmax",[159,3772,3773,3776],{},[175,3774,3775],{"align":164},"Notebook e script são a mesma coisa",[175,3777,3778],{"align":164},"Notebook guarda estado entre células fora de ordem, script não, e isso pode esconder bug de import inteiro",[159,3780,3781,3784],{},[175,3782,3783],{"align":164},"\"Matematicamente equivalente\" é sinônimo de \"numericamente igual\"",[175,3785,3786,3787,3789,3790,3792],{"align":164},"Não é: ",[247,3788,1694],{}," deu ",[247,3791,1704],{}," em duas contas que deveriam bater, por causa da ordem das operações em ponto flutuante",[85,3794,200],{"id":199},[11,3796,3797],{},"Os números da tabela ali em cima vêm dos dois treinos de verdade, passo a passo, registrados durante o treino de 5.000 iterações de cada notebook (a cada 100 passos). Arrasta o zoom do gráfico e repara na abertura entre treino e validação: no Shakespeare as duas linhas ficam bem coladas até o final, no Dom Casmurro elas descolam cedo e não voltam a se aproximar, o sinal visual exato do que a tabela já contou em número.",[3799,3800],"slm-training-loss-compare",{"dom-casmurro-train-label":3801,"dom-casmurro-val-label":3802,"shakespeare-train-label":3803,"shakespeare-val-label":3804,"x-label":3805,"y-label":1263},"Dom Casmurro · treino","Dom Casmurro · validação","Shakespeare · treino","Shakespeare · validação","passo de treino",[11,3807,3808],{},"Mesma arquitetura, mesmos hiperparâmetros, duas línguas diferentes, e o gráfico sozinho já mostra qual delas deu mais trabalho pro modelinho de 215 mil parâmetros aprender.",[3810,3811,3812],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":42,"searchDepth":43,"depth":43,"links":3814},[3815,3816,3817,3818,3819,3820,3821,3822,3823,3824,3825,3826,3828,3829,3830,3831,3832],{"id":270,"depth":43,"text":271},{"id":344,"depth":43,"text":345},{"id":553,"depth":43,"text":554},{"id":660,"depth":43,"text":661},{"id":775,"depth":43,"text":776},{"id":981,"depth":43,"text":982},{"id":1336,"depth":43,"text":1337},{"id":1453,"depth":43,"text":1454},{"id":1778,"depth":43,"text":1779},{"id":1976,"depth":43,"text":1977},{"id":2123,"depth":43,"text":2124},{"id":2342,"depth":43,"text":3827},"Bug #3: a pegadinha do Fold, e o modelo completo",{"id":3489,"depth":43,"text":3490},{"id":3634,"depth":43,"text":3635},{"id":3733,"depth":43,"text":3734},{"id":150,"depth":43,"text":151},{"id":199,"depth":43,"text":200},"Capítulo 2 do livro constrói um GPT minúsculo do zero, o mesmo exercício clássico do Karpathy. Passo a limpo aqui o meu notebook inteiro, célula por célula, código completo, comparando com o notebook original e achando bugs reais pelo caminho.",{},"\u002Fpt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fbuilding-gpt-from-scratch-dom-casmurro",{"title":234,"description":3833},"pt\u002Fplaylists\u002Fhow-to-build-and-finetune-slm\u002Fbuilding-gpt-from-scratch-dom-casmurro",[3839,3840,3841,3842],"transformers","self-attention","gpt","pytorch","PnmyaoFVffOXUTPQTTBUxWgmwPM10U5tIL8lYkZCxN4",1787605213150]