Pular para o conteúdo principal

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​

RankCapacidadeCaso de Uso
4BaixaTarefas simples, mudanças mínimas
8Baixa-MédiaFine-tuning padrão
16MédiaRecomendação padrão
32Média-AltaTarefas complexas
64AltaMáxima expressividade
128+Muito AltaRaramente 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):

ComponenteFull FTLoRA
Pesosdk × 2dk × 2 (congelado)
Gradientesdk × 22rk × 2
Otimizadordk × 82rk × 8
Total12dk2dk + 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​

ConceitoEquação
Forward passh = Wx + (α/r)BAx
Contagem de parâmetros(d + k) × r
Fator de escalaα/r
Pesos fundidosW' = W + (α/r)BA
Na inicializaçãoBA = 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:

  1. Qual é o fator de escala efetivo para r=16, alpha=32?
  2. Quantos parâmetros o LoRA adiciona para uma matriz 4096×4096 com r=8?
  3. Por que B é inicializado em zero e não A?