Mangaba AI · Relatório técnico

Mangaba-Nano: treinando do zero um modelo de linguagem de 1 milhão de parâmetros em português

Dados públicos, tokenizer próprio, otimizador Muon, controles honestos e uma noite inteira de GPU. O que funcionou, o que não funcionou e quanto cada técnica rendeu.

Outubro de 2026 Mangaba-Nano-1M Licença Apache-2.0

Resumo

1,41Mparâmetros (0,89M fora do embedding)
2,85Btokens de treino em PT-BR (v1)
1,282bits por byte na validação
5,6 MBem float32 (2,9 MB em GGUF F16)
  1. Por que um modelo minúsculo
  2. Dados e tokenizer
  3. Arquitetura
  4. Receita de treino
  5. Benchmarks e controles
  6. Ablações
  7. GPU compartilhada com produção
  8. Versão 2: escala e destilação
  9. Lições
  10. Como reproduzir

1. Por que um modelo minúsculo

Modelos de bilhões de parâmetros dominam a conversa, mas há um espaço que eles não ocupam: dispositivos sem GPU, navegadores, microcontroladores e tarefas estreitas que precisam responder em microssegundos. Um modelo de 1M de parâmetros cabe em 3 MB e roda a cerca de 1.000 tokens por segundo num notebook comum.

Ele também é um laboratório barato. Cada experimento leva minutos, então dá para fazer o que raramente se faz com modelos grandes: comparar receitas lado a lado, com os mesmos dados e o mesmo orçamento, e medir quanto cada escolha rende de verdade.

Não é um assistente. Com 1M de parâmetros, o modelo aprende ortografia, morfologia e sintaxe do português, e só um pouco de semântica. O texto sai gramatical, mas perde o fio em poucas frases.

2. Dados e tokenizer

Usamos só dados públicos em português, do GigaVerbo-v2:

Na v1, usamos três arquivos de cada fonte: 2,9 bilhões de tokens, sendo 1,02B sintéticos e 1,88B da web. Descartamos documentos com menos de 200 caracteres e normalizamos tudo em Unicode NFC. Antes do treino, separamos 0,5% dos documentos (14 milhões de tokens) para validação; eles nunca entram no treino.

Tokenizer do zero

Num modelo de 1M de parâmetros, o vocabulário é uma decisão de orçamento. Cada token custa uma linha da matriz de embedding: com 4.096 tokens e dimensão 128, só o embedding já consome 524 mil parâmetros, 37% do modelo. Treinamos um BPE byte-level de 4.096 tokens sobre uma amostra de 400 mil documentos do próprio corpus. Ele comprime de 3,4 a 3,7 caracteres por token em português: um pouco menos que vocabulários grandes, mas ainda razoável.

3. Arquitetura

É um decoder no desenho atual da indústria, o mesmo de famílias como Qwen3 e Llama. Usamos a classe Qwen3ForCausalLM do Transformers só como implementação desse desenho: os pesos são inicializados aleatoriamente e nenhum peso da Qwen é usado.

ComponenteEscolhaPor quê
Camadas × dimensão6 × 128, FFN 256Venceu as alternativas na ablação (seção 6)
Atenção4 cabeças de query, 2 de key/value (GQA), head_dim 32Menos parâmetros na atenção
QK-normRMSNorm em query e keyEvita picos de perda
NormalizaçãoRMSNorm antes de cada blocoTreino mais estável
PosiçãoRoPE, θ = 10.000, contexto 512Sem tabela de posições para aprender
Feed-forwardSwiGLUMelhor por parâmetro que GELU
EmbeddingsAmarrados entre entrada e saídaPoupa 524 mil parâmetros

4. Receita de treino

Otimizador Muon

Nas matrizes internas usamos o Muon: a cada passo, a atualização com momentum de Nesterov é ortogonalizada por 5 iterações de Newton-Schulz, de modo que todas as direções da matriz avançam com a mesma intensidade. Embeddings e normas ficam com o AdamW. Uma varredura curta escolheu a taxa de aprendizado 0,015 para os dois.

Agenda Warmup-Stable-Decay

A taxa sobe por 2% dos passos, fica estável e cai nos últimos 25% numa curva 1−√t. Essa agenda permite parar a qualquer momento: basta fazer o resfriamento a partir do checkpoint estável, sem refazer o treino.

Execução

Usamos precisão mista bf16 e torch.compile. A cross-entropy fica dentro do grafo compilado, então o compilador funde a conversão para float32 com o softmax e não guarda os logits em fp32. Isso reduziu o pico de memória de mais de 680 MB para 326 MB. O lote é de 65.536 tokens por passo, com 43.487 passos e recorte de gradiente em 1,0. A vazão ficou em ~700 mil tokens por segundo.

5. Benchmarks e controles

Não existe outro modelo público de ~1M de parâmetros em português. Para ter uma comparação justa, treinamos dois controles do mesmo tamanho, com os mesmos 2,85B tokens e o mesmo tokenizer, usando a receita clássica (AdamW com agenda cosseno e LR escolhido por varredura):

Curvas de perda de validação: Mangaba-Nano-1M termina em 3,125; a mesma arquitetura com AdamW em 3,224; GPT-2 com AdamW em 3,471.
Perda de validação ao longo do treino. O Muon abre vantagem logo no início e a mantém até o fim. O GPT-2 teve uma instabilidade em ~0,5B tokens antes de se recuperar.

As métricas:

ModeloParâmetrosBPB GigaVerbo ↓BPB Wikipédia ↓CALAME-PT ↑Story Cloze ↑
Mangaba-Nano-1M1,41M1,2821,59317,1%56,2%
Controle: mesma arquitetura + AdamW1,41M1,3231,63516,2%55,9%
Controle: GPT-2 + AdamW1,42M1,4251,73611,7%56,7%
TinyStories-1M (inglês)3,75M6,4076,2730,0%50,0%
Tucano-160m (referência)162M0,9290,95545,6%61,7%

Decompondo o ganho em bits por byte na validação:

No Story Cloze os três modelos de ~1M empatam: com 1.511 histórias, a margem de erro é de ±2,5 pontos. Só o Tucano-160m, mais de 100 vezes maior, se destaca. O TinyStories-1M, o modelo público mais próximo em tamanho, foi treinado em inglês e não entende português; entra apenas como referência de porte.

6. Ablações

Com as GPUs dedicadas, rodamos de 10 a 12 treinos de 300M tokens ao mesmo tempo em cada GPU. Todos usam a mesma ordem de amostragem dos dados, para que as diferenças venham só da configuração testada.

Forma: mais fundo ou mais largo?

Trabalhos recentes com modelos de 125M a 1B parâmetros mostram que modelos mais fundos e estreitos rendem mais por parâmetro. Testamos cinco formas com o mesmo orçamento de ~1,4M e duas taxas de aprendizado em cada uma.

Ablação de forma: d128 com 8 camadas 3,242; d128 com 6 camadas 3,247; d144 com 4 camadas 3,253; d96 com 12 camadas 3,260; d96 com 10 camadas 3,273.
Melhor resultado de cada forma após 300M tokens. O eixo começa em 3,20 para mostrar diferenças pequenas.

Nessa escala, a forma quase não importa: as cinco ficam a menos de 0,03 de distância. As formas mais fundas e estreitas (d96) perderam, provavelmente porque, com dimensão 96, cada camada fica estreita demais para representar bem o vocabulário. A forma d128 com 8 camadas empata com a da v1, mas é ~35% mais lenta; com o mesmo tempo de GPU, a v1 vê mais tokens. Mantivemos a forma da v1.

Uma sexta forma (d112) foi descartada por desempenho: com head_dim 28, que não é múltiplo de 8, a atenção cai num kernel 4 vezes mais lento.

Mistura de dados

Mistura de dados: com 15% de texto sintético, Wikipédia 1,651 e CALAME 13,5%; com 85%, Wikipédia 1,718 e CALAME 10,9%.
Proporção de lotes vindos do texto sintético, na forma da v1.

Entre 15% e 60% de texto sintético, a perda na validação empata. Mas menos sintético é melhor fora do domínio: Wikipédia e CALAME preferem 15%. Com 85%, tudo piora. Texto sintético simplificado ajuda modelos minúsculos até certo ponto; depois, falta variedade.

7. GPU compartilhada com produção

As duas L40S deste projeto, num datacenter no Brasil, também servem um modelo de 122B de parâmetros em produção, que reserva 95% da memória de cada GPU. A v1 foi treinada nos ~2,5 GB que sobravam. Um teto de memória por processo (set_per_process_memory_fraction) garante que o treino nunca invada a reserva do modelo de produção.

Memória não foi o problema; computação foi. Medindo direito, com o treino pausado e retomado no mesmo minuto, um único treino ao lado do modelo de produção aumentou o tempo de resposta de 2,6 s para 4,5 s; dois treinos, para 7,2 s. Como o modelo de produção divide as duas GPUs, qualquer carga extra em uma delas atrasa tudo. Nossa primeira medição tinha indicado "sem impacto", mas pausava o processo errado.

A solução foi uma janela noturna dedicada: o modelo de produção é desligado, o tráfego vai para um modelo reserva em CPU, e as GPUs ficam 100% para treino até de manhã. A v1 usou menos de 2% de uma GPU (~6 TFLOPS); a v2 usa as duas a 98–100%.

8. Versão 2: escala e destilação

A v2 manteve a arquitetura e mudou três coisas:

  1. Mais dados: mais 20 arquivos do edu_high e mais 12 do sintético. O total passou de 2,9B para 21,6B tokens, com ~24% de sintético, a proporção natural dos dados e dentro da faixa que funcionou na ablação.
  2. Um professor treinado do zero: o Mangaba-Base-91M (dimensão 768, 14 camadas, 91,25M de parâmetros, mesmo tokenizer), treinado em 2,52B tokens em 4h52 numa L40S. Ele também começou de pesos aleatórios.
  3. Destilação de logits: o aluno de 1,4M aprende a distribuição completa de probabilidades do professor para o próximo token. A perda combina 30% de cross-entropy com 70% de divergência KL. Em 4 horas, o aluno viu 4,9B tokens a 345 mil tokens/s; a maior parte do custo é o professor.

Para medir o efeito da destilação, um controle sem professor rodou ao mesmo tempo na outra GPU por ~8 horas (33,8B tokens).

Curvas de validação da v2: o destilado acompanha a v1 e termina em 3,138; o controle instabiliza entre 8 e 26 bilhões de tokens e termina em 3,335.
Perda de validação da v2 em escala logarítmica de tokens. O controle instabilizou entre 8B e 26B tokens e só se recuperou no resfriamento.
ModeloTokensBPB GigaVerbo ↓BPB Wikipédia ↓CALAME-PT ↑Story Cloze ↑
Nano-1M v12,85B1,2821,59317,1%56,2%
Nano-1M v2 destilado4,9B1,2961,56917,8%55,4%
Nano-1M v2 controle (instabilizou)33,8B1,3701,67411,8%58,0%
Mangaba-Base-91M (professor)2,5B0,8861,09145,7%57,8%
Tucano-160m (referência)—0,9290,95545,6%61,7%

O que os números dizem

9. Lições

  1. Varreduras curtas de LR enganam. No controle GPT-2, a taxa 1e-2 venceu em 40M tokens e divergiu no passo ~2.900 do treino longo. Escolher a taxa vencedora da varredura curta e abaixá-la um pouco é mais seguro.
    Perda de treino do controle GPT-2: com LR 1e-2 a perda salta de 3,8 para 6,5 no passo 2.850; com 3e-3 segue estável.
  2. Tokenize contexto e resposta juntos na avaliação. Nossa primeira medição do CALAME-PT deu 0% ao Tucano-160m. Tokenizers SentencePiece tratam o espaço inicial de " palavra" de forma diferente quando a palavra aparece sozinha. Tokenizando o texto completo e recortando a resposta, o resultado correto é 45,6%.
  3. Use head_dim múltiplo de 8. Caso contrário, a atenção cai num kernel lento, e o custo pode superar qualquer ganho da forma.
  4. Meça o impacto na produção pausando o processo certo. Em comandos remotos, pgrep -f casa com o próprio shell que o executa.
  5. Em treino longo, o Muon precisa de weight decay. O controle da v2 usou a receita da v1 sem weight decay. Ela era estável em 2,85B tokens, mas instabilizou depois de ~8B, com a perda subindo e ficando presa por 15B tokens. Trabalhos que usam o Muon em escala (Moonlight) adicionam weight decay justamente para conter o crescimento dos pesos. Treinos curtos não revelam esse problema.
  6. Aquecimento proporcional engana em treinos longos. 2% de warmup é razoável em 3B tokens, mas vira 0,67B tokens num treino de 34B. É melhor fixar o aquecimento em passos ou tokens absolutos.
  7. Fixe o critério de sucesso antes de ver os números. Combinamos que a v2 só substituiria a v1 se fosse melhor. O resultado misto, então, não é publicado como "melhoria".
  8. Em 1M de parâmetros, o embedding é o maior bloco. Com vocabulário de 4.096, ele ocupa 37% do modelo. Testar vocabulários de 2.048 e 8.192 é o próximo experimento óbvio.

10. Como reproduzir

Tudo está no repositório do modelo: prep.py (tokenizer e tokenização), train.py (treino da v1 e dos controles), bench.py (a suíte de benchmarks), eval_bpb.py e make_card.py. A v2 usa train2.py (dados em vários arquivos, mistura configurável, orçamento por relógio e destilação). O professor está em mangaba-ai/Mangaba-Base-91M e tem relatório próprio.

from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("mangaba-ai/Mangaba-Nano-1M")
model = AutoModelForCausalLM.from_pretrained("mangaba-ai/Mangaba-Nano-1M")
ids = tok("A fotossíntese é o processo", return_tensors="pt").input_ids
print(tok.decode(model.generate(ids, max_new_tokens=60, do_sample=True, temperature=0.7)[0]))

Com o llama.cpp: llama-completion -m Mangaba-Nano-1M-F16.gguf -p "O Brasil é um país" -n 60.

Limitações