Alpaca-LoRA PT-BR 7B: como desenvolvi um ajuste instrucional eficiente
Eu desenvolvi o Alpaca-LoRA PT-BR 7B como um adaptador LoRA para o LLaMA 7B original, treinado para seguir instruções em português. Publicado em março de 2023, ele registra um momento importante da abertura prática dos grandes modelos de linguagem: em vez de reajustar bilhões de pesos, conserva o modelo-base e aprende cerca de quatro milhões de parâmetros adicionais. O resultado distribuído no Hugging Face não é um modelo completo; é um adapter_model.bin de aproximadamente 16 MiB que só funciona quando acoplado ao checkpoint LLaMA compatível [1][2].
Eu parti do fluxo público do Alpaca-LoRA, de Tim Dettmers e Eric J. Wang, e do formato de dados do Stanford Alpaca. Traduzi os dados, adaptei os templates para português, configurei o LoRA, executei o treinamento e publiquei o adaptador e um aplicativo Gradio. No repositório DominguesM/alpaca-lora-ptbr-7b também mantive a trilha de evolução em changelog, o link para Colab do notebook de avaliação e a página de Hugging Face Space para validação rápida do modelo quantizado [1][8].
Essas bases anteriores continuam atribuídas aos seus autores; minha contribuição foi a adaptação e a execução em português [1][8].
Ficha técnica
| Propriedade | Valor documentado |
|---|---|
| Modelo-base | decapoda-research/llama-7b-hf, conversão histórica do LLaMA 7B v1 |
| Arquitetura | 32 blocos, dimensão oculta 4.096, 32 cabeças, MLP 11.008, vocabulário 32.000 |
| Tarefa PEFT | CAUSAL_LM |
| Dados | dominguesm/alpaca-data-pt-br; 52.002 linhas vistas no notebook de 2023 |
| Estado atual do dataset | 51.759 linhas, campos instruction, input e output |
| LoRA | r=8, alpha=16, dropout=0.05, sem bias |
| Matrizes adaptadas | q_proj e v_proj em todos os 32 blocos |
| Comprimento | 512 tokens |
| Treino | 3 épocas, lote efetivo 128, taxa 3e-4, FP16, base carregada em 8 bits |
| Avaliação | perda em uma separação aleatória de 2.000 exemplos; exemplos qualitativos |
| Artefato | 4.194.304 parâmetros LoRA; arquivo de 16.822.989 bytes |
Configurei o adaptador para a geometria do LLaMA v1. Isso é relevante: um adaptador não pode ser aplicado apenas porque outro checkpoint também se chama “7B”. Nomes, quantidades e formatos das projeções precisam coincidir. Llama 2, Llama 3 e modelos de outras famílias não são substitutos binariamente compatíveis.
Linhagem dos dados
O ponto de partida conceitual é o Stanford Alpaca. Seus autores usaram text-davinci-003 e uma variação de Self-Instruct para gerar 52 mil pares de instrução e demonstração em inglês. Cada registro contém uma instrução, uma entrada contextual opcional e a resposta. Aproximadamente 40% dos registros originais têm input não vazio [5]. O projeto Alpaca-Cleaned revisou essa coleção para remover ou corrigir saídas vazias, instruções mescladas, referências a imagens e URLs inexistentes, caracteres de controle e muitas respostas erradas [6].
No treinamento de 2023, usei 52.002 linhas, quantidade preservada na saída do notebook. Depois, publiquei a edição atual de dominguesm/alpaca-data-pt-br com 51.759 exemplos traduzidos de yahma/alpaca-cleaned [3]. Não registrei um identificador imutável da edição usada no primeiro treino; por isso, a versão atual do dataset não deve ser usada como se fosse exatamente o corpus daquela execução.
Renderizando diagrama...
Como a tradução foi executada
Usei um pipeline("translation") com Helsinki-NLP/opus-mt-tc-big-en-pt na GPU. Esse modelo Transformer-big foi treinado no ecossistema OPUS com Marian NMT, usa SentencePiece e requer um marcador inicial de língua-alvo. Para português genérico, o marcador documentado é >>por<<; >>pob<< também é aceito para português brasileiro. Eu escolhi >>por<< [4][10].
Para cada linha, prefixei separadamente os campos não vazios instruction, input e output, transformei-os em uma sequência com três itens por exemplo e executei a tradução em lotes de 32, com truncation=True e max_length=512. Depois, reagrupei cada trio traduzido e publiquei um novo Dataset com o mesmo esquema. Assim, traduzi não só as perguntas, mas também as respostas produzidas originalmente em inglês.
No notebook que preservei, a célula de reconstrução chama batched(ds, 3) onde deveria chamar batched(translated_ds, 3). Essa célula publicada não recompõe corretamente os resultados e precisa ser corrigida antes da reprodução. Não mantive o notebook final usado para gerar o dataset; por isso, não apresento o arquivo disponível como reprodução integral do processo.
A tradução automática preservou escala, não qualidade editorial. O conjunto que publiquei inclui problemas concretos: João reaparece como John; código Python vira pseudocódigo corrompido; símbolos de multiplicação e igualdade desaparecem; e uma resposta diz incorretamente que prótons e nêutrons têm carga positiva. Há também construções de português europeu e frases truncadas. Parte dos erros já estava nas demonstrações sintéticas em inglês, parte pode vir da limpeza e parte da tradução. Aprendi que traduzir uma resposta não a verifica nem localiza conhecimentos para o Brasil.
Em uma próxima versão, pretendo registrar as edições e datas do dataset e do tradutor, conservar os textos inglês-português alinhados, publicar um manifesto da execução, registrar comprimentos antes e depois, validar linguagem e caracteres, deduplicar e submeter amostras estratificadas a revisão humana. O corte de 512 tokens na tradução também pode eliminar o fim de respostas longas antes mesmo do corte de 512 tokens do LLaMA.
Templates e objetivo causal
Usei traduções diretas dos dois templates do Stanford Alpaca. Com contexto:
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:
{output}Sem contexto, omiti o bloco ### Entrada:. No treino, mantive a resposta na mesma sequência causal. Tokenizei com max_length=513, truncamento e preenchimento até o máximo; em seguida, descartei o último elemento de input_ids e de attention_mask, produzindo 512 posições. Usei DataCollatorForLanguageModeling(mlm=False) para criar os rótulos do próximo token. Não apliquei uma máscara explícita para excluir instrução e entrada da perda, então o modelo aprende a prever todo o prompt, não apenas a resposta.
Esse detalhe tem dois efeitos. Primeiro, preencher todos os exemplos até 512 desperdiça computação quando as sequências são curtas. Segundo, a perda publicada mistura a modelagem da fórmula fixa, da instrução, do contexto e da resposta; ela não mede isoladamente a capacidade de obedecer à tarefa.
O que LoRA altera
No ajuste completo, cada matriz pré-treinada receberia uma atualização densa. LoRA congela e representa a atualização como o produto de duas matrizes de posto baixo [7]:
Escolhi e , portanto a contribuição aprendida é multiplicada por . Durante o treino, apliquei dropout de 5% no ramo LoRA. Adaptei somente as projeções de query (q_proj) e value (v_proj) da atenção; k_proj, o_proj, MLP, embeddings, normalizações e cabeça de saída permaneceram congelados.
Para uma projeção quadrada , o ajuste denso teria 16.777.216 parâmetros. O par LoRA tem apenas:
São duas projeções em cada um dos 32 blocos:
Com a arquitetura que usei, o modelo-base tem 6.738.415.616 parâmetros, contando embeddings de entrada e cabeça de saída não compartilhados. Assim, o adaptador corresponde a aproximadamente 0,0622% do base; o modelo composto tem 6.742.609.920 parâmetros. Os 4.194.304 valores em FP32 ocupam exatamente 16 MiB sem metadados, coerentes com os 16.822.989 bytes do adapter_model.bin [2].
Renderizando diagrama...
Treinamento com base em 8 bits
Segui a receita do Alpaca-LoRA: carreguei LlamaForCausalLM com load_in_8bit=True e device_map="auto", preparei o modelo para treino inteiro de 8 bits por meio da API PEFT da época e injetei os adaptadores [1][8]. Isso não significa que os pesos LoRA sejam inteiros nem que toda a execução use um byte por parâmetro. A base é quantizada e congelada; adaptadores, operações sensíveis, ativações e gradientes usam ponto flutuante. Também não é adequado chamar automaticamente esse experimento de QLoRA: a receita antecede QLoRA, usa a integração int8 disponível em 2023 e mira apenas q_proj e v_proj.
Usei os seguintes parâmetros:
| Parâmetro | Valor |
|---|---|
| Microbatch por dispositivo | 4 |
| Acumulação de gradiente | 32 passos |
| Lote efetivo declarado | 128 |
| Épocas | 3 |
| Taxa de aprendizado | 3e-4 |
| Aquecimento | 100 passos |
| Conjunto de validação | 2.000 exemplos, seed=42 |
| Avaliação e checkpoint | a cada 200 passos |
| Checkpoints mantidos | 3 |
| Precisão de treino | FP16 |
| LoRA | r=8, alpha=16, dropout=0.05 |
No README, publiquei 3e-5 para a taxa e 0.5 para dropout, valores diferentes dos que preservei no notebook; além disso, o adapter_config.json confirma lora_dropout=0.05. Com esses registros, adoto 3e-4 e 0.05 como os valores sustentados pelo notebook e pela configuração e trato a tabela do README como informação que preciso corrigir. O treino usa load_best_model_at_end=True, desativa o cache e sobrescreve state_dict para salvar somente pesos PEFT.
Registrei perdas de treino/validação de 0,891900/0,891723 no passo 200, 0,854400/0,866401 no 400, 0,850600/0,854273 no 600, 0,831000/0,846825 no 800 e 0,832000/0,842221 no 1.000. A tendência sugere convergência sem grande abertura entre os dois conjuntos, mas não me permite concluir qualidade instrucional. Validei no mesmo corpus traduzido e não incluí baseline do LLaMA, teste português independente, avaliação humana protocolada, intervalo de confiança ou benchmark de segurança.
Memória: como interpreto a economia
Em precisão FP16, apenas os 6.738.415.616 pesos da base exigiriam cerca de 12,55 GiB. A representação ideal de um byte por peso reduz essa parcela para 6,28 GiB. O estado FP32 do adaptador adiciona 16 MiB; seus gradientes e os dois momentos de Adam acrescentam dezenas de MiB, dependendo da precisão e da implementação do otimizador.
Esses números não são uma estimativa total de VRAM. Quantização inclui escalas e tratamento de valores atípicos; há ativações para microbatches de quatro sequências de 512 tokens, buffers temporários, logits sobre 32 mil tokens, contexto CUDA e, em certas implementações, cópias mestras. LoRA elimina gradientes e estados de otimizador para os 6,7 bilhões de pesos, que seriam o custo dominante do ajuste completo, mas não elimina memória de inferência nem ativações. “Base em 8 bits” não equivale a “treina em 7 GB”.
Inferência com Transformers e PEFT
Na avaliação, carreguei a mesma base em 8 bits, acoplei o adaptador e usei temperature=0.1, top_p=0.75, quatro feixes e até 256 novos tokens. Como não ativei amostragem, temperature e top_p não controlam a busca por feixes nas versões usuais do Transformers. O código abaixo preserva a busca determinística relevante e evita o frágil split("### Resposta:"), decodificando apenas os tokens novos. É necessário fornecer uma cópia legítima e exatamente compatível do LLaMA v1 convertido para Transformers:
import torch
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
BASE_MODEL = "/caminho/autorizado/para/llama-7b-hf"
ADAPTER = "dominguesm/alpaca-lora-ptbr-7b"
quantization = BitsAndBytesConfig(load_in_8bit=True)
tokenizer = AutoTokenizer.from_pretrained(BASE_MODEL, use_fast=False)
base = AutoModelForCausalLM.from_pretrained(
BASE_MODEL,
quantization_config=quantization,
device_map="auto",
)
model = PeftModel.from_pretrained(base, ADAPTER)
model.eval()
def prompt_alpaca(instruction: str, context: str | None = None) -> str:
if context:
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}
### 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:"""
prompt = prompt_alpaca("Dê três cuidados básicos ao revisar um contrato.")
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.inference_mode():
sequence = model.generate(
**inputs,
do_sample=False,
num_beams=4,
max_new_tokens=256,
eos_token_id=tokenizer.eos_token_id,
)[0]
answer_ids = sequence[inputs["input_ids"].shape[1]:]
print(tokenizer.decode(answer_ids, skip_special_tokens=True).strip())No repositório, ofereço ainda outro caminho: um app Gradio que baixa de ALPACA_MODEL_URI um arquivo model.bin quantizado e já utilizável por llama_cpp.Llama. Esse artefato GGML combina a linhagem da base e do ajuste para execução em llama.cpp; não é o pequeno adaptador PEFT. O app transmite a geração em fluxo, limita a resposta a 150 tokens e interrompe em ### ou no início do preâmbulo. Embora a interface use um componente de chat, somente a última mensagem é inserida no prompt. Não há histórico conversacional, campo de contexto, filtro de conteúdo ou memória entre turnos. O próprio README estimava 30 a 60 segundos por resposta no hardware do Space [1].
Resultados e aprendizados
Nos exemplos que publiquei, o modelo aprendeu o formato e produziu português fluente em tarefas curtas, mas também deixou claro que fluência não é factualidade.
Na pergunta sobre alpacas, a saída as chama de “uma espécie de camelo”, quando são camelídeos da espécie Vicugna pacos, e troca fibra por “cabelos”. A afirmação de uso para leite e carne é apresentada sem contexto e não representa sua finalidade produtiva predominante. A resposta é legível, mas biologicamente imprecisa.
Na explicação da Lei Maria da Penha, o modelo inventa que Maria da Penha foi assassinada em 1973 e que a lei foi aprovada em 1985. Ela sobreviveu a tentativas de homicídio, uma delas em 1983, e a Lei nº 11.340 foi sancionada em 2006. Registrei esses erros junto ao exemplo publicado. É um caso forte de alucinação confiante em um tema jurídico sensível.
Para CaCl2, a massa molar de 110,98 g/mol está correta: . Já na tarefa sobre anatomia da abelha, a resposta gera quatro perguntas, mas três perguntam por “órgãos sensoriais” em cabeça, tórax e abdômen mesmo quando o texto só os situa na cabeça. Ela satisfaz a contagem e falha na fidelidade à passagem. No contrato, a saída praticamente copia o texto, sem organizar os pontos-chave em dever de assistência, acesso documental, confidencialidade e condição de consentimento.
No exemplo do app sobre evolução, obtive concordâncias quebradas, uma atribuição da primatologia a “físicos” e uma formulação confusa sobre seleção natural. Em conjunto, os casos comprovam capacidade de continuação no template, não uma avaliação abrangente. Uso jurídico, médico, financeiro ou educacional exige recuperação de fontes, checagem externa, testes adversariais e supervisão humana.
Licenças, atribuição e limites de uso
Não existe uma única licença capaz de apagar as obrigações de toda a cadeia:
- Publiquei o texto de
DominguesM/alpaca-lora-ptbr-7bsob CC BY-SA 4.0 e o adaptador no Hugging Face sob CC BY 4.0. Essa diferença precisa ser considerada por artefato, preservando atribuição e, quando aplicável, compartilhamento pela mesma licença [1][2]. - O dataset PT-BR atual declara CC BY-NC 4.0. Stanford também licencia os dados Alpaca sob CC BY-NC 4.0 e restringe seu uso a pesquisa não comercial; Alpaca-Cleaned repete a restrição não comercial em seu repositório [3][5][6].
- O LLaMA v1 foi liberado sob licença própria não comercial, mediante acesso concedido pela Meta. O adaptador não inclui a base e não concede direito de obtê-la, redistribuí-la ou ignorar seus termos [9].
- O modelo OPUS-MT declara CC BY 4.0, mas seu corpus agrega fontes OPUS com proveniências próprias [4][10].
- Um binário GGML fundido pode incorporar pesos da base, ao contrário do adaptador LoRA isolado. Sua distribuição e uso precisam ser avaliados sob a licença do LLaMA v1, além das licenças dos dados e do ajuste.
Logo, a licença CC BY do adaptador não transforma o sistema completo em produto comercial livre. Recomendo uso de pesquisa, não comercial, com atribuição a Stanford Alpaca, Alpaca-Cleaned quando efetivamente usado, OPUS-MT, LLaMA, LoRA/PEFT, Alpaca-LoRA e ao adaptador PT-BR. Estas são informações técnicas sobre a publicação, não aconselhamento jurídico.
O que realizei e como reproduzir hoje
Com o Alpaca-LoRA PT-BR 7B, demonstrei a ideia central de adaptação eficiente: 4,19 milhões de parâmetros, cerca de 0,0622% da base, são suficientes para deslocar um LLaMA 7B em direção ao seguimento de instruções em português. A combinação de base int8, acumulação de gradiente e LoRA tornou um experimento antes restrito a múltiplas GPUs muito mais acessível.
Em 2023, não documentei a edição exata do dataset, preservei no notebook de tradução uma célula que preciso corrigir, registrei valores diferentes no README e na configuração e limitei a avaliação à perda interna e a poucos exemplos. As saídas que publiquei mostram simultaneamente utilidade, erros de tradução, baixa fidelidade ao contexto e alucinações factuais. Hoje, eu o apresento como artefato de pesquisa parcialmente reproduzível, não como assistente validado para produção; para reproduzi-lo, é necessário fixar a edição dos dados e das dependências, corrigir e registrar a tradução e ampliar a avaliação.
Referências
- Domingues, M. Alpaca-LoRA-PTBR: Low-Rank LLaMA Instruct-Tuning. GitHub, 2023. https://github.com/DominguesM/alpaca-lora-ptbr-7b
- Domingues, M. dominguesm/alpaca-lora-ptbr-7b: model card, adapter_config.json e pesos PEFT. Hugging Face, 2023. https://huggingface.co/dominguesm/alpaca-lora-ptbr-7b
- Domingues, M. Alpaca-Cleaned-PTBR dataset card. Hugging Face, 2023. https://huggingface.co/datasets/dominguesm/alpaca-data-pt-br
- Helsinki-NLP. opus-mt-tc-big-en-pt model card. Hugging Face, 2022. https://huggingface.co/Helsinki-NLP/opus-mt-tc-big-en-pt
- Taori, R. et al. Stanford Alpaca: An Instruction-following LLaMA Model. 2023. https://github.com/tatsu-lab/stanford_alpaca
- Gururise. AlpacaDataCleaned. GitHub, 2023. https://github.com/gururise/AlpacaDataCleaned
- Hu, E. J. et al. LoRA: Low-Rank Adaptation of Large Language Models. arXiv:2106.09685, 2021. https://arxiv.org/abs/2106.09685
- Wang, E. J.; Dettmers, T. et al. Alpaca-LoRA. GitHub, 2023. https://github.com/tloen/alpaca-lora
- Touvron, H. et al. LLaMA: Open and Efficient Foundation Language Models. arXiv:2302.13971, 2023. https://arxiv.org/abs/2302.13971
- Tiedemann, J.; Thottingal, S. OPUS-MT: Building open translation services for the World. EAMT, 2020, p. 479-480. https://aclanthology.org/2020.eamt-1.61/
BibTeX
@misc{domingues2023alpacaloraptbr,
author = {Maicon Domingues},
title = {Alpaca-LoRA-PTBR: Low-Rank LLaMA Instruct-Tuning},
year = {2023},
howpublished = {GitHub repository},
url = {https://github.com/DominguesM/alpaca-lora-ptbr-7b}
}
@misc{taori2023alpaca,
author = {Rohan Taori and Ishaan Gulrajani and Tianyi Zhang and Yann Dubois and Xuechen Li and Carlos Guestrin and Percy Liang and Tatsunori B. Hashimoto},
title = {Stanford Alpaca: An Instruction-following LLaMA Model},
year = {2023},
howpublished = {GitHub repository},
url = {https://github.com/tatsu-lab/stanford_alpaca}
}
@article{hu2021lora,
author = {Edward J. Hu and Yelong Shen and Phillip Wallis and Zeyuan Allen-Zhu and Yuanzhi Li and Shean Wang and Lu Wang and Weizhu Chen},
title = {LoRA: Low-Rank Adaptation of Large Language Models},
journal = {arXiv preprint arXiv:2106.09685},
year = {2021},
url = {https://arxiv.org/abs/2106.09685}
}
@article{touvron2023llama,
author = {Hugo Touvron and Thibaut Lavril and Gautier Izacard and others},
title = {LLaMA: Open and Efficient Foundation Language Models},
journal = {arXiv preprint arXiv:2302.13971},
year = {2023},
url = {https://arxiv.org/abs/2302.13971}
}
@inproceedings{tiedemann-thottingal-2020-opus,
author = {Jörg Tiedemann and Santhosh Thottingal},
title = {OPUS-MT: Building open translation services for the World},
booktitle = {Proceedings of the 22nd Annual Conference of the European Association for Machine Translation},
year = {2020},
pages = {479--480},
url = {https://aclanthology.org/2020.eamt-1.61/}
}