Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Entender a matemática de LoRA: W_novo = W + B × A com r << d
Explicar por que 0,1% dos parâmetros treináveis é suficiente para especialização
Comparar LoRA vs full fine-tuning em custo, qualidade e velocidade
Entender QLoRA e quando usar em hardware limitado
Decidir quando LoRA é a ferramenta certa vs API-based fine-tuning
Por que isso importa
Fine-tuning completo de um modelo de 7B parâmetros requer ~112GB de VRAM (modelo + gradientes + otimizador). Isso é inacessível para a maioria dos times. LoRA (Low-Rank Adaptation) permite fazer o mesmo com ~14GB — habilitando fine-tuning em consumer GPUs ou instâncias menores de cloud.
Conceitos Fundamentais
A Matemática de LoRA
Fundamento: LoRA e PEFT
Fine-tuning completo ajusta todos os pesos do modelo — para um modelo de 7B parâmetros, são 7 bilhões de números a atualizar, exigindo ~112 GB de VRAM. LoRA parte da hipótese de que a mudança necessária nos pesos tem "rank baixo" — pode ser aproximada pelo produto de duas matrizes pequenas (B × A). Em vez de atualizar W diretamente, você treina B e A (que juntas têm 0,1% dos parâmetros de W) e soma: W_novo = W + B × A. QLoRA adiciona quantização (NF4 4-bit) ao modelo base congelado, reduzindo VRAM de ~112 GB para ~6 GB para um modelo de 7B. Para o arquiteto: LoRA é a decisão que torna fine-tuning viável numa GPU de consumidor em vez de exigir cluster de H100.
# Sem LoRA: toda a matrix W é atualizada durante treinamento
# W tem forma [d_in × d_out], ex: [4096 × 4096] = 16M parâmetros
# Com LoRA: decomposição de baixo rank
# W_novo = W_original + B × A
# onde:
# B tem forma [d_in × r] ex: [4096 × 8] = 32.768 parâmetros
# A tem forma [r × d_out] ex: [8 × 4096] = 32.768 parâmetros
# r = rank (tipicamente 4, 8, 16, 32)
# Parâmetros treináveis: B + A = 2 × r × d
# vs Full FT: d × d
# Economia para d=4096, r=8:
total_params = 4096 * 4096 # 16.7M
lora_params = 2 * 8 * 4096 # 65.5K
pct_treinavel = lora_params / total_params # 0.39%
print(f"LoRA usa {pct_treinavel*100:.2f}% dos parâmetros")Por que baixo rank funciona?
A hipótese de LoRA é que as atualizações de peso durante fine-tuning têm “low intrinsic rank” — ou seja, a mudança necessária no modelo pode ser representada em um espaço de dimensão muito menor.
import numpy as np
# Simulação: delta_W de um treinamento real tende a ser low-rank
# Verificar com SVD (Singular Value Decomposition):
def verificar_rank_efetivo(delta_W: np.ndarray, threshold: float = 0.01) -> int:
U, S, Vt = np.linalg.svd(delta_W)
# Valores singulares normalizados
S_norm = S / S[0]
# Rank efetivo: quantos valores singulares são > threshold
return int((S_norm > threshold).sum())
# Tipicamente: rank efetivo << dimensão da matrix
# Para uma camada de atenção de GPT-2: rank efetivo ≈ 8-32 de 768Comparativo LoRA vs Full Fine-Tuning
Modelo: LLaMA-2 7B
Full Fine-Tuning:
VRAM: ~112 GB (modelo 14GB × 8 fp32)
Parâmetros treináveis: 7B
Custo na AWS: ~$50/hora (8× A100)
LoRA (r=8):
VRAM: ~14 GB (modelo 7GB quantizado + adaptadores)
Parâmetros treináveis: ~4M (0.06%)
Custo na AWS: ~$1.5/hora (RTX 3090 ou T4)
QLoRA (4-bit + LoRA):
VRAM: ~6 GB (!!)
Qualidade: ~95% do Full FT em tarefas de instrução
Custo: roda em laptop com RTX 3060QLoRA: Quantização + LoRA
# QLoRA combina:
# 1. NF4 quantization: comprime pesos de 16-bit para 4-bit (4x menor)
# 2. Double quantization: comprime os parâmetros de quantização também
# 3. Paginação de memória: move estados do otimizador para CPU quando necessário
# Em código (HuggingFace):
from transformers import BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
)
lora_config = LoraConfig(
r=8,
lora_alpha=16, # escala: alpha/r = fator de escala do update
target_modules=["q_proj", "v_proj"], # quais layers aplicar LoRA
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# → trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.0622Aprofundamento Técnico
Escolha do Rank
r=1: muito pouco → underfitting
r=4: adequado para tarefas simples (classificação)
r=8: padrão para instrução → mais comum
r=16: para tarefas complexas de geração
r=64+: começa a se aproximar de full FT em custo/memóriaQuais Camadas Aplicar LoRA?
# Mínimo efetivo (uso comum):
target_modules=["q_proj", "v_proj"] # query e value da atenção
# Mais completo (melhor qualidade):
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"] # todas as projeções
# Máximo (quase full FT):
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]Exemplos Anotados
Exemplo 1: Calculo de Memória LoRA
def calcular_mem_lora(d: int, r: int, n_layers: int, n_target_modules: int, bits: int = 16) -> dict:
bytes_por_param = bits / 8
# Parâmetros LoRA (A + B por layer por module)
lora_params = 2 * r * d * n_layers * n_target_modules
lora_mb = (lora_params * bytes_por_param) / 1e6
return {
'lora_params': lora_params,
'lora_vram_mb': round(lora_mb, 1),
'pct_do_total': round(lora_params / (d * d * n_layers) * 100, 3),
}
# LLaMA-2 7B: d=4096, n_layers=32, target_modules=2 (q,v)
resultado = calcular_mem_lora(d=4096, r=8, n_layers=32, n_target_modules=2)
# → {'lora_params': 4194304, 'lora_vram_mb': 8.4, 'pct_do_total': 0.195}
# LoRA adiciona apenas 8.4 MB ao modelo de 14GB!Padrões e Armadilhas
Padrões
Padrão 1: lora_alpha = 2 × r como ponto de partida
r=8, lora_alpha=16 # escala efetiva = alpha/r = 2.0Padrão 2: Salvar apenas os adaptadores LoRA, não o modelo completo
model.save_pretrained("meu-lora-adapter") # salva apenas os 8MB de LoRA
# Para usar: carregar modelo base + aplicar adaptadorArmadilhas
⚠️ Armadilha 1: LoRA não acelera inferência LoRA acelera treinamento, não inferência. Para inferência mais rápida, use quantização separada.
⚠️ Armadilha 2: lora_alpha muito alto pode destabilizar treinamento
alpha=128 com r=8 → escala 16.0 → updates grandes → gradiente explode
Mantenha alpha/r ≤ 4 para início⚠️ Armadilha 3: Não testar em hardware alvo antes de treinar
GPU: RTX 3060 12GB
Modelo 7B quantizado: 4GB
LoRA + optimizer: 6GB ← OOM!
Solução: gradient_checkpointing=True economiza ~30% de VRAMSe não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
Esta unidade é conceitual — não há starter para executar. Os exercícios em exercicios.md pedem que você: 1. Calcule memória necessária para diferentes configurações de LoRA 2. Explique a matemática de W_novo = W + B × A com exemplos numéricos 3. Decida quando usar QLoRA vs fine-tuning via API
Agora você está pronto para o lab.