Capítulo 2: PEFT vs Fine-Tuning Completo
Entendendo os Trade-offs
Escolher entre PEFT e fine-tuning completo exige compreender seus respectivos pontos fortes e limitações.
Comparação Abrangente
| Aspecto | Fine-Tuning Completo | PEFT |
|---|---|---|
| Memória | 4-6x o tamanho do modelo | 0,1-0,5x o tamanho do modelo |
| Velocidade | Mais lento | 2-3x mais rápido |
| Teto de Qualidade | Mais alto | Um pouco menor |
| Risco de Esquecimento | Alto | Nenhum |
| Hardware | GPUs caras | GPUs de consumidor |
| Troca de Adaptador | Não | Sim |
| Overhead na Inferência | Nenhum | Mínimo (ou merge) |
Comparação de Memória
Detalhamento
def calculate_memory(params_billions, method):
"""Calculate approximate training memory."""
params = params_billions * 1e9
if method == "full_ft":
# Weights + Gradients + Optimizer (2 states) + Activations
weights = params * 2 # bf16
gradients = params * 2
optimizer = params * 4 * 2 # fp32, 2 states
activations = params * 0.5 # Rough estimate
return (weights + gradients + optimizer + activations) / 1e9
elif method == "lora":
# Frozen weights + small LoRA gradients/optimizer
weights = params * 2
lora_params = params * 0.01 # ~1% trainable
lora_training = lora_params * (2 + 2 + 8) # grad + optimizer
return (weights + lora_training) / 1e9
elif method == "qlora":
# 4-bit weights + LoRA training
weights = params * 0.5 # 4-bit
lora_params = params * 0.01
lora_training = lora_params * (2 + 2 + 8)
return (weights + lora_training) / 1e9
# Results for 7B model
print(f"Full FT: {calculate_memory(7, 'full_ft'):.1f} GB") # ~77 GB
print(f"LoRA: {calculate_memory(7, 'lora'):.1f} GB") # ~15 GB
print(f"QLoRA: {calculate_memory(7, 'qlora'):.1f} GB") # ~4 GB
Comparação de Qualidade
Resultados Empíricos
Pesquisas mostram consistentemente que PEFT atinge qualidade comparável:
| Tarefa | Fine-Tuning Completo | LoRA | Diferença |
|---|---|---|---|
| Classificação de Texto | 94,2% | 93,8% | -0,4% |
| Sumarização | 45,3 ROUGE | 44,9 ROUGE | -0,9% |
| Seguir Instruções | 8,2/10 | 8,0/10 | -2,4% |
| Geração de Código | 67,1% | 65,8% | -1,9% |
Quando o PEFT Fica Aquém
O PEFT pode ter desempenho inferior em:
- Mudanças de domínio significativas: Pré-treinamento → domínio muito especializado
- Novos formatos de saída: Muito diferentes de qualquer dado de treinamento
- Qualidade máxima possível: Quando cada 0,1% importa
- Novos comportamentos complexos: Padrões profundamente embutidos
# Example: PEFT might struggle with
# 1. Medical model from general LLM (major domain shift)
# 2. Entirely new language not in pretraining
# 3. Competition benchmarks where every point counts
Comparação de Velocidade de Treinamento
Métricas de Throughput
| Método | Tempo por Época | Velocidade Relativa |
|---|---|---|
| Fine-Tuning Completo | 60 min | 1x |
| LoRA | 25 min | 2,4x |
| QLoRA | 30 min | 2x |
Por que o PEFT é Mais Rápido
- Menos cálculos de gradiente: Só calcula gradientes para parâmetros treináveis
- Menor uso de banda de memória: Menos bytes para transferir
- Atualizações menores do otimizador: Menos estados para manter
# Gradient computation scales with trainable parameters
# Full FT: 7 billion gradient computations
# LoRA: 70 million gradient computations (100x fewer)
Comparação de Flexibilidade
PEFT: Múltiplos Adaptadores
# Load base model once
base_model = AutoModelForCausalLM.from_pretrained("llama-3.1-8b")
# Create different adapters
adapters = {
"customer_support": "adapters/support/",
"code_review": "adapters/code/",
"translation": "adapters/translate/",
}
# Runtime adapter switching
for task, adapter_path in adapters.items():
model = PeftModel.from_pretrained(base_model, adapter_path)
# Use model for task
model.unload() # Remove adapter
Fine-Tuning Completo: Modelos Separados
# Each task requires loading a different full model
models = {
"customer_support": "models/support-full/", # 15GB
"code_review": "models/code-full/", # 15GB
"translation": "models/translate-full/", # 15GB
}
# Total: 45GB storage, high switching cost
Comparação de Inferência
PEFT com Adaptadores
# Slight overhead from adapter computation
# Latency: ~5-10% slower per token
# Option 1: Keep adapter separate
model = PeftModel.from_pretrained(base, adapter)
output = model.generate(input_ids)
# Option 2: Merge for zero overhead
model = model.merge_and_unload() # No adapter overhead
output = model.generate(input_ids)
PEFT Mesclado = Velocidade de Fine-Tuning Completo
Após a mesclagem:
W_merged = W_base + B × A # One-time computation
# Inference is identical to full fine-tuned model
Análise de Custos
Custos de Treinamento
| Método | GPU Necessária | Tempo | Custo (On-Demand) |
|---|---|---|---|
| Fine-Tuning Completo (7B) | A100-80GB | 4 h | $24 |
| LoRA (7B) | A100-40GB | 1,5 h | $6 |
| QLoRA (7B) | RTX 4090 | 2 h | $2 |
Considerações de Longo Prazo
Cenário: Fine-tuning para 10 tarefas diferentes
Fine-Tuning Completo:
- 10 execuções de treinamento em GPUs caras
- 10 modelos completos para armazenar (150GB)
- Carregar modelo diferente para cada tarefa
PEFT:
- 10 execuções de treinamento em GPUs mais baratas
- 1 modelo base + 10 adaptadores (15GB + 200MB)
- Troca rápida de adaptador
Framework de Decisão
Escolha PEFT Quando:
✅ Memória da GPU é limitada (< 40GB)
✅ Precisa experimentar rapidamente
✅ Precisa de variações para múltiplas tarefas
✅ Deseja preservar as capacidades do modelo base
✅ Custo é um fator importante
✅ Produção exige troca de adaptadores
Escolha Fine-Tuning Completo Quando:
✅ Qualidade máxima é crítica
✅ Mudança de domínio significativa é necessária
✅ Recursos não são uma restrição
✅ Precisa de modelo para um único propósito
✅ Vai fazer deploy como modelo mesclado de qualquer forma
✅ Pesquisa exige controle total
Estratégias Híbridas
Comece com PEFT, Evolua se Necessário
# Phase 1: Quick experimentation with LoRA
lora_model = train_with_lora(dataset_small)
evaluate(lora_model) # Check if quality is sufficient
# Phase 2: If PEFT isn't enough, try full FT
if quality_gap > threshold:
full_ft_model = train_full(dataset_full)
PEFT para Iteração, Fine-Tuning Completo para Finalização
# Iterate on data/hyperparameters with PEFT (fast, cheap)
for experiment in experiments:
model = train_lora(config=experiment)
score = evaluate(model)
if score > best_score:
best_config = experiment
# Final training with full FT using best settings
final_model = train_full(config=best_config)
Recomendações Práticas
Para a Maioria dos Casos: Comece com PEFT
# Default recommendation for most practitioners
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_dropout=0.05,
)
model = get_peft_model(base_model, config)
# Train and evaluate
Para Produção: Considere Mesclar
# After training, merge for deployment
model = model.merge_and_unload()
model.save_pretrained("./production-model")
# Now behaves exactly like full fine-tuned model
Principais Conclusões
✅ PEFT alcança 90-99% da qualidade do fine-tuning completo com 80-95% menos memória
✅ Fine-tuning completo tem teto de qualidade maior, mas a um custo significativo
✅ PEFT permite troca de adaptadores; fine-tuning completo gera modelos fixos
✅ PEFT mesclado tem desempenho de inferência idêntico ao fine-tuning completo
✅ Comece com PEFT, só faça fine-tuning completo se necessário
✅ Considere o custo total incluindo armazenamento, troca e iteração
Próximos Passos
Continue para Capítulo 3: Visão Geral dos Métodos PEFT para explorar todas as técnicas PEFT disponíveis.
Verificação Rápida
Antes de seguir, certifique-se de que pode responder:
- Quanta memória o PEFT economiza em comparação ao fine-tuning completo?
- Quando o fine-tuning completo pode superar o PEFT?
- Qual a vantagem dos adaptadores PEFT mesclados?