Mambarim-110M: como desenvolvi um modelo Mamba em português
Eu desenvolvi o Mambarim-110M como um modelo-base autorregressivo para português, publicado em 11 de março de 2024 e implementado como MambaForCausalLM. Seu interesse não está apenas no porte reduzido: ele troca integralmente a autoatenção por blocos Mamba, uma família de modelos de espaço de estados seletivos, ou selective state-space models (SSMs), capaz de processar sequências em tempo linear no comprimento [1]. Publiquei pesos no Hugging Face, código e ativos no GitHub, um notebook de treinamento e resultados do Open Portuguese LLM Leaderboard [4, 5, 8, 9]. O README do repositório também expõe o notebook em Colab e o dashboard de treinamento no W&B que eu usei como acompanhamento de perda, WER e checkpoints [4].
Na publicação inicial, chamei o modelo de 110M e estimei 119.930.880 parâmetros, mas o checkpoint que gerei possui 69.836.544 parâmetros. Também publiquei a média 14,16 na tabela e preservei a média não normalizada 18,013 no resultado bruto da avaliação. A seguir, explico de onde vieram essas diferenças e qual valor uso em cada contexto.
Ficha técnica
| Item | Valor verificável |
|---|---|
| Tarefa | modelagem causal, previsão do próximo token |
| Classe | MambaForCausalLM |
| Idioma | português |
| Vocabulário | 32.000 tokens |
| Dimensão oculta | 768 |
| Blocos Mamba | 12 |
| Expansão interna | 2, produzindo dimensão 1.536 |
| Estado SSM | 16 por canal |
| Convolução causal local | kernel 4 |
| Passo temporal | rank 48, intervalo inicial de 0,001 a 0,1 |
| Ativação e normalização | SiLU e RMSNorm, epsilon 1e-5 |
| Pesos | float32, em formato SafeTensors |
| Parâmetros no checkpoint | 69.836.544 |
| Número declarado no card | 119.930.880, uma estimativa incompatível com o checkpoint |
| Contexto de treinamento e avaliação | 2.048 tokens |
| Dados declarados | Pt-Corpus-Instruct tokenizado, aproximadamente 6,2 bilhões de tokens |
| Etapas declaradas | 758.423 |
| Licença declarada para os pesos | CC BY 4.0 |
Por que SSM em vez de atenção?
Em um Transformer causal, cada posição compara sua consulta com chaves de todas as posições anteriores. Isso oferece roteamento explícito entre tokens e explica boa parte da eficácia da atenção, mas a matriz de escores cresce com o quadrado do comprimento L. Para dimensão d, a parte dominante da atenção tem custo e armazenamento teóricos:
Kernels como FlashAttention reduzem leituras e evitam materializar toda a matriz na memória de alta largura de banda, mas não mudam o número quadrático de interações. Na geração, o cache KV impede recalcular o passado, porém cresce linearmente com o contexto e precisa ser lido a cada novo token [2].
Um SSM segue outra estratégia: comprime o prefixo em um estado latente de tamanho fixo. Mamba preserva a recorrência eficiente e adiciona seleção dependente do conteúdo, para decidir o que inserir, manter ou expor. Com dimensão de estado N, o selective scan custa:
Isso não significa que qualquer Mamba será automaticamente mais rápido. Projeções densas, largura, precisão, batch, kernels CUDA e comprimento alteram o ponto de equilíbrio. O artigo original reporta até 5 vezes o throughput de geração de Transformers comparáveis e escala linear até sequências muito longas, mas esses números pertencem aos modelos e ao hardware daquele estudo; não são medições do Mambarim [1].
Renderizando diagrama...
Do sistema contínuo ao SSM seletivo
O ponto de partida é um sistema linear contínuo que transforma uma entrada escalar ou vetorial em saída por um estado oculto [1]:
Como texto é discreto, o sistema é discretizado. Com zero-order hold e passo Δ, obtêm-se:
Em SSMs lineares invariantes no tempo, Δ, A, B e C permanecem iguais em todas as posições. Essa invariância permite expressar a recorrência como convolução global, mas limita a resposta ao conteúdo: o mesmo mecanismo precisa tratar uma palavra informativa e um separador irrelevante. Mamba mantém A estruturada, mas torna três parâmetros funções da entrada:
Um Δ_t pequeno faz a dinâmica mudar lentamente e conservar memória; um valor maior favorece atualização ou esquecimento. B_t controla como a entrada modifica o estado e C_t, o que desse estado é lido. Como os parâmetros agora variam por token, já não existe um único kernel convolucional. O algoritmo de selective scan recupera paralelismo por varredura associativa, fusão de operações e recomputação: carrega parâmetros da HBM, discretiza e atualiza estados na SRAM, grava apenas a saída e recompõe intermediários no backward. É uma otimização consciente da hierarquia de memória, não apenas uma redução assintótica [1].
No bloco completo, a entrada de 768 dimensões é projetada em dois ramos. Um ramo passa por convolução causal depthwise de largura 4, SiLU e SSM seletivo; o outro funciona como gate. A combinação é projetada de 1.536 para 768, somada à conexão residual e repetida 12 vezes. Não há cabeças de atenção nem MLP Transformer separado.
Arquitetura e contagem de parâmetros
Configurei o Mambarim com hidden_size=768, num_hidden_layers=12, state_size=16, expand=2, conv_kernel=4, time_step_rank=48, vocab_size=32000, sem bias nas projeções principais, com bias na convolução, resíduo em FP32 e cache habilitado [5]. No notebook, preservei as dimensões de cada bloco: in_proj: 768→3072, x_proj: 1536→80, dt_proj: 48→1536, out_proj: 1536→768 e RMSNorm [4].
A estimativa 119.930.880 que publiquei não corresponde à contagem do Mamba instanciado. Eu a calculei assim:
Na versão atual do notebook, explico que essa aproximação foi baseada em atenção e MLP de Transformer. Também contei lm_head separadamente, embora os pesos de saída estejam atados aos embeddings no checkpoint. A soma dos tensores reais é:
O checkpoint que publiquei soma exatamente 69.836.544 parâmetros. “110M” permanece como nome histórico e 119.930.880 como a estimativa inicial; para memória, FLOPs, comparação de porte e razão tokens/parâmetro, uso 69.836.544.
O “contexto 2.048” também exige precisão. Mamba não usa embeddings posicionais com um max_position_embeddings rígido, e o tokenizer publicado contém um sentinela enorme em model_max_length. O valor 2.048 vem do comprimento de todas as sequências de treinamento e do limite aplicado na avaliação. Extrapolar além dele é tecnicamente possível, mas ainda não publiquei uma avaliação de qualidade fora dessa distribuição.
Corpus, tokenizer e treinamento
O Pt-Corpus-Instruct concatena 10.564.643 amostras em português, nativas ou traduzidas, provenientes de Wikipedia, CulturaX, OSCAR, CC100 e conjuntos conversacionais/instrucionais, entre outros. A curadoria relata filtros inspirados no Gopher e um classificador BERTimbau de toxicidade; também alerta para dados pessoais, conteúdo nocivo, contaminação de idiomas e código corrompido por tradução automática [6, 7].
Usei a variante pré-tokenizada: 3.033.690 sequências de treino e 30.000 de teste, todas com 2.048 posições e campos input_ids, attention_mask e labels. As 3.033.690 sequências equivalem a 6.212.997.120 posições, origem da aproximação 6,2B tokens. Não retokenizei esse material no projeto, ou não preservei um registro de retokenização.
Usei o tokenizer de nicholasKluge/TeenyTinyLlama-460m: um LlamaTokenizer de 32.000 itens, carregado com use_fast=True. Os IDs especiais são <unk>=0, <s>=1, </s>=2 e <pad>=3; ele adiciona BOS, não adiciona EOS automaticamente e usa padding à direita [4, 5, 7]. Assim, trocar tokenizer torna os pesos incompatíveis mesmo que o novo vocabulário também tenha 32 mil itens.
Renderizando diagrama...
Registrei 758.423 etapas na README e no card, mas não preservei o log final que define essa contagem. Em agosto de 2025, acrescentei ao GitHub um notebook com Trainer, batch por dispositivo 16, acumulação 2, uma época, bf16, learning rate 5e-5, scheduler cosseno, avaliação e checkpoint a cada 1.000 passos, além de default_data_collator e W&B [4]. Essa receita posterior não descreve necessariamente os hiperparâmetros da execução de 2024.
A relação
não fecha com a receita posterior, cujo batch efetivo 32 produziria cerca de 94,8 mil atualizações em uma época. Isso sugere outra definição de “step” ou outra configuração original; não me permite concluir batch 4. Na primeira execução, não preservei publicamente a seed, o otimizador explicitamente fixado, o warmup, o clipping, o hardware, a duração, o consumo energético nem o checkpoint de retomada.
A intensidade de dados também depende de qual contagem se aceita:
Inferência com Transformers
Versões atuais do Transformers carregam Mamba pela API automática; a instrução antiga de instalar a branch main era necessária antes do lançamento 4.39. Os kernels de mamba-ssm e causal-conv1d podem acelerar GPUs compatíveis, mas devem ser comparados com o fallback no ambiente real [3].
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = "dominguesm/mambarim-110m"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.float32,
device_map="auto",
)
inputs = tokenizer("O Natal é uma", return_tensors="pt").to(model.device)
with torch.inference_mode():
output = model.generate(
**inputs,
do_sample=True,
temperature=0.8,
top_k=50,
top_p=0.85,
repetition_penalty=1.2,
max_new_tokens=80,
use_cache=True,
)
print(tokenizer.decode(output[0], skip_special_tokens=True))Na geração recorrente, o cache Mamba guarda estados da convolução e do SSM por camada, não chaves e valores de todos os tokens. O prefill ainda percorre o prompt inteiro; depois, cada token atualiza um estado fixo. Para resultados determinísticos, use do_sample=False; para amostragem reproduzível, fixe a seed e registre versões, GPU, dtype e parâmetros. Como é modelo-base, não há chat_template: prompts de conversa não recebem formatação automática.
Benchmark publicado e limites da comparação
Na README e no card, publiquei os valores percentuais abaixo, obtidos com o lm-evaluation-harness-pt [4, 5]. Escrevi “FAQNAD” na versão original; o nome correto é FaQuAD-NLI. Retiro daqui as linhas de outros modelos porque não preservei, para todas elas, revisões e protocolos pareados que sustentem uma comparação controlada.
| Modelo | Média | ENEM | BLUEX | OAB | ASSIN2 RTE | ASSIN2 STS | FaQuAD NLI | HateBR | PT Hate Speech | TweetSentBR | Arquitetura |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Mambarim-110M | 14,16 | 18,40 | 10,57 | 21,87 | 16,09 | 1,89 | 9,29 | 15,75 | 17,77 | 15,79 | Mamba |
Na execução de 18 de abril de 2024, usei FP16, uma GPU, batch 64, contexto máximo 2.048, 14.150 exemplos e quatro few-shots truncados; a avaliação durou 3.771,97 segundos. Usei acurácia em ENEM, BLUEX e OAB, F1 macro em RTE e classificações e Pearson em STS. O modelo não possuía template de chat [9].
Os resultados que obtive são baixos em termos absolutos. ENEM, com 18,40, fica abaixo dos 20% de acaso para cinco alternativas. OAB, com quatro alternativas, fica abaixo dos 25% de acaso. Não atribuo um único baseline aleatório ao BLUEX, cuja composição varia; além disso, a documentação atual do harness mudou sua métrica principal para F1 macro. Essa deriva de protocolo não deve ser sobreposta sem ressalva à execução de 2024, que usou acurácia. Os resultados baixos em F1 e Pearson não me permitem concluir robustez nessas tarefas; exposição a dados instrucionais durante pré-treino tampouco equivale a ajuste para instruções.
Esses resultados não me permitem concluir inferioridade arquitetural nem sustentam comparação direta com modelos de outro porte, dados ou ajuste. Publiquei duas representações diferentes: o JSON bruto registra média 18,013% e, por exemplo, F1 de 24,13 em ASSIN2-RTE, 13,93 em FaQuAD-NLI e 23,62 em HateBR, enquanto a tabela mostra 16,09, 9,29 e 15,75. Algebricamente, todas as tarefas binárias da tabela valem exatamente o F1 bruto multiplicado por 2/3; TweetSentBR, com três classes, vale o F1 bruto multiplicado por 3/4. O padrão é compatível com uma classe adicional de resposta inválida, com F1 zero, incluída na macro-média, mas não preservei a regra usada nessa transformação. A média 14,16 é a média aritmética dos nove valores transformados da tabela. Uma versão antiga desta página ainda usava sete tarefas e média 17,72. Para distinguir as edições, apresento o valor “14,16 na tabela publicada” junto do JSON bruto.
Reprodutibilidade e experimentos de eficiência
Uma reprodução defensável deve registrar as versões públicas do código, do modelo, do dataset, de PyTorch, Transformers e CUDA, além de tokenizer, seed, dtype e comando do harness. Também deve verificar a contagem de parâmetros, a integridade do arquivo, a sobreposição entre treino e benchmark e a divergência entre os logits do kernel otimizado e do fallback. No repositório, forneço código, notebook e link W&B, mas não incluí lockfile, imagem de contêiner, manifesto de hardware nem registro completo da execução original; portanto, o material disponível permite uma reprodução parcial.
Para testar a promessa de eficiência no Mambarim, o experimento prioritário é um Transformer causal pareado em aproximadamente 70M parâmetros, com o mesmo tokenizer, corpus, número de tokens, dtype e hardware. Meça comprimentos 128, 512, 1.024 e 2.048, batches 1, 8 e 32, separando prefill de decodificação. Após aquecimento e sincronização CUDA, reporte mediana, p95, memória máxima, energia e qualidade:
Repita com e sem mamba-ssm/causal-conv1d, torch.compile, FP32/BF16 e cache. Use prompts reais e sintéticos de igual comprimento, descarte o custo de download e registre potência média. Para contexto acima de 2.048, trate o resultado como extrapolação: avalie perplexidade por posição, recuperação de chave distante e degradação sem afirmar que a janela treinada aumentou. Finalmente, compare qualidade por FLOP e por joule, pois throughput isolado pode premiar um modelo rápido que perdeu capacidade.
Limitações, uso responsável e licença
Com 69,8M parâmetros efetivos e resultados abaixo do acaso em ENEM e OAB nessa execução, apresento o modelo principalmente para pesquisa, ensino, estudo de SSMs e experimentos de continuação de texto. Não o apresento como fonte factual, assistente confiável nem sistema para decisões médicas, jurídicas, financeiras ou de segurança. Ele pode alucinar, repetir o prompt, produzir toxicidade e reproduzir vieses, dados pessoais ou conteúdo protegido presentes no corpus. Como ainda não avaliei memorização, privacidade, viés regional, variantes do português e segurança, não recomendo sua implantação sem validação adicional.
O Hub marca os pesos como CC BY 4.0, que exige atribuição e indicação de modificações [5]. Isso não resolve automaticamente os direitos dos dados: o Pt-Corpus-Instruct usa licença agregada other e incorpora fontes sob CC BY-SA, ODC-By, CC0, Apache 2.0, termos do Common Crawl, Llama 2 Community License e licenças não comerciais ou sem derivações [6]. No repositório GitHub, não publiquei arquivo de licença nem registrei uma licença detectável. Pesos, código e corpus têm, portanto, regimes distintos; redistribuidores devem revisar cada um em vez de assumir que a etiqueta do modelo relicencia todo material de origem.
Referências
- Gu, A.; Dao, T. Mamba: Linear-Time Sequence Modeling with Selective State Spaces. arXiv:2312.00752, 2023/2024. https://arxiv.org/abs/2312.00752
- Vaswani, A. et al. Attention Is All You Need. NeurIPS, 2017. https://arxiv.org/abs/1706.03762
- Hugging Face. Transformers: Mamba documentation. https://huggingface.co/docs/transformers/model_doc/mamba
- Domingues, M. DominguesM/mambarim-110M: README, notebook e ativos. https://github.com/DominguesM/mambarim-110M
- Domingues, M. Mambarim-110M: card, configuração e arquivos do modelo. https://huggingface.co/dominguesm/mambarim-110m
- Corrêa, N. K. Pt-Corpus-Instruct e variante tokenizada. https://huggingface.co/datasets/nicholasKluge/Pt-Corpus-Instruct
- Corrêa, N. K. et al. TeenyTinyLlama: open-source tiny language models trained in Brazilian Portuguese. Machine Learning with Applications 16, 2024. https://doi.org/10.1016/j.mlwa.2024.100558
- Garcia, E. A. S. Language Model Evaluation Harness for Portuguese LLMs. https://github.com/eduagarcia/lm-evaluation-harness-pt
- Garcia, E. A. S. Resultado bruto do Mambarim-110M no Open Portuguese LLM Leaderboard, 18 abr. 2024. https://huggingface.co/datasets/eduagarcia-temp/llm_pt_leaderboard_raw_results/tree/main/dominguesm/mambarim-110m
BibTeX
@misc{gu2024mamba,
title = {Mamba: Linear-Time Sequence Modeling with Selective State Spaces},
author = {Gu, Albert and Dao, Tri},
year = {2024},
eprint = {2312.00752},
archivePrefix = {arXiv},
primaryClass = {cs.LG}
}
@inproceedings{vaswani2017attention,
title = {Attention Is All You Need},
author = {Vaswani, Ashish and Shazeer, Noam and Parmar, Niki and
Uszkoreit, Jakob and Jones, Llion and Gomez, Aidan N. and
Kaiser, Lukasz and Polosukhin, Illia},
booktitle = {Advances in Neural Information Processing Systems},
year = {2017}
}
@article{correa2024teenytinyllama,
title = {TeenyTinyLlama: open-source tiny language models trained in Brazilian Portuguese},
author = {Corrêa, Nicholas Kluge and Falk, Sophia and Fatimah, Shiza and
Sen, Aniket and de Oliveira, Nythamar},
journal = {Machine Learning with Applications},
volume = {16},
pages = {100558},
year = {2024},
doi = {10.1016/j.mlwa.2024.100558}
}
@misc{domingues2024mambarim,
title = {Mambarim-110M},
author = {Domingues, Maicon},
year = {2024},
howpublished = {Hugging Face model and GitHub repository},
url = {https://huggingface.co/dominguesm/mambarim-110m}
}
@misc{garcia2024openpt,
title = {Open Portuguese LLM Leaderboard},
author = {Garcia, Eduardo A. S.},
year = {2024},
howpublished = {Hugging Face Space},
url = {https://huggingface.co/spaces/eduagarcia/open_pt_llm_leaderboard}
}