48 6K 84

Mambarim-110M

Como desenvolvi o Mambarim-110M, meu modelo causal em português baseado em Mamba, e avaliei arquitetura, dados e treinamento.

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

ItemValor verificável
Tarefamodelagem causal, previsão do próximo token
ClasseMambaForCausalLM
Idiomaportuguês
Vocabulário32.000 tokens
Dimensão oculta768
Blocos Mamba12
Expansão interna2, produzindo dimensão 1.536
Estado SSM16 por canal
Convolução causal localkernel 4
Passo temporalrank 48, intervalo inicial de 0,001 a 0,1
Ativação e normalizaçãoSiLU e RMSNorm, epsilon 1e-5
Pesosfloat32, em formato SafeTensors
Parâmetros no checkpoint69.836.544
Número declarado no card119.930.880, uma estimativa incompatível com o checkpoint
Contexto de treinamento e avaliação2.048 tokens
Dados declaradosPt-Corpus-Instruct tokenizado, aproximadamente 6,2 bilhões de tokens
Etapas declaradas758.423
Licença declarada para os pesosCC 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:

Tatencao(L,d)=Θ(L2d),Mescores(L)=Θ(L2).T_{\text{atencao}}(L,d)=\Theta(L^2d), \qquad M_{\text{escores}}(L)=\Theta(L^2).

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:

TMamba(L,d,N)=Θ(LdN),Mestado na geracao=Θ(dN).T_{\text{Mamba}}(L,d,N)=\Theta(LdN), \qquad M_{\text{estado na geracao}}=\Theta(dN).

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 x(t)x(t) em saída y(t)y(t) por um estado oculto h(t)h(t) [1]:

h˙(t)=Ah(t)+Bx(t),y(t)=Ch(t).\begin{aligned} \dot h(t)&=A h(t)+B x(t),\\ y(t)&=C h(t). \end{aligned}

Como texto é discreto, o sistema é discretizado. Com zero-order hold e passo Δ, obtêm-se:

Aˉ=exp(ΔA),Bˉ=(ΔA)1[exp(ΔA)I]ΔB,ht=Aˉht1+Bˉxt,yt=Cht.\begin{aligned} \bar A&=\exp(\Delta A),\\ \bar B&=(\Delta A)^{-1}\left[\exp(\Delta A)-I\right]\Delta B,\\ h_t&=\bar A h_{t-1}+\bar B x_t,\\ y_t&=C h_t. \end{aligned}

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:

Bt=sB(xt),Ct=sC(xt),Δt=softplus ⁣(θΔ+sΔ(xt)),Aˉt,Bˉt=discretizar(Δt,A,Bt),ht=Aˉtht1+Bˉtxt,yt=Ctht.\begin{aligned} B_t&=s_B(x_t),& C_t&=s_C(x_t),\\ \Delta_t&=\operatorname{softplus}\!\left(\theta_\Delta+s_\Delta(x_t)\right),\\ \bar A_t,\bar B_t&=\operatorname{discretizar}(\Delta_t,A,B_t),\\ h_t&=\bar A_t h_{t-1}+\bar B_t x_t,\\ y_t&=C_t h_t. \end{aligned}

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:

Nemb=32000×768=24576000,Nblocos=12×(4d2+3d×1536)=70778880,Nlm_head=768×32000=24576000,Nestimativa=119930880.\begin{aligned} N_{\mathrm{emb}}&=32\,000\times768=24\,576\,000,\\ N_{\mathrm{blocos}}&=12\times\left(4d^2+3d\times1\,536\right)=70\,778\,880,\\ N_{\mathrm{lm\_head}}&=768\times32\,000=24\,576\,000,\\ N_{\mathrm{estimativa}}&=119\,930\,880. \end{aligned}

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 é:

Nemb=24576000,Nblocos=12×3771648=45259776,NRMSNorm=768,Ncheckpoint=69836544 parametros.\begin{aligned} N_{\mathrm{emb}}&=24\,576\,000,\\ N_{\mathrm{blocos}}&=12\times3\,771\,648=45\,259\,776,\\ N_{\mathrm{RMSNorm}}&=768,\\ N_{\mathrm{checkpoint}}&=69\,836\,544\ \text{parametros}. \end{aligned}

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

30336907584234\frac{3\,033\,690}{758\,423}\approx4

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:

6,2B119,93088M51,7e6,2B69,836544M88,8tokens por parametro.\frac{6{,}2\,\mathrm{B}}{119{,}93088\,\mathrm{M}}\approx51{,}7 \quad\text{e}\quad \frac{6{,}2\,\mathrm{B}}{69{,}836544\,\mathrm{M}}\approx88{,}8 \quad\text{tokens por parametro}.

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.

ModeloMédiaENEMBLUEXOABASSIN2 RTEASSIN2 STSFaQuAD NLIHateBRPT Hate SpeechTweetSentBRArquitetura
Mambarim-110M14,1618,4010,5721,8716,091,899,2915,7517,7715,79Mamba

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:

throughput=batch×tokens processados ou geradostempos,latenciadecode=tempo de geracaobatch×novos tokens,\mathrm{throughput}=\frac{\mathrm{batch}\times\text{tokens processados ou gerados}}{\mathrm{tempo}_{s}}, \qquad \mathrm{latencia}_{\mathrm{decode}}=\frac{\text{tempo de geracao}}{\mathrm{batch}\times\text{novos tokens}},
ganho=throughputMambathroughputTransformer.\mathrm{ganho}=\frac{\mathrm{throughput}_{\mathrm{Mamba}}}{\mathrm{throughput}_{\mathrm{Transformer}}}.

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

  1. Gu, A.; Dao, T. Mamba: Linear-Time Sequence Modeling with Selective State Spaces. arXiv:2312.00752, 2023/2024. https://arxiv.org/abs/2312.00752
  2. Vaswani, A. et al. Attention Is All You Need. NeurIPS, 2017. https://arxiv.org/abs/1706.03762
  3. Hugging Face. Transformers: Mamba documentation. https://huggingface.co/docs/transformers/model_doc/mamba
  4. Domingues, M. DominguesM/mambarim-110M: README, notebook e ativos. https://github.com/DominguesM/mambarim-110M
  5. Domingues, M. Mambarim-110M: card, configuração e arquivos do modelo. https://huggingface.co/dominguesm/mambarim-110m
  6. Corrêa, N. K. Pt-Corpus-Instruct e variante tokenizada. https://huggingface.co/datasets/nicholasKluge/Pt-Corpus-Instruct
  7. 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
  8. Garcia, E. A. S. Language Model Evaluation Harness for Portuguese LLMs. https://github.com/eduagarcia/lm-evaluation-harness-pt
  9. 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}
}