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