[{"data":1,"prerenderedAt":366},["ShallowReactive",2],{"all-posts":3},[4,316,333,350],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"date":11,"tags":12,"body":16,"_type":310,"_id":311,"_source":312,"_file":313,"_stem":314,"_extension":315},"/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",[13,14,15],"AI","Python","C",{"type":17,"children":18,"toc":302},"root",[19,27,34,40,45,50,56,61,66,80,101,107,126,131,152,158,171,176,181,247,252,258,271,284,297],{"type":20,"tag":21,"props":22,"children":24},"element","h1",{"id":23},"por-que-o-tokenizer-do-meu-gpt-2-caseiro-demorava-5-minutos-e-como-c-resolveu-em-3-segundos",[25],{"type":26,"value":9},"text",{"type":20,"tag":28,"props":29,"children":31},"h2",{"id":30},"tokenizer-o-tradutor-que-ninguém-presta-atenção",[32],{"type":26,"value":33},"Tokenizer: o tradutor que ninguém presta atenção",{"type":20,"tag":35,"props":36,"children":37},"p",{},[38],{"type":26,"value":39},"Modelos de linguagem não leem texto. Leem números. Alguém precisa fazer essa conversão, e esse alguém é o tokenizer.",{"type":20,"tag":35,"props":41,"children":42},{},[43],{"type":26,"value":44},"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":20,"tag":35,"props":46,"children":47},{},[48],{"type":26,"value":49},"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":20,"tag":28,"props":51,"children":53},{"id":52},"o-problema-na-prática",[54],{"type":26,"value":55},"O problema na prática",{"type":20,"tag":35,"props":57,"children":58},{},[59],{"type":26,"value":60},"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":20,"tag":35,"props":62,"children":63},{},[64],{"type":26,"value":65},"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":20,"tag":35,"props":67,"children":68},{},[69,71,78],{"type":26,"value":70},"O gargalo tem nome: ",{"type":20,"tag":72,"props":73,"children":75},"code",{"className":74},[],[76],{"type":26,"value":77},"_apply_merges",{"type":26,"value":79},". 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":20,"tag":35,"props":81,"children":82},{},[83,85,91,93,99],{"type":26,"value":84},"O que acontece por baixo do Python nesse loop é meio doloroso de pensar. Cada ",{"type":20,"tag":72,"props":86,"children":88},{"className":87},[],[89],{"type":26,"value":90},"tokens[j]",{"type":26,"value":92}," não é um acesso à memória. É uma indireção via ponteiro pra um objeto ",{"type":20,"tag":72,"props":94,"children":96},{"className":95},[],[97],{"type":26,"value":98},"int",{"type":26,"value":100}," 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":20,"tag":28,"props":102,"children":104},{"id":103},"o-que-muda-em-c",[105],{"type":26,"value":106},"O que muda em C",{"type":20,"tag":35,"props":108,"children":109},{},[110,112,117,119,124],{"type":26,"value":111},"Em C, o array de tokens é um bloco contíguo de ",{"type":20,"tag":72,"props":113,"children":115},{"className":114},[],[116],{"type":26,"value":98},{"type":26,"value":118},". Acessar ",{"type":20,"tag":72,"props":120,"children":122},{"className":121},[],[123],{"type":26,"value":90},{"type":26,"value":125}," é 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":20,"tag":35,"props":127,"children":128},{},[129],{"type":26,"value":130},"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":20,"tag":35,"props":132,"children":133},{},[134,136,142,144,150],{"type":26,"value":135},"A implementação em C ficou com umas 80 linhas. Duas funções: ",{"type":20,"tag":72,"props":137,"children":139},{"className":138},[],[140],{"type":26,"value":141},"apply_merges",{"type":26,"value":143}," (um chunk) e ",{"type":20,"tag":72,"props":145,"children":147},{"className":146},[],[148],{"type":26,"value":149},"apply_merges_batch",{"type":26,"value":151}," (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":20,"tag":28,"props":153,"children":155},{"id":154},"como-ficou",[156],{"type":26,"value":157},"Como ficou",{"type":20,"tag":35,"props":159,"children":160},{},[161,163,169],{"type":26,"value":162},"Criei um módulo C e um wrapper Python com ctypes. O ",{"type":20,"tag":72,"props":164,"children":166},{"className":165},[],[167],{"type":26,"value":168},".c",{"type":26,"value":170}," compila sozinho na primeira importação. Se o gcc não estiver disponível, volta pro Python puro sem quebrar nada.",{"type":20,"tag":35,"props":172,"children":173},{},[174],{"type":26,"value":175},"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":20,"tag":35,"props":177,"children":178},{},[179],{"type":26,"value":180},"Benchmark com 100 artigos (1.5M chars):",{"type":20,"tag":182,"props":183,"children":184},"table",{},[185,209],{"type":20,"tag":186,"props":187,"children":188},"thead",{},[189],{"type":20,"tag":190,"props":191,"children":192},"tr",{},[193,199,204],{"type":20,"tag":194,"props":195,"children":196},"th",{},[197],{"type":26,"value":198},"Backend",{"type":20,"tag":194,"props":200,"children":201},{},[202],{"type":26,"value":203},"Tempo",{"type":20,"tag":194,"props":205,"children":206},{},[207],{"type":26,"value":208},"Tokens",{"type":20,"tag":210,"props":211,"children":212},"tbody",{},[213,231],{"type":20,"tag":190,"props":214,"children":215},{},[216,221,226],{"type":20,"tag":217,"props":218,"children":219},"td",{},[220],{"type":26,"value":14},{"type":20,"tag":217,"props":222,"children":223},{},[224],{"type":26,"value":225},"326.2s",{"type":20,"tag":217,"props":227,"children":228},{},[229],{"type":26,"value":230},"344,620",{"type":20,"tag":190,"props":232,"children":233},{},[234,238,243],{"type":20,"tag":217,"props":235,"children":236},{},[237],{"type":26,"value":15},{"type":20,"tag":217,"props":239,"children":240},{},[241],{"type":26,"value":242},"3.6s",{"type":20,"tag":217,"props":244,"children":245},{},[246],{"type":26,"value":230},{"type":20,"tag":35,"props":248,"children":249},{},[250],{"type":26,"value":251},"89.5x mais rápido. Mesma saída, token por token. O corpus inteiro (~27M chars) agora encoda em cerca de 1 minuto.",{"type":20,"tag":28,"props":253,"children":255},{"id":254},"valeu-o-trabalho",[256],{"type":26,"value":257},"Valeu o trabalho?",{"type":20,"tag":35,"props":259,"children":260},{},[261,263,269],{"type":26,"value":262},"Sim e não. Eu já tinha um cache em disco: encoda uma vez, salva como ",{"type":20,"tag":72,"props":264,"children":266},{"className":265},[],[267],{"type":26,"value":268},".pt",{"type":26,"value":270},", 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":20,"tag":35,"props":272,"children":273},{},[274,276,282],{"type":26,"value":275},"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":20,"tag":72,"props":277,"children":279},{"className":278},[],[280],{"type":26,"value":281},"int*",{"type":26,"value":283}," e expor via ctypes.",{"type":20,"tag":35,"props":285,"children":286},{},[287,289,295],{"type":26,"value":288},"Se o corpus crescer pra GBs, dá pra jogar OpenMP. Os chunks são independentes, então um ",{"type":20,"tag":72,"props":290,"children":292},{"className":291},[],[293],{"type":26,"value":294},"#pragma omp parallel for",{"type":26,"value":296}," no batch cairia sem atrito. Mas 89x single-threaded tá mais que suficiente por enquanto.",{"type":20,"tag":35,"props":298,"children":299},{},[300],{"type":26,"value":301},"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":8,"searchDepth":303,"depth":303,"links":304},2,[305,306,307,308,309],{"id":30,"depth":303,"text":33},{"id":52,"depth":303,"text":55},{"id":103,"depth":303,"text":106},{"id":154,"depth":303,"text":157},{"id":254,"depth":303,"text":257},"markdown","content:blog:tokenizer-c.md","content","blog/tokenizer-c.md","blog/tokenizer-c","md",{"_path":317,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":318,"description":319,"date":320,"tags":321,"externalUrl":325,"body":326,"_type":310,"_id":330,"_source":312,"_file":331,"_stem":332,"_extension":315},"/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",[322,323,324],"math","machine-learning","pca","https://medium.com/@hnrqpdr.sc/from-eigenvectors-and-eigenvalues-to-pca-principal-component-analysis-07946f3836db",{"type":17,"children":327,"toc":328},[],{"title":8,"searchDepth":303,"depth":303,"links":329},[],"content:blog:eigenvectors-eigenvalues-pca.md","blog/eigenvectors-eigenvalues-pca.md","blog/eigenvectors-eigenvalues-pca",{"_path":334,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":335,"description":336,"date":337,"tags":338,"externalUrl":342,"body":343,"_type":310,"_id":347,"_source":312,"_file":348,"_stem":349,"_extension":315},"/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",[339,340,341],"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":17,"children":344,"toc":345},[],{"title":8,"searchDepth":303,"depth":303,"links":346},[],"content:blog:computacao-alto-desempenho-gpus-cuda.md","blog/computacao-alto-desempenho-gpus-cuda.md","blog/computacao-alto-desempenho-gpus-cuda",{"_path":351,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":352,"description":353,"date":354,"tags":355,"externalUrl":358,"body":359,"_type":310,"_id":363,"_source":312,"_file":364,"_stem":365,"_extension":315},"/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",[356,357],"vue","typescript","https://dev.to/hnrqpdr/how-to-add-custom-events-in-vue-3-using-typescript-5931",{"type":17,"children":360,"toc":361},[],{"title":8,"searchDepth":303,"depth":303,"links":362},[],"content:blog:custom-events-vue3-typescript.md","blog/custom-events-vue3-typescript.md","blog/custom-events-vue3-typescript",1773882640316]