[{"data":1,"prerenderedAt":367},["ShallowReactive",2],{"recent-notas":3,"recent-posts":4},[],[5,317,334,351],{"_path":6,"_dir":7,"_draft":8,"_partial":8,"_locale":9,"title":10,"description":11,"date":12,"tags":13,"body":17,"_type":311,"_id":312,"_source":313,"_file":314,"_stem":315,"_extension":316},"/blog/tokenizer-c","blog",false,"","Por que o tokenizer do meu GPT-2 caseiro demorava 5 minutos, e como C resolveu em 3 segundos","Voltando para as raizes codando em C","2026-03-18",[14,15,16],"AI","Python","C",{"type":18,"children":19,"toc":303},"root",[20,28,35,41,46,51,57,62,67,81,102,108,127,132,153,159,172,177,182,248,253,259,272,285,298],{"type":21,"tag":22,"props":23,"children":25},"element","h1",{"id":24},"por-que-o-tokenizer-do-meu-gpt-2-caseiro-demorava-5-minutos-e-como-c-resolveu-em-3-segundos",[26],{"type":27,"value":10},"text",{"type":21,"tag":29,"props":30,"children":32},"h2",{"id":31},"tokenizer-o-tradutor-que-ninguém-presta-atenção",[33],{"type":27,"value":34},"Tokenizer: o tradutor que ninguém presta atenção",{"type":21,"tag":36,"props":37,"children":38},"p",{},[39],{"type":27,"value":40},"Modelos de linguagem não leem texto. Leem números. Alguém precisa fazer essa conversão, e esse alguém é o tokenizer.",{"type":21,"tag":36,"props":42,"children":43},{},[44],{"type":27,"value":45},"O GPT-2 usa BPE (Byte Pair Encoding). Começa com os 256 bytes possíveis e vai fundindo os pares mais frequentes do corpus até montar um vocabulário. Depois de uns milhares de merges, o vocabulário captura pedaços úteis da língua: sílabas, palavras comuns, sufixos. O tamanho do vocabulário importa porque define a resolução com que o modelo enxerga o texto. Se o tokenizer fragmenta demais, o modelo gasta capacidade juntando pedaços. Se comprime bem, sobra capacidade pra entender o que está escrito.",{"type":21,"tag":36,"props":47,"children":48},{},[49],{"type":27,"value":50},"No Gepeto-2 (meu mini GPT-2 educacional), implementei o BPE do zero em Python. Byte-level, mesma regex de pré-tokenização do GPT-2 original, vocabulário de 8192 tokens. Funciona. Só que demora.",{"type":21,"tag":29,"props":52,"children":54},{"id":53},"o-problema-na-prática",[55],{"type":27,"value":56},"O problema na prática",{"type":21,"tag":36,"props":58,"children":59},{},[60],{"type":27,"value":61},"Antes de treinar o modelo, preciso encodar o corpus inteiro: pegar todos os textos do JSONL e converter em sequências de token IDs. O corpus tem ~1800 artigos da Wikipedia, ~27 milhões de caracteres.",{"type":21,"tag":36,"props":63,"children":64},{},[65],{"type":27,"value":66},"100 artigos levavam 326 segundos em Python. O corpus inteiro levaria mais de uma hora. E toda vez que eu quisesse trocar o corpus (e eu pretendo treinar com vários), ia ter que esperar de novo.",{"type":21,"tag":36,"props":68,"children":69},{},[70,72,79],{"type":27,"value":71},"O gargalo tem nome: ",{"type":21,"tag":73,"props":74,"children":76},"code",{"className":75},[],[77],{"type":27,"value":78},"_apply_merges",{"type":27,"value":80},". Pra cada palavra do texto, o tokenizer aplica ~7900 merges em sequência. Cada merge varre a lista de tokens procurando pares adjacentes pra fundir. Loop dentro de loop, multiplicado por centenas de milhares de palavras.",{"type":21,"tag":36,"props":82,"children":83},{},[84,86,92,94,100],{"type":27,"value":85},"O que acontece por baixo do Python nesse loop é meio doloroso de pensar. Cada ",{"type":21,"tag":73,"props":87,"children":89},{"className":88},[],[90],{"type":27,"value":91},"tokens[j]",{"type":27,"value":93}," não é um acesso à memória. É uma indireção via ponteiro pra um objeto ",{"type":21,"tag":73,"props":95,"children":97},{"className":96},[],[98],{"type":27,"value":99},"int",{"type":27,"value":101}," que mora no heap. Cada iteração passa por resolução de referências, type checking dinâmico, e o garbage collector tá ali rondando. São milhões de iterações, e cada uma carrega todo esse overhead.",{"type":21,"tag":29,"props":103,"children":105},{"id":104},"o-que-muda-em-c",[106],{"type":27,"value":107},"O que muda em C",{"type":21,"tag":36,"props":109,"children":110},{},[111,113,118,120,125],{"type":27,"value":112},"Em C, o array de tokens é um bloco contíguo de ",{"type":21,"tag":73,"props":114,"children":116},{"className":115},[],[117],{"type":27,"value":99},{"type":27,"value":119},". Acessar ",{"type":21,"tag":73,"props":121,"children":123},{"className":122},[],[124],{"type":27,"value":91},{"type":27,"value":126}," é somar um offset num ponteiro. Acabou. Sem interpretador, sem type checking em runtime, sem GC, sem objetos. O compilador ainda otimiza por cima: desenrola loops, aloca registradores, dá hints de branch prediction. Nada disso existe no mundo Python.",{"type":21,"tag":36,"props":128,"children":129},{},[130],{"type":27,"value":131},"Eu gosto de Python. É minha linguagem do dia a dia. Mas pra um loop apertado varrendo arrays de inteiros milhões de vezes, a diferença entre interpretado e compilado aparece. E não é sutíl.",{"type":21,"tag":36,"props":133,"children":134},{},[135,137,143,145,151],{"type":27,"value":136},"A implementação em C ficou com umas 80 linhas. Duas funções: ",{"type":21,"tag":73,"props":138,"children":140},{"className":139},[],[141],{"type":27,"value":142},"apply_merges",{"type":27,"value":144}," (um chunk) e ",{"type":21,"tag":73,"props":146,"children":148},{"className":147},[],[149],{"type":27,"value":150},"apply_merges_batch",{"type":27,"value":152}," (vários de uma vez, pra amortizar o custo das chamadas via ctypes). O algoritmo é o mesmo do Python, linha por linha. A diferença é toda na execução.",{"type":21,"tag":29,"props":154,"children":156},{"id":155},"como-ficou",[157],{"type":27,"value":158},"Como ficou",{"type":21,"tag":36,"props":160,"children":161},{},[162,164,170],{"type":27,"value":163},"Criei um módulo C e um wrapper Python com ctypes. O ",{"type":21,"tag":73,"props":165,"children":167},{"className":166},[],[168],{"type":27,"value":169},".c",{"type":27,"value":171}," compila sozinho na primeira importação. Se o gcc não estiver disponível, volta pro Python puro sem quebrar nada.",{"type":21,"tag":36,"props":173,"children":174},{},[175],{"type":27,"value":176},"Quando chamo o tokenizer na hora de treinar, se o backend C carregou, usa ele. Senão, usa a implementação Python de sempre. O resto do tokenizer (regex, special tokens, decode) continua em Python porque não são gargalo.",{"type":21,"tag":36,"props":178,"children":179},{},[180],{"type":27,"value":181},"Benchmark com 100 artigos (1.5M chars):",{"type":21,"tag":183,"props":184,"children":185},"table",{},[186,210],{"type":21,"tag":187,"props":188,"children":189},"thead",{},[190],{"type":21,"tag":191,"props":192,"children":193},"tr",{},[194,200,205],{"type":21,"tag":195,"props":196,"children":197},"th",{},[198],{"type":27,"value":199},"Backend",{"type":21,"tag":195,"props":201,"children":202},{},[203],{"type":27,"value":204},"Tempo",{"type":21,"tag":195,"props":206,"children":207},{},[208],{"type":27,"value":209},"Tokens",{"type":21,"tag":211,"props":212,"children":213},"tbody",{},[214,232],{"type":21,"tag":191,"props":215,"children":216},{},[217,222,227],{"type":21,"tag":218,"props":219,"children":220},"td",{},[221],{"type":27,"value":15},{"type":21,"tag":218,"props":223,"children":224},{},[225],{"type":27,"value":226},"326.2s",{"type":21,"tag":218,"props":228,"children":229},{},[230],{"type":27,"value":231},"344,620",{"type":21,"tag":191,"props":233,"children":234},{},[235,239,244],{"type":21,"tag":218,"props":236,"children":237},{},[238],{"type":27,"value":16},{"type":21,"tag":218,"props":240,"children":241},{},[242],{"type":27,"value":243},"3.6s",{"type":21,"tag":218,"props":245,"children":246},{},[247],{"type":27,"value":231},{"type":21,"tag":36,"props":249,"children":250},{},[251],{"type":27,"value":252},"89.5x mais rápido. Mesma saída, token por token. O corpus inteiro (~27M chars) agora encoda em cerca de 1 minuto.",{"type":21,"tag":29,"props":254,"children":256},{"id":255},"valeu-o-trabalho",[257],{"type":27,"value":258},"Valeu o trabalho?",{"type":21,"tag":36,"props":260,"children":261},{},[262,264,270],{"type":27,"value":263},"Sim e não. Eu já tinha um cache em disco: encoda uma vez, salva como ",{"type":21,"tag":73,"props":265,"children":267},{"className":266},[],[268],{"type":27,"value":269},".pt",{"type":27,"value":271},", e depois carrega direto. Isso resolve se você roda o mesmo corpus sempre. Mas eu quero testar corpus diferentes, e pra cada corpus novo, o primeiro encoding levava mais de uma hora. Agora leva 1 minuto.",{"type":21,"tag":36,"props":273,"children":274},{},[275,277,283],{"type":27,"value":276},"Escrever o módulo C não foi um projeto grande. Foram umas 80 linhas de C e umas 80 de wrapper. O algoritmo já existia em Python, era traduzir pra ",{"type":21,"tag":73,"props":278,"children":280},{"className":279},[],[281],{"type":27,"value":282},"int*",{"type":27,"value":284}," e expor via ctypes.",{"type":21,"tag":36,"props":286,"children":287},{},[288,290,296],{"type":27,"value":289},"Se o corpus crescer pra GBs, dá pra jogar OpenMP. Os chunks são independentes, então um ",{"type":21,"tag":73,"props":291,"children":293},{"className":292},[],[294],{"type":27,"value":295},"#pragma omp parallel for",{"type":27,"value":297}," no batch cairia sem atrito. Mas 89x single-threaded tá mais que suficiente por enquanto.",{"type":21,"tag":36,"props":299,"children":300},{},[301],{"type":27,"value":302},"No fim, o que me fez pensar foi perceber que \"encodar o texto\" não é uma etapa burocrática. São milhões de caracteres passando por milhares de merges, e cada merge é um loop sobre a sequência inteira. Em Python, cada iteração desse loop carrega o peso do interpretador. Em C, é uma soma de ponteiro. Multiplicado por uns bilhões de iterações, isso vira a diferença entre 5 minutos e 3 segundos.",{"title":9,"searchDepth":304,"depth":304,"links":305},2,[306,307,308,309,310],{"id":31,"depth":304,"text":34},{"id":53,"depth":304,"text":56},{"id":104,"depth":304,"text":107},{"id":155,"depth":304,"text":158},{"id":255,"depth":304,"text":258},"markdown","content:blog:tokenizer-c.md","content","blog/tokenizer-c.md","blog/tokenizer-c","md",{"_path":318,"_dir":7,"_draft":8,"_partial":8,"_locale":9,"title":319,"description":320,"date":321,"tags":322,"externalUrl":326,"body":327,"_type":311,"_id":331,"_source":313,"_file":332,"_stem":333,"_extension":316},"/blog/eigenvectors-eigenvalues-pca","From Eigenvectors and Eigenvalues to PCA (Principal Component Analysis)","A walkthrough from the mathematical foundations of eigenvectors and eigenvalues to their practical application in Principal Component Analysis.","2023-08-10",[323,324,325],"math","machine-learning","pca","https://medium.com/@hnrqpdr.sc/from-eigenvectors-and-eigenvalues-to-pca-principal-component-analysis-07946f3836db",{"type":18,"children":328,"toc":329},[],{"title":9,"searchDepth":304,"depth":304,"links":330},[],"content:blog:eigenvectors-eigenvalues-pca.md","blog/eigenvectors-eigenvalues-pca.md","blog/eigenvectors-eigenvalues-pca",{"_path":335,"_dir":7,"_draft":8,"_partial":8,"_locale":9,"title":336,"description":337,"date":338,"tags":339,"externalUrl":343,"body":344,"_type":311,"_id":348,"_source":313,"_file":349,"_stem":350,"_extension":316},"/blog/computacao-alto-desempenho-gpus-cuda","Computação de Alto Desempenho em GPUs: Entendendo o Poder da API CUDA","Uma introdução à computação de alto desempenho com GPUs, explorando os fundamentos e o poder da API CUDA para processamento paralelo.","2023-06-15",[340,341,342],"cuda","gpu","hpc","https://medium.com/@hnrqpdr.sc/computa%C3%A7%C3%A3o-de-alto-desempenho-em-gpus-entendendo-o-poder-da-api-cuda-554398bc44e1",{"type":18,"children":345,"toc":346},[],{"title":9,"searchDepth":304,"depth":304,"links":347},[],"content:blog:computacao-alto-desempenho-gpus-cuda.md","blog/computacao-alto-desempenho-gpus-cuda.md","blog/computacao-alto-desempenho-gpus-cuda",{"_path":352,"_dir":7,"_draft":8,"_partial":8,"_locale":9,"title":353,"description":354,"date":355,"tags":356,"externalUrl":359,"body":360,"_type":311,"_id":364,"_source":313,"_file":365,"_stem":366,"_extension":316},"/blog/custom-events-vue3-typescript","How to Add Custom Events in Vue 3 Using TypeScript","A tutorial on creating and managing custom events in Vue 3 applications using TypeScript - covering event registries, listeners, and dispatching across components.","2023-03-27",[357,358],"vue","typescript","https://dev.to/hnrqpdr/how-to-add-custom-events-in-vue-3-using-typescript-5931",{"type":18,"children":361,"toc":362},[],{"title":9,"searchDepth":304,"depth":304,"links":363},[],"content:blog:custom-events-vue3-typescript.md","blog/custom-events-vue3-typescript.md","blog/custom-events-vue3-typescript",1773882640109]