Capítulo 2: Matemática do LoRA
Os Fundamentos Matemáticos da Adaptação de Baixo Rank
Compreender a matemática por trás do LoRA ajuda você a tomar melhores decisões sobre hiperparâmetros e entender por que ele funciona de forma tão eficaz.
Fatoração de Matrizes
A Equação Central
Para uma matriz de pesos pré-treinada W ∈ ℝ^(d×k):
W' = W + ΔW = W + BA
Onde:
- W é o peso pré-treinado congelado (d × k)
- B ∈ ℝ^(d×r) é a matriz de projeção para baixo
- A ∈ ℝ^(r×k) é a matriz de projeção para cima
- r é o rank (muito menor que d e k)
Forward Pass
def forward(x, W, B, A, scaling):
# Caminho congelado
h_base = W @ x # (d × k) @ (k × batch) = (d × batch)
# Caminho LoRA
h_lora = B @ A @ x # (d × r) @ (r × k) @ (k × batch) = (d × batch)
# Combinado
return h_base + scaling * h_lora
Rank e Capacidade
O que é Rank?
O rank r determina a máxima "expressividade" da atualização do LoRA.
Matriz ΔW = BA tem rank no máximo r
Se r é pequeno, ΔW só pode representar atualizações em um subespaço de baixa dimensão.
Contagem de Parâmetros
def lora_params(d, k, r):
"""Calcula os parâmetros do LoRA para uma matriz de pesos."""
return r * d + r * k # Matrizes B + A
# Exemplo: matriz 4096 × 4096 com r=16
params = lora_params(4096, 4096, 16)
print(f"LoRA params: {params:,}") # 131,072
# Comparando com a matriz completa
full = 4096 * 4096
print(f"Full params: {full:,}") # 16,777,216
print(f"Ratio: {params/full:.4%}") # 0.78%
Escolhendo o Rank
| Rank | Capacidade | Caso de Uso |
|---|---|---|
| 4 | Baixa | Tarefas simples, mudanças mínimas |
| 8 | Baixa-Média | Fine-tuning padrão |
| 16 | Média | Recomendação padrão |
| 32 | Média-Alta | Tarefas complexas |
| 64 | Alta | Máxima expressividade |
| 128+ | Muito Alta | Raramente necessário |
Escala Alpha
O Fator de Escala
LoRA inclui um fator de escala α (alpha):
ΔW = (α/r) × BA
Por que Escalar?
Sem escala, mudar r altera a magnitude das atualizações:
- r maior → mais parâmetros → atualizações maiores
- r menor → menos parâmetros → atualizações menores
Com a escala α/r, você pode ajustar r sem alterar a magnitude.
Configurações Comuns
# Padrões comuns
config1 = {"r": 16, "alpha": 16} # Scale = 1
config2 = {"r": 16, "alpha": 32} # Scale = 2
config3 = {"r": 32, "alpha": 64} # Scale = 2
# Regra prática: alpha = 2 × r é comum
Efeito do Alpha
def effective_scaling(alpha, r):
return alpha / r
# Exemplos
print(effective_scaling(16, 16)) # 1.0
print(effective_scaling(32, 16)) # 2.0
print(effective_scaling(64, 32)) # 2.0
Alpha/r maior → maior contribuição do LoRA → adaptação mais rápida, mas maior risco de instabilidade.
Inicialização
Matriz A: Inicialização Kaiming/He
# Valores aleatórios pequenos
A = torch.randn(r, k) * math.sqrt(2 / k)
# Ou mais simples:
A = torch.randn(r, k) * 0.01
Matriz B: Inicialização em Zero
B = torch.zeros(d, r)
Justificativa Matemática
Na inicialização:
ΔW = BA = B × A = 0 × A = 0
h_total = Wx + (α/r) × BAx = Wx + 0 = Wx
O modelo começa exatamente como o modelo pré-treinado—sem perturbação inicial.
Fluxo de Gradientes
Calculando Gradientes
Para uma perda L, precisamos dos gradientes para B e A:
∂L/∂B = (α/r) × (∂L/∂h) × x^T × A^T
∂L/∂A = (α/r) × B^T × (∂L/∂h) × x^T
Propriedade Chave
Os gradientes fluem apenas pelo caminho LoRA durante o treinamento:
- W está congelado (sem gradientes)
- B e A recebem e atualizam gradientes
# No código
for name, param in model.named_parameters():
if "lora" in name.lower():
param.requires_grad = True
else:
param.requires_grad = False
Análise de Memória
Componentes de Memória no Treinamento
Para uma matriz de pesos W ∈ ℝ^(d×k):
| Componente | Full FT | LoRA |
|---|---|---|
| Pesos | dk × 2 | dk × 2 (congelado) |
| Gradientes | dk × 2 | 2rk × 2 |
| Otimizador | dk × 8 | 2rk × 8 |
| Total | 12dk | 2dk + 20rk |
def memory_comparison(d, k, r, bytes_per_param=2):
"""Compara o uso de memória."""
# Full FT: pesos + gradientes + otimizador (2 estados)
full_ft = d * k * (2 + 2 + 8) # 12 bytes por parâmetro
# LoRA: pesos congelados + treinamento LoRA
frozen = d * k * 2 # Apenas pesos, sem gradientes/otimizador
lora = r * (d + k) * (2 + 2 + 8) # Treinamento completo
lora_total = frozen + lora
print(f"Full FT: {full_ft / 1e9:.2f} GB")
print(f"LoRA: {lora_total / 1e9:.2f} GB")
print(f"Savings: {(1 - lora_total/full_ft) * 100:.1f}%")
memory_comparison(4096, 4096, 16)
Matemática da Seleção de Rank
Visão Informacional
O rank determina quantas "direções" independentes a atualização pode modificar:
- Rank r permite r modificações independentes
- Rank maior = mais expressivo, mas mais parâmetros
Decomposição em Valores Singulares
Qualquer matriz pode ser decomposta:
M = UΣV^T
Se M tem rank efetivo baixo (a maioria dos valores singulares é pequena), uma aproximação de baixo rank funciona bem.
Seleção Prática
def estimate_rank(task_complexity, model_size):
"""Heurística para seleção de rank."""
base_rank = 8 # Bom padrão
# Ajuste para complexidade
if task_complexity == "simple":
rank = max(4, base_rank // 2)
elif task_complexity == "moderate":
rank = base_rank
elif task_complexity == "complex":
rank = base_rank * 2
else:
rank = base_rank * 4
return rank
# Exemplos
print(estimate_rank("simple", "7B")) # 4
print(estimate_rank("moderate", "7B")) # 8
print(estimate_rank("complex", "7B")) # 16
Matemática da Fusão
Operação de Fusão
Na inferência, podemos fundir o LoRA nos pesos base:
def merge_lora(W, B, A, alpha, r):
"""Funde o LoRA nos pesos base."""
scaling = alpha / r
W_merged = W + scaling * (B @ A)
return W_merged
Após a Fusão
h = W_merged × x
Equivalente a:
h = (W + (α/r) × BA) × x
Mas computado com uma única multiplicação de matriz—sem overhead.
Resumo das Equações Principais
| Conceito | Equação |
|---|---|
| Forward pass | h = Wx + (α/r)BAx |
| Contagem de parâmetros | (d + k) × r |
| Fator de escala | α/r |
| Pesos fundidos | W' = W + (α/r)BA |
| Na inicialização | BA = 0 (B é zero) |
Principais Pontos
✅ LoRA aproxima atualizações de pesos como produtos de matrizes de baixo rank
✅ Rank r controla a capacidade; α controla a magnitude
✅ Escala efetiva = α/r; valor comum é 2
✅ Inicialização de B em zero garante início limpo
✅ Economia de memória vem de não armazenar gradientes/otimizador para pesos congelados
✅ Fusão combina LoRA aos pesos base sem overhead
Próximos Passos
Continue para Capítulo 3: Implementando LoRA com PEFT para implementação prática.
Verificação Rápida
Antes de prosseguir, certifique-se de que você consegue responder:
- Qual é o fator de escala efetivo para r=16, alpha=32?
- Quantos parâmetros o LoRA adiciona para uma matriz 4096×4096 com r=8?
- Por que B é inicializado em zero e não A?