8 4.9K 56

Canarim-7B-Instruct

Como desenvolvi o Canarim-7B-Instruct, um modelo causal de 6,74 bilhões de parâmetros ajustado para seguir instruções em português.

Canarim-7B-Instruct: objetivos e resultados do ajuste em português

Eu desenvolvi o Canarim-7B-Instruct como a variante ajustada para instruções do Canarim-7B. O checkpoint preserva a arquitetura causal Llama 2 de 7 bilhões de parâmetros, mas parte de pesos já adaptados ao português e passa por uma etapa adicional com exemplos de instrução e resposta. A consequência pretendida não é adicionar uma base de conhecimento separada: é ensinar uma interface de tarefa, isto é, reconhecer uma instrução, usar um contexto opcional e produzir a continuação no formato esperado.[1]

No Hub, publiquei pesos, configuração, tokenizer, template textual, histórico e resultados de avaliação. Na versão inicial, não incluí o script nem um relatório de treinamento com épocas, learning rate, batch size, comprimento das sequências, otimizador, scheduler, precisão, hardware, seed, estratégia de truncamento ou política de máscara da loss. Ao revisitar o projeto, prefiro manter esses itens como não registrados a preenchê-los pela semelhança com outras receitas.

Propriedade verificávelValor publicado
ArquiteturaLlamaForCausalLM
Parâmetros nos arquivos SafeTensors6.738.415.616 em FP16
Camadas / dimensão oculta32 / 4.096
Cabeças de atenção / KV32 / 32
Dimensão do MLP11.008, ativação SiLU
Vocabulário / janela posicional32.000 / 4.096 tokens
Modelo inicialdominguesm/canarim-7b
TokenizerLlama, com <s>, </s> e <unk>; sem pad_token declarado
Chat template no tokenizernão publicado
Licença dos pesosLlama 2 Community License

Linhagem: de Llama 2 ao modelo de instruções

Para criar o Canarim-7B base, parti dos pesos do Llama 2 7B e executei pré-treinamento continuado em 16 bilhões de tokens do subconjunto português CC-MAIN-2023-23, com corte dos dados em meados de 2023. Publiquei o dataset associado com 16.899.389 documentos e cerca de 97,6 GB lógicos, mas não registei a seleção exata dos 16 bilhões de tokens, a deduplicação, os filtros nem os hiperparâmetros dessa adaptação.[2][3] Depois, inicializei o Instruct a partir desse modelo, não diretamente do Llama 2 original.

Renderizando diagrama...

Mantive a mesma geometria nos dois checkpoints: 32 blocos, atenção multi-head sem GQA (num_key_value_heads = 32), RoPE com base 10.000, RMSNorm com epsilon 1e-5 e contexto de 4.096 posições. O número comercial “7B” arredonda os 6,738 bilhões de parâmetros. O ajuste instrucional altera os pesos, não acrescenta novas camadas.[1][2]

Canarim Instruct Dataset e o formato de tarefa

Eu construí o Canarim Instruct PTBR Dataset para este projeto, com três strings: instruction, input e output. Publiquei 316.413 exemplos de treino e 1.519 de teste, ou 317.932 exemplos no total, reunidos de cinco fontes traduzidas ou adaptadas:[5]

51759+57692+74350+82612+50000=316413.51\,759+57\,692+74\,350+82\,612+50\,000=316\,413.

As parcelas são alpaca-data-pt-br, cahya/instructions-pt, HuggingFaceH4/self_instruct e as configurações self_instruct e super_natural_instructions de HuggingFaceH4/helpful_instructions. A igualdade explica toda a contagem de treino publicada. Na primeira versão, não registei como tratei duplicatas, traduções defeituosas, respostas conflitantes ou sobreposição entre fontes, e alertei que podem existir erros de tradução.[5]

No ajuste, usei uma variedade de datasets públicos de instruções e o mesmo template do Canarim Instruct Dataset. Porém, no metadado YAML mantive apenas CC-MAIN-2023-23, que é o corpus de pré-treinamento do modelo-base, e não publiquei um manifesto das fontes efetivamente consumidas. Por isso, não afirmo que as 316.413 linhas, sem alterações, foram o conjunto exato de treinamento do checkpoint.[1][5]

Criei dois ramos para o template. Sem contexto, concatenei preâmbulo, instrução e marcador de resposta:

Abaixo está uma instrução que descreve uma tarefa. Escreva uma resposta que conclua adequadamente a solicitação.
 
### Instruções:
{instruction}
 
### Resposta:

Com contexto, o preâmbulo informa que existe uma entrada e acrescenta o bloco ### Entrada::

Abaixo está uma instrução que descreve uma tarefa, emparelhada com uma entrada que fornece mais contexto. Escreva uma resposta que conclua adequadamente a solicitação.
 
### Instruções:
{instruction}
 
### Entrada:
{input}
 
### Resposta:

input vazio seleciona o primeiro ramo; uma string não vazia seleciona o segundo. Nos exemplos, também chamei esse campo de “context”, mas o nome real da coluna é input. Não publiquei chat_template em tokenizer_config.json, e a avaliação registrou has_chat_template: false. Logo, tokenizer.apply_chat_template() não reproduz automaticamente esse protocolo: a aplicação deve formatá-lo explicitamente.[1][6]

O objetivo do ajuste instrucional

Em supervised fine-tuning, cada tripla é convertida em uma sequência. Seja P=T(instruction,input)P=T(\text{instruction},\text{input}) o prompt formatado e R=outputR=\text{output} a resposta. Um modelo causal estima cada próximo token condicionado aos anteriores:

pθ(z1,,zn)=t=1npθ(ztz<t),z=[P;R].p_\theta(z_1, \ldots, z_n) = \prod_{t=1}^{n} p_\theta(z_t \mid z_{<t}), \qquad z = [P; R].

A loss causal completa é a entropia cruzada deslocada em uma posição:

Lfull=1n1t=2nlogpθ(ztz<t).\mathcal{L}_{\mathrm{full}} = -\frac{1}{n-1} \sum_{t=2}^{n} \log p_\theta(z_t \mid z_{<t}).

Ela treina o modelo a reproduzir tanto o template quanto a resposta. Outra implementação comum mascara os labels do prompt e calcula gradiente apenas sobre a resposta. Com mt=1m_t=1 para tokens de RR e zero para tokens de PP:

Lresp=1t=2nmtt=2nmtlogpθ(ztz<t).\mathcal{L}_{\mathrm{resp}} = -\frac{1}{\sum_{t=2}^{n} m_t} \sum_{t=2}^{n} m_t \log p_\theta(z_t \mid z_{<t}).

Em PyTorch/Transformers, isso costuma ser realizado atribuindo -100 aos labels ignorados. Não registrei se usei essa máscara no treinamento do Canarim-7B-Instruct. No GitHub ligado pelo dataset, publiquei somente README e imagem; no repositório do modelo, disponibilizei artefatos de inferência, não batches ou labels de treino. Assim, apresento L_full e L_resp como duas receitas possíveis, sem atribuir uma delas à execução histórica.[7]

Renderizando diagrama...

Em ambos os casos, o ajuste muda a distribuição condicional para que ### Resposta: seja seguido por uma solução compatível com a instrução. Isso explica por que um modelo Instruct pode ser mais útil em zero-shot mesmo sem superar o base em todo benchmark: seguir o envelope textual e preservar conhecimento são objetivos relacionados, mas não idênticos.

Inferência reproduzível e exemplo corrigido

No exemplo que publiquei, terminei com make_prompt(question), mas não defini question; a variável correta é instruction. Também criei context sem passá-lo à função. Esse trecho não produz o resultado exibido e precisa ser corrigido. A versão abaixo resolve os dois erros, usa os pesos SafeTensors e decodifica somente tokens novos:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
 
MODEL_ID = "dominguesm/Canarim-7B-Instruct"
 
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    torch_dtype=torch.float16,
    device_map="auto",
    use_safetensors=True,
)
 
 
def make_prompt(instruction: str, context: str | None = None) -> str:
    if context and context.strip():
        return f"""Abaixo está uma instrução que descreve uma tarefa, emparelhada com uma entrada que fornece mais contexto. Escreva uma resposta que conclua adequadamente a solicitação.
 
### Instruções:
{instruction}
 
### Entrada:
{context.strip()}
 
### Resposta:"""
    return f"""Abaixo está uma instrução que descreve uma tarefa. Escreva uma resposta que conclua adequadamente a solicitação.
 
### Instruções:
{instruction}
 
### Resposta:"""
 
 
instruction = "Faça um resumo conciso, sem acrescentar fatos ao texto."
context = """Bentinho explica que recebeu o apelido Dom Casmurro de um poeta
que julgou que ele cochilava no trem. Aos 15 anos, Bentinho era vizinho de
Capitu, de 14. José Dias sugeriu que D. Glória enviasse o filho ao seminário,
cumprindo uma promessa feita quando ele nasceu."""
 
prompt = make_prompt(instruction, context)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
 
with torch.inference_mode():
    generated = model.generate(
        **inputs,
        max_new_tokens=160,
        do_sample=False,
        eos_token_id=tokenizer.eos_token_id,
        pad_token_id=tokenizer.eos_token_id,
    )
 
new_tokens = generated[0, inputs["input_ids"].shape[1] :]
print(tokenizer.decode(new_tokens, skip_special_tokens=True).strip())

Em FP16, os parâmetros sozinhos exigem aproximadamente 13,5 GB, ou 12,6 GiB.

O JSON bruto do leaderboard observou footprint de cerca de 13,61 GB durante a avaliação; inferência ainda requer memória para KV cache, ativações e runtime. device_map="auto" exige accelerate e pode descarregar partes do modelo quando a GPU não comporta tudo. Quantização reduz memória, mas muda a representação e não foi a condição do benchmark publicado.[6]

Configurei a geração com do_sample=true, temperature=0.6, top_p=0.9, max_length=4096, IDs BOS/EOS 1/2 e pad_token_id=0. Esse último valor é inconsistente com o tokenizer, que trata o ID 0 como <unk> e não declara padding. Para uma requisição individual, definir localmente pad_token_id=eos_token_id evita usar <unk> como padding sem alterar os arquivos do modelo. No exemplo atual, prefiro max_new_tokens, que limita apenas a resposta; max_length conta prompt mais resposta.[1][8]

Na amostragem, a temperatura transforma o logit lil_i em

pi=softmax ⁣(liT).p_i=\operatorname{softmax}\!\left(\frac{l_i}{T}\right).

Com T=0,6T=0{,}6, diferenças entre logits são ampliadas e a distribuição fica mais concentrada. top_p = 0,9 retém o menor conjunto ordenado de tokens cuja probabilidade acumulada atinge 90% e renormaliza esse conjunto. Esses são defaults de geração, não hiperparâmetros de treinamento. Para resumo factual e comparação reprodutível, do_sample=False é uma linha de base melhor; para escrita diversa, pode-se testar os defaults e várias seeds.

Leaderboards: comparação pareada com o base

O Open Portuguese LLM Leaderboard avaliou os dois checkpoints em FP16, sem quantização e sem chat template, com batch 8, contexto máximo de 4.064 tokens e até 32 tokens gerados. Foram 14.150 itens sem truncamento: três few-shots em ENEM, BLUEX e OAB; 15 em ASSIN2 e FaQuAD NLI; 25 nas três tarefas de classificação social. Os JSONs usam a mesma versão 1.1.0 da avaliação e versões idênticas das tarefas, tornando esta uma comparação bem mais controlada do que confrontar cards de épocas diferentes.[6]

Tarefa e métrica principalCanarim-7B-InstructCanarim-7B baseDelta Instruct - base
Média não ponderada47,2147,36-0,15 pp
ENEM, acurácia, 3-shot27,5025,96+1,54 pp
BLUEX, acurácia, 3-shot26,1529,76-3,62 pp
OAB, acurácia, 3-shot29,9331,48-1,55 pp
ASSIN2 RTE, F1 macro, 15-shot75,7471,96+3,78 pp
ASSIN2 STS, Pearson, 15-shot12,0813,34-1,26 pp
FaQuAD NLI, F1 macro, 15-shot43,9249,09-5,17 pp
HateBR, F1 macro, 25-shot79,5778,49+1,08 pp
PT Hate Speech, F1 macro, 25-shot64,0163,74+0,28 pp
tweetSentBR, F1 macro, 25-shot66,0062,38+3,62 pp

A média publicada é simplesmente a média dos nove escores principais:

27,50+26,15+29,93+75,74+12,08+43,92+79,57+64,01+66,009=47,21.\frac{27{,}50+26{,}15+29{,}93+75{,}74+12{,}08+43{,}92+79{,}57+64{,}01+66{,}00}{9}=47{,}21.

Ela mistura acurácia, F1 macro e Pearson e dá o mesmo peso a datasets de tamanhos diferentes; não é uma probabilidade agregada de acerto. O Instruct melhora cinco tarefas, piora quatro e quase empata na média. Os ganhos em RTE e tweetSentBR coexistem com perdas em FaQuAD NLI e BLUEX. Sem intervalos de confiança, múltiplas seeds ou análise por exemplo, não se deve tratar diferenças pequenas como prova de superioridade.

Na comparação com o modelo-base, usei 25,96 no ENEM, valor da execução bruta que fecha a média 47,36. Também preservei 26,96 no model-index, um valor diferente para a mesma tarefa. Para não misturar registros, mantenho 25,96 na tabela pareada e recomendo citar resultado e protocolo juntos.[2][6]

Para o Canarim-7B base, publiquei ainda resultados do antigo Open LLM Leaderboard geral: média 48,63; ARC 51,96; HellaSwag 77,52; MMLU 40,92; TruthfulQA 40,03; Winogrande 71,43; GSM8K 9,93. Não publiquei uma execução equivalente para o Instruct; portanto, essa tabela descreve apenas o base e não sustenta uma comparação entre os dois.[2] Leaderboards medem tarefas e protocolos específicos; tampouco medem diretamente utilidade conversacional, segurança ou fidelidade de resumos.

Resultados observados e prioridades de avaliação

No card, mostro um resumo de Dom Casmurro que parece fluente, mas contém erros verificáveis contra o próprio contexto. Ele introduz uma “governanta Josefa”, “o outono de 1860” e a ideia de que Bentinho conhece Capitu naquela temporada; nada disso aparece na entrada, que já descreve Capitu como vizinha e amiga. Também omite a origem do título, José Dias, a promessa de D. Glória e o seminário, os elementos causais centrais do trecho.[1]

Uma leitura manual simples pode decompor a saída em oito alegações. A tabela deixa explícita uma segmentação possível; outra anotação pode separar ou agrupar as proposições de modo diferente.

Alegação extraída da saídaRelação com a entrada
A obra apresenta memórias de Bentinhosustentada
Bentinho tem 15 anossustentada
D. Glória é sua mãesustentada
A infância ocorre na casa dos paisnão sustentada
Existe uma governanta chamada Josefanão sustentada
A mudança ocorre no outono de 1860não sustentada
Ele conhece Capitu, de 14 anos, nessa ocasiãoparcial: a idade aparece, mas eles já eram amigos
Capitu se tornaria sua companheira por muitos anosnão sustentada pelo trecho

Com três alegações sustentadas, uma parcialmente sustentada e quatro não sustentadas, se a parcial receber peso 0,5, a precisão de suporte ilustrativa será

3+0,58=43,75%.\frac{3+0{,}5}{8}=43{,}75\%.

Esse cálculo não é um benchmark do modelo: avalia um único texto escolhido e uma segmentação manual. Ele demonstra por que fluência não equivale a factualidade e por que a correção do código importa. No exemplo quebrado, o contexto não chegava ao prompt; ainda assim, o resultado era apresentado como resumo dele. Uma avaliação séria deve executar o código corrigido em um conjunto estável, registrar a versão do modelo e os parâmetros, separar omissão de contradição, usar mais de um anotador e reportar concordância.

Para produção, instruções como “não acrescente fatos” ajudam, mas não garantem obediência. Estratégias mais robustas incluem decodificação determinística, limite curto, retorno de trechos de evidência, validação de entidades e números contra a fonte, e recusa quando o contexto não contém resposta. Nenhuma configuração de temperature transforma o checkpoint em fonte confiável de informação atualizada.

Escopo do ajuste, segurança e licenças

Na versão inicial, não publiquei a receita de treino completa, um manifesto completo dos datasets, a avaliação do split de teste, métricas de chat e factualidade nem um card de segurança específico. O corte de conhecimento em meados de 2023 é herdado do corpus português. Dados traduzidos podem carregar anglicismos e erros; dados gerados por outros modelos podem transmitir vieses, estilo artificial e respostas factualmente incorretas. Common Crawl pode conter toxicidade, dados pessoais, desinformação e material protegido. O ajuste supervisionado não é RLHF nem, por si só, alinhamento de segurança.[3][5]

Os pesos são distribuídos sob a Llama 2 Community License, não sob uma licença permissiva como Apache-2.0. Redistribuição exige conservar licença e atribuição; há política de uso aceitável, restrição para organizações acima do limiar contratual de usuários ativos e limites para usar materiais ou resultados na melhoria de outros LLMs. A análise jurídica deve considerar o texto integral, não apenas a tag llama2 do Hub.[9]

Publiquei o dataset de instruções separadamente sob CC BY-NC 4.0, com atribuição e restrição a uso não comercial, por causa da presença de conteúdo derivado ou influenciado por gerações da OpenAI. Licenciar o dataset não elimina direitos ou termos das fontes subjacentes, e a licença dos pesos não substitui a licença dos dados. Um uso comercial precisa avaliar ambos os conjuntos de obrigações e a proveniência real da versão treinada.[5][10]

Vejo o Canarim-7B-Instruct como um artefato inicial de instruction tuning aberto em português: publiquei pesos integrais, arquitetura conhecida, template legível e avaliação pareada com o modelo-base. Para orientar seu uso responsável, preciso fixar revisões, reproduzir prompts, validar fatos e declarar com clareza as informações que ainda não registrei.

Referências

1. Domingues, M. Canarim-7B-Instruct: card, configuração, configuração de geração, tokenizer e API. Consulta em 24 jul. 2026.

2. Domingues, M. Canarim-7B, Hugging Face, 2023. DOI 10.57967/hf/1356.

3. Domingues, M. CC-MAIN-2023-23, dataset em português derivado do Common Crawl.

4. Hugging Face. Históricos de publicação do Canarim-7B-Instruct, Canarim-7B e Canarim Instruct Dataset.

5. Domingues, M. Canarim-Instruct-PTBR-Dataset, Hugging Face, 2023. DOI 10.57967/hf/0983; ver também o repositório GitHub.

6. Garcia, E. et al. Open Portuguese LLM Leaderboard e resultados brutos de Canarim-7B-Instruct e Canarim-7B. Execuções de 25 e 16 fev. 2024.

7. Touvron, H. et al. Llama 2: Open Foundation and Fine-Tuned Chat Models, 2023. O artigo descreve a modelagem causal e o supervised fine-tuning da família de origem, mas não documenta a receita particular do Canarim.

8. Hugging Face. Transformers: Generation e estratégias de geração.

9. Meta. Llama 2 Community License Agreement e Acceptable Use Policy, versão de 18 jul. 2023.

10. Creative Commons. Attribution-NonCommercial 4.0 International, licença declarada para o dataset de instruções.

BibTeX

@misc{domingues2023canarim7b,
  author    = {Maicon Domingues},
  title     = {canarim-7b},
  year      = {2023},
  publisher = {Hugging Face},
  doi       = {10.57967/hf/1356},
  url       = {https://huggingface.co/dominguesm/canarim-7b}
}
 
@misc{domingues2023canariminstruct,
  author    = {Maicon Domingues},
  title     = {Canarim-7B-Instruct},
  year      = {2023},
  publisher = {Hugging Face},
  url       = {https://huggingface.co/dominguesm/Canarim-7B-Instruct}
}
 
@misc{domingues2023canarimdataset,
  author    = {Maicon Domingues},
  title     = {Canarim-Instruct-PTBR-Dataset},
  year      = {2023},
  publisher = {Hugging Face},
  doi       = {10.57967/hf/0983},
  url       = {https://huggingface.co/datasets/dominguesm/Canarim-Instruct-PTBR-Dataset}
}
 
@misc{touvron2023llama2,
  author        = {Hugo Touvron and others},
  title         = {Llama 2: Open Foundation and Fine-Tuned Chat Models},
  year          = {2023},
  eprint        = {2307.09288},
  archivePrefix = {arXiv},
  primaryClass  = {cs.CL},
  url           = {https://arxiv.org/abs/2307.09288}
}
 
@misc{garcia2024openptllmleaderboard,
  author = {Eduardo Garcia and contributors},
  title  = {Open Portuguese LLM Leaderboard},
  year   = {2024},
  url    = {https://huggingface.co/spaces/eduagarcia/open_pt_llm_leaderboard}
}