Voltar ao blog

Fine-Tuning de Modelos de Linguagem (LLMs) com Hugging Face e PyTorch

Fine-Tuning de Modelos de Linguagem (LLMs) com Hugging Face e PyTorch

Introdução

No contexto atual de inteligência artificial, Modelos de Linguagem de Grande Porte (LLMs) como GPT, LLaMA e Falcon têm revolucionado tarefas de análise de texto, tradução, geração de código e muito mais. No entanto, embora esses modelos sejam poderosos, muitas vezes queremos especializá-los em tarefas específicas ou adaptá-los a dados de domínio restrito (por exemplo, análises jurídicas, atendimento automatizado, etc.). É aí que entra o fine-tuning (ou ajuste fino): a técnica de continuar o treinamento de um modelo pré-treinado em um novo conjunto de dados específico.

Neste tutorial prático, você aprenderá passo a passo como preparar dados, carregar um modelo pré-treinado do Hugging Face e realizá-lo o fine-tuning usando PyTorch. Vamos também abordar técnicas avançadas de ajuste fino eficiente em parâmetros, como LoRA (Low-Rank Adaptation) e a biblioteca PEFT do Hugging Face. Ao final, discutiremos como avaliar o desempenho do modelo ajustado. Todo o conteúdo será ilustrado com exemplos de código e analogias simples para facilitar a compreensão.

Ao longo deste artigo, você aprenderá a:

  • Preparar e carregar um conjunto de dados apropriado para treinamento.
  • Configurar o ambiente, instalar bibliotecas e carregar modelos pré-treinados e seus tokenizadores.
  • Realizar fine-tuning básico de um LLM usando o Trainer do Hugging Face ou loops de treinamento do PyTorch.
  • Implementar LoRA, uma técnica de ajuste fino eficiente, usando a biblioteca PEFT.
  • Avaliar o modelo ajustado com métricas simples e geração de exemplos de texto.

Vamos começar pela preparação dos dados!

Preparação de Dados para Fine-Tuning

Antes de treinar o modelo, precisamos definir que tipo de dados ele verá. Para tasks de geração de texto ou instrução (instruction tuning), normalmente estruturamos os dados como pares de entrada-saída (prompt-completion) ou como conversas com “usuário” e “assistente”.

Fontes de Dados e Formatos Comuns

O Hugging Face oferece a biblioteca datasets para facilitar o download e pré-processamento de muitos conjuntos de dados públicos. Por exemplo, podemos usar o conjunto wikitext-2 para tarefas de modelagem de linguagem ou qualquer outro texto. Em Python, basta:

from datasets import load_dataset # Exemplo: carregar conjunto wikitext-2 para modelagem de linguagem dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train") print(dataset[0])

Esse comando baixa o conjunto wikitext e retorna um objeto que contém splits como train e validation. Veja que load_dataset("imdb"), por exemplo, já nos retorna diretamente dicionários com "train" e "test" prontos para classificação de resenhas (huggingface.co).

Outro exemplo: suponha que temos um conjunto de frases traduzidas para “linguagem do Yoda” (isso existe em um dataset público chamado dvgodoy/yoda_sentences). Podemos carregá-lo assim:

dataset = load_dataset("dvgodoy/yoda_sentences", split="train") print(dataset)

Isso retorna algo como:

Dataset({
    features: ['sentence', 'translation', 'translation_extra'],
    num_rows: 720
})

Ele tem 720 exemplos, cada um com ‘sentence’ (inglês original), ‘translation’ e ‘translation_extra’ (versão Yoda) (huggingface.co).

Pré-processamento e Tokenização

Após carregar o conjunto de dados, é comum pré-processá-lo antes de treinar. Se for modelagem de linguagem simples (por exemplo, texto contínuo), poderíamos concatenar frases. Mas para tarefas de instrução/chat, precisamos de um formato específico. Por exemplo, o SFTTrainer do Hugging Face aceita datasets em formatos de prompt-completion ou conversacional (huggingface.co):

  • Formato padrão (prompt-completion):
    {"prompt": "Pergunta do usuário", "completion": "Resposta esperada"}
  • Formato conversacional:
    {"messages":[ {"role": "user", "content": "Pergunta do usuário"}, {"role": "assistant", "content": "Resposta do modelo"} ]}

No exemplo do Yoda, podemos renomear e formatar os campos para torná-lo compatível. Usando o dataset acima:

# Supondo que usamos a tradução 'translation_extra' como resposta final dataset = dataset.rename_column("sentence", "prompt") dataset = dataset.rename_column("translation_extra", "completion") # Função para converter cada exemplo no formato conversacional def format_dataset(examples): return {"messages": [ {"role": "user", "content": examples["prompt"]}, {"role": "assistant", "content": examples["completion"]} ]} dataset = dataset.map(format_dataset, remove_columns=["prompt","completion","translation"]) print(dataset[0])

Isso transforma cada linha para um par user/assistant (huggingface.co). Com isso, estamos prontos para alimentar o modelo. Note que cada exemplo agora possui um campo "messages" contendo a conversa (semelhante a templates de bate-papo).

Outra etapa crucial é a tokenização: converter texto em tokens que o modelo entende. Para isso, carregamos o tokenizador correspondente ao modelo pré-treinado:

from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("gpt2")

O tokenizador do Hugging Face cuidará de dividir o texto em IDs de tokens e aplicar padding ou truncamento conforme necessário. Por exemplo:

tokens = tokenizer("Olá, como vai?", padding="max_length", truncation=True, max_length=60) print(tokens)

Sempre use o mesmo tokenizador do modelo pré-treinado para manter a consistência do vocabulário (huggingface.co). Em resumo, nessa fase preparamos os dados (coleta, limpeza e formatação) e garantimos a consistência via tokenização.

Configuração do Ambiente e Carregamento do Modelo

Agora que temos os dados prontos, devemos configurar o ambiente e carregar o modelo LLM.

Instalação de Bibliotecas

Certifique-se de ter as bibliotecas necessárias instaladas. Os pacotes principais são:

Exemplo de instalação via pip:

pip install transformers datasets torch accelerate peft bitsandbytes

É importante usar versões compatíveis. Por exemplo, a quantização 4-bit (BITS256) funciona bem com versões recentes do PyTorch e https://accelerate.

Carregando o Modelo Pré-Treinado

No Hugging Face, podemos carregar um modelo de linguagem causal (para geração de texto) usando AutoModelForCausalLM:

from transformers import AutoModelForCausalLM model_name = "gpt2" # por exemplo model = AutoModelForCausalLM.from_pretrained(model_name) print(model.config)

Isso carrega os pesos e configurações do GPT-2 (ou outro modelo de sua escolha). Você também deve carregar o tokenizador correspondente:

tokenizer = AutoTokenizer.from_pretrained(model_name)

Agora temos o model e o tokenizer prontos.

Quantização (Opcional)

Grandes modelos (vários bilhões de parâmetros) podem exigir muita memória. Uma solução é aplicar quantização: converter pesos de 32-bit para 4-bit, por exemplo, para reduzir drasticamente o tamanho do modelo. A biblioteca bitsandbytes facilita isso. Por exemplo:

from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_use_double_quant=True, bnb_4bit_compute_dtype=torch.float16 ) model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config, device_map="auto")

Essa configuração carrega o modelo em precisão de 4-bit (NF4), que reduz o uso de memória em cerca de 8 vezes (huggingface.co). Assim, modelos grandes podem até caber em uma única GPU. No exemplo da Phi-3 (3.8B), mesmo quantizado ele ocupava ~2 GB de VRAM (huggingface.co) (huggingface.co).

Importante: um modelo quantizado não pode ser treinado diretamente, pois as camadas quantizadas (Linear4bit) ficam congeladas. Para treinar, usaremos opções como LoRA (ver próximo tópico).

Fine-Tuning de LLMs com Hugging Face e PyTorch

Com dados e modelo prontos, executamos o fine-tuning. Existem várias maneiras de fazer isso:

  • Usando o Trainer do Hugging Face (transformers.Trainer).
  • Usando o SFTTrainer da biblioteca TRL (específico para instruções/chat).
  • Escrevendo um loop de treinamento manual em PyTorch.

Exemplo com Trainer do Hugging Face

Para tarefas como classificação ou geração tradicional, podemos usar o Trainer. Aqui vai um exemplo simples de treinamento:

from transformers import Trainer, TrainingArguments # 1. Pré-processar: tokenizar os dados def tokenize_function(examples): return tokenizer(examples["text"], truncation=True, padding="max_length", max_length=128) tokenized_dataset = dataset.map(tokenize_function, batched=True, remove_columns=["text"]) # 2. Definir argumentos de treinamento training_args = TrainingArguments( output_dir="out", num_train_epochs=3, per_device_train_batch_size=4, evaluation_strategy="epoch", logging_strategy="epoch", save_total_limit=2, fp16=True ) # 3. Instanciar o Trainer trainer = Trainer( model=model, args=training_args, train_dataset=tokenized_dataset["train"], eval_dataset=tokenized_dataset["validation"] ) # 4. Treinar trainer.train()

Nesse exemplo (baseado na documentação Hugging Face), Trainer automatiza o loop de treinamento: cálculo do loss, backpropagation e atualização de parâmetros (huggingface.co) (huggingface.co). O código acima é uma versão simplificada do tutorial oficial. Basta chamar trainer.train() e acompanhar o progresso.

Durante o treinamento, você pode monitorar métricas como perda (loss) e precisão (accuracy) usando compute_metrics se for tarefa de classificação (huggingface.co). No caso de modelos de linguagem causal (geração), o objetivo é minimizar a entropia cruzada (NLL) em cada token, ou a perplexidade resultante.

AutoModelForCausalLM e Fine-Tuning

Para ajuste de um LLM causal (ex. GPT-2) usando Trainer, substitua AutoModelForSequenceClassification por AutoModelForCausalLM e forneça um dataset de textos. Por exemplo, usando o Wikitext:

from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("gpt2") train_dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train") tokenized = train_dataset.map(lambda x: tokenizer(x["text"], truncation=True, max_length=50), batched=True) trainer = Trainer(model=model, args=training_args, train_dataset=tokenized) trainer.train()

Isso treinará o GPT-2 para gerar o texto do Wikitext. Note que o Trainer trata da pré-tokenização e do batching.

SFTTrainer para Instruções/Chat

Para cenários de instruction tuning (modelos de chat), o Hugging Face oferece o SFTTrainer da biblioteca TRL. O SFTTrainer simplifica o fine-tuning supervisionado com formatos de dados conversacionais (huggingface.co). Por exemplo:

from trl import SFTTrainer trainer = SFTTrainer( model="Qwen/Qwen3-0.6B", # modelo no Hugging Face Hub train_dataset=load_dataset("trl-lib/Capybara", split="train") ) trainer.train()

Esse treina o modelo Qwen3-0.6B no dataset Capybara (um exemplo de instruções). Repare como basta informar o modelo e o dataset; o SFTTrainer aplica automaticamente o template de conversa, se necessário (huggingface.co). Para usar o SFTTrainer, molde seu dataset no formato esperado (prompt/completion ou messages) como mostrado anteriormente (huggingface.co) (huggingface.co).

Pontos-Chave do Fine-Tuning

  • Congelar Camadas: Se o modelo for muito grande, às vezes congelamos camadas iniciais e treinamos só o final (ou um adaptador). Isso acelera e consome pouca memória.
  • Hiperparâmetros: Ajuste learning rate, batch size, número de épocas e outras TrainingArguments para seu caso.
  • Mixed Precision: Use fp16=True (treinamento em 16 bits) se GPU permitir, reduzindo memória e tempo.

O objetivo final é minimizar a perda (cross-entropy) entre a saída real e a previsão do modelo, integrando o conhecimento prévio com o novo domínio de dados (huggingface.co). O processo completo copia a transferência de aprendizado: mantemos o conhecimento geral do pré-treinamento e o adaptamos ao nosso domínio específico (huggingface.co).

Técnicas Avançadas: LoRA (Low-Rank Adaptation) e PEFT

Em muitos casos, o fine-tuning completo de um LLM (atualizar TODOS os bilhões de pesos) é muito caro em tempo e memória. Aqui entram técnicas PEFT (Parameter-Efficient Fine-Tuning) para atualizar apenas parte do modelo. LoRA é uma das abordagens mais populares nesse contexto (www.digitalocean.com) (www.digitalocean.com).

O que é LoRA?

Imagine o modelo LLM pré-treinado como um grande livro. Ajustar tudo seria ler o livro inteiro de novo. LoRA, em vez disso, nos permite ler apenas os pontos destacados. Ou seja, LoRA introduz matrizes pequenas e treináveis dentro das camadas do modelo, mantendo os pesos originais congelados.

Na prática, LoRA altera pouco o modelo original: ela adiciona dois pequenos pesos (matrizes de rank baixo) para cada peso original, de modo que o peso final passa a ser:

$$ W' = W + A \times B $$

  • (W): matriz original (congelada).
  • (A) e (B): matrizes pequenas, ajustáveis (LoRA).
  • (\Delta W = A \times B) é a atualização de pequena rank.

Dessa forma, apenas os parâmetros em (A) e (B) são treinados. Como descrito em tutoriais, isso reduz drasticamente os custos de treinamento e memória, pois atualizamos apenas uma fração dos parâmetros (www.digitalocean.com). A citação a seguir resume:

“LoRA introduz pequenas matrizes treináveis que aproximam as atualizações de pesos usando decomposição de baixo rank. Isso reduz drasticamente os custos de treinamento, o uso de memória GPU e o tempo para ajustar modelos para novas tarefas.” (www.digitalocean.com)

Em outras palavras, LoRA faz um ajuste fino parametricamente eficiente: “adicionamos algumas camadas inteligentes num cérebro já treinado, em vez de reprogramar tudo” (www.digitalocean.com). Na analogia do livro, ler apenas os trechos importantes é muito mais rápido que reler tudo (www.digitalocean.com).

Implementando LoRA com Hugging Face PEFT

O Hugging Face criou o pacote PEFT para facilitar LoRA e outras abordagens. O fluxo básico é:

  1. Carregue o modelo (pode ser quantizado ou não).
  2. Prepare o modelo para treinamento (função prepare_model_for_kbit_training se usando quantizado).
  3. Defina LoraConfig com parâmetros como rank (r), alpha, dropout e quais módulos do modelo serão adaptados.
  4. Chame get_peft_model(model, config), que retorna um modelo modificado com LoRA.

Exemplo de código (seguindo o tutorial da Hugging Face):

from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training # Se usarmos um modelo quantizado, prepará-lo para treino: model = prepare_model_for_kbit_training(model) # Configurar LoRA config = LoraConfig( r=8, # rank baixo lora_alpha=16, target_modules=['q_proj', 'v_proj'], # camadas a serem adaptadas (exemplo) lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, config) print(model)

Após isso, o model possui os adaptadores LoRA embutidos. As camadas lineares originais continuam congeladas; apenas os parâmetros de LoRA (as matrizes A e B) serão treinados. Como observado na documentação, isso faz com que o total de parâmetros treináveis seja apenas cerca de 1% do original (huggingface.co)! Ou seja, hinja enorme redução no custo de ajuste.

Na prática, agora você executa o treino normalmente (com Trainer.train() ou outro método), mas somente LoRA estará aprendendo. Isso permite treinar em GPUs menores ou com menos dados. Por exemplo, veja os dois trechos:

“Low-rank adapters can be attached to each... setting up LoRA adapters on a quantized model drastically reduces the total number of trainable parameters to just 1% (or less) of its original size.” (huggingface.co)

Essa mudança quase invisível é poderosa. A integração com PEFT torna o processo simples e compatível com o ecossistema Transformers.

Mais Sobre LoRA e Variações

  • Integração com Treinador: O modelo modificado por get_peft_model pode ser passado para o Trainer normalmente. Não há necessidade de treinar manualmente loss de LoRA; tudo é feito sob o capô.
  • QLoRA: Variante que combina LoRA com quantização em 4-bit (quantização ultra eficiente + LoRA). Básico segue o mesmo princípio, só que os pesos originais são ultra-congelados.
  • Vantagens: redução de memória, tempo e custo computacional. Ideal quando temos recurso limitado ou quando só queremos especializar o modelo sem criar um novo do zero.
  • Limitações: como qualquer fine-tuning, se os dados forem muito pequenos ou fora do domínio, talvez não haja ganho significativo. LoRA ainda requer bom preparo de dados e alambricação cuidadosa do treinamento.

Em resumo, LoRA e PEFT são recomendados quando o ajuste fino total é impraticável. Eles se tornam padrão para muitos desenvolvedores que querem customizar LLMs sem precisar de clusters de GPUs.

Avaliação do Modelo Fine-Tuned

Após treinar o modelo (básico ou com LoRA), precisamos avaliar seu desempenho. A avaliação depende da tarefa, mas para LLMs de geração podemos:

  • Gerar texto em alguns prompts de teste e verificar qualitativamente.
  • Calcular perplexidade ou perda de validação como métrica genérica.
  • Usar métricas de tarefa (ex: BLEU, ROUGE, acurácia do conteúdo, etc.) se houver rótulos de “resposta certa”.

Exemplo de Avaliação Prática

Se usamos o Trainer, temos funções como trainer.evaluate() que calculam a perda média no conjunto de validação. Também podemos manualmente verificar perplexidade:

eval_results = trainer.evaluate() print(f"Loss (valid): {eval_results['eval_loss']:.2f}")

De maneira simples, perplexidade (exp do loss) avalia quão bem o modelo prevê os dados; quanto menor, melhor.

Para geração de texto, podemos fazer um teste manual:

model.eval() prompt = "Pergunta de exemplo para o modelo:" inputs = tokenizer(prompt, return_tensors="pt").to(model.device) output_ids = model.generate(**inputs, max_new_tokens=50) print("Resposta do modelo:", tokenizer.decode(output_ids[0], skip_special_tokens=True))

Esse código faz o modelo gerar 50 tokens a partir de um prompt. Você pode inspecionar a saída e julgar qualitativamente se faz sentido.

Também é comum comparar o LoRA com o modelo sem LoRA (ou comparar contra vs. sem fine-tuning). Por exemplo, no blog de alguns, mostram graciosamente que sem LoRA o modelo mantém-se básico, mas com LoRA as respostas ficam mais coerentes ao contexto de treinamento.

Métricas e Logs

Durante o treinamento, o Trainer já loga algumas estatísticas (perda, aprendizados) e você pode configurar outras métricas. Por exemplo, Trainer permite calcular acurácia em tarefas de classificação, ou outras métricas customizadas ao fim de cada época (huggingface.co).

No contexto de LLMs, alguns pontos para observar nos logs:

  • Loss de Treino/Validação: é o principal indicador. Deve descer progressivamente e estabilizar.
  • Learning Rate: acompanhar a curva (normalmente um scheduler faz warm-up/resfriamento).
  • Tokens Processados: saber quantos tokens foram vistos (útil para diagnóstico de overfitting).
  • Perplexidade Estimada: é comum converter loss em perplexidade = exp(loss) para interpretar melhor.

Finalmente, lembre-se de avaliar externamente: se possível, dê prompts reais do negócio (ou métricas específicas do domínio) e veja se o modelo atende melhor após o fine-tuning.

Conclusão

Ajustar finamente LLMs com Hugging Face e PyTorch envolve várias etapas, desde preparar os dados até executar o treinamento propriamente dito. Vimos como carregar dados (usando a biblioteca Datasets), configurar o modelo (pré-treinamento, quantização) e treinar (via Trainer ou TRL SFTTrainer) (huggingface.co) (huggingface.co).

Também exploramos técnicas avançadas como LoRA (Low-Rank Adaptation), que permite personalizar um LLM com muito menos parâmetros treináveis (huggingface.co) (www.digitalocean.com). Em suma:

  • O fine-tuning tradicional ajusta todos os pesos do modelo (computacionalmente caro).
  • LoRA/PEFT atualiza apenas camadas resumidas, quase como “ler apenas trechos importantes de um livro” (www.digitalocean.com). Isso economiza memória e custo, mantendo a performance.

Ao final, sempre avalie o modelo ajustado para garantir que ele realmente aprendeu o novo domínio. Use métricas numéricas (perplexidade, acurácia) e testes de geração de exemplo para validar.

Próximos passos: você pode experimentar outros formatos de fine-tuning, como fine-tuning batched, prefix tuning, ou até treinamentos com RLHF (Reinforcement Learning from Human Feedback). Fique de olho nas novidades do Hugging Face e da comunidade; técnicas como LoRA continuam evoluindo (p.ex., QLoRA, adapter composition) (www.digitalocean.com) (www.digitalocean.com). Em resumo, a personalização de LLMs tornou-se muito mais acessível e poderosa graças a essas ferramentas – vale a pena dominar essas técnicas!

Inscrever agora para a próxima turma do DevClub?

Que tal não perder esta oportunidade e já se inscrever agora para a próxima turma do DevClub?