Aller au contenu principal

Chapitre 2 : Mathématiques de LoRA

Les Fondements Mathématiques de l'Adaptation de Rang Faible​

Comprendre les mathématiques derrière LoRA vous aide à prendre de meilleures décisions concernant les hyperparamètres et à comprendre pourquoi cela fonctionne si efficacement.


Factorisation de Matrice​

L'Équation Principale​

Pour une matrice de poids pré-entraînée W ∈ ℝ^(d×k) :

W' = W + ΔW = W + BA

Où :

  • W est le poids pré-entraîné figé (d × k)
  • B ∈ ℝ^(d×r) est la matrice de projection vers le bas
  • A ∈ ℝ^(r×k) est la matrice de projection vers le haut
  • r est le rang (beaucoup plus petit que d et k)

Passage Avant​

def forward(x, W, B, A, scaling):
# Chemin figé
h_base = W @ x # (d × k) @ (k × batch) = (d × batch)

# Chemin LoRA
h_lora = B @ A @ x # (d × r) @ (r × k) @ (k × batch) = (d × batch)

# Combiné
return h_base + scaling * h_lora

Rang et Capacité​

Qu'est-ce que le Rang ?​

Le rang r détermine la "capacité d'expression" maximale de la mise à jour LoRA.

La matrice ΔW = BA a un rang au plus égal à r

Si r est petit, ΔW ne peut représenter que des mises à jour dans un sous-espace de faible dimension.

Nombre de Paramètres​

def lora_params(d, k, r):
"""Calculer les paramètres LoRA pour une matrice de poids."""
return r * d + r * k # Matrices B + A

# Exemple : matrice 4096 × 4096 avec r=16
params = lora_params(4096, 4096, 16)
print(f"Paramètres LoRA : {params:,}") # 131,072

# Comparer à la matrice complète
full = 4096 * 4096
print(f"Paramètres complets : {full:,}") # 16,777,216
print(f"Ratio : {params/full:.4%}") # 0.78%

Choix du Rang​

RangCapacitéCas d'utilisation
4FaibleTâches simples, changements minimaux
8Faible-MoyenAjustement fin standard
16MoyenRecommandation par défaut
32Moyen-ÉlevéTâches complexes
64ÉlevéCapacité d'expression maximale
128+Très ÉlevéRarement nécessaire

Mise à l'Échelle Alpha​

Le Facteur d'Échelle​

LoRA inclut un facteur d'échelle α (alpha) :

ΔW = (α/r) × BA

Pourquoi Échelonner ?​

Sans échelle, changer r modifie l'amplitude des mises à jour :

  • r plus élevé → plus de paramètres → mises à jour plus grandes
  • r plus bas → moins de paramètres → mises à jour plus petites

Avec l'échelle α/r, vous pouvez ajuster r sans changer l'amplitude.

Configurations Courantes​

# Modèles courants
config1 = {"r": 16, "alpha": 16} # Échelle = 1
config2 = {"r": 16, "alpha": 32} # Échelle = 2
config3 = {"r": 32, "alpha": 64} # Échelle = 2

# Règle générale : alpha = 2 × r est courant

Effet de Alpha​

def effective_scaling(alpha, r):
return alpha / r

# Exemples
print(effective_scaling(16, 16)) # 1.0
print(effective_scaling(32, 16)) # 2.0
print(effective_scaling(64, 32)) # 2.0

Plus α/r est élevé → plus grande contribution de LoRA → adaptation plus rapide mais risque accru d'instabilité.


Initialisation​

Matrice A : Initialisation de Kaiming/He​

# Petites valeurs aléatoires
A = torch.randn(r, k) * math.sqrt(2 / k)
# Ou plus simple :
A = torch.randn(r, k) * 0.01

Matrice B : Initialisation à Zéro​

B = torch.zeros(d, r)

Raisonnement Mathématique​

À l'initialisation :

ΔW = BA = B × A = 0 × A = 0
h_total = Wx + (α/r) × BAx = Wx + 0 = Wx

Le modèle commence exactement comme le modèle pré-entraîné—aucune perturbation initiale.


Flux de Gradient​

Calcul des Gradients​

Pour une perte L, nous avons besoin des gradients pour B et A :

∂L/∂B = (α/r) × (∂L/∂h) × x^T × A^T
∂L/∂A = (α/r) × B^T × (∂L/∂h) × x^T

Propriété Clé​

Les gradients ne passent que par le chemin LoRA pendant l'entraînement :

  • W est figé (pas de gradients)
  • B et A reçoivent et mettent à jour les gradients
# En code
for name, param in model.named_parameters():
if "lora" in name.lower():
param.requires_grad = True
else:
param.requires_grad = False

Analyse de la Mémoire​

Composants de Mémoire d'Entraînement​

Pour une matrice de poids W ∈ ℝ^(d×k) :

ComposantFT CompletLoRA
Poidsdk × 2dk × 2 (figé)
Gradientsdk × 22rk × 2
Optimiseurdk × 82rk × 8
Total12dk2dk + 20rk
def memory_comparison(d, k, r, bytes_per_param=2):
"""Comparer l'utilisation de la mémoire."""
# FT Complet : poids + gradients + optimiseur (2 états)
full_ft = d * k * (2 + 2 + 8) # 12 octets par paramètre

# LoRA : poids figés + entraînement LoRA
frozen = d * k * 2 # Juste les poids, pas de gradients/optimiseur
lora = r * (d + k) * (2 + 2 + 8) # Entraînement complet
lora_total = frozen + lora

print(f"FT Complet : {full_ft / 1e9:.2f} GB")
print(f"LoRA : {lora_total / 1e9:.2f} GB")
print(f"Économies : {(1 - lora_total/full_ft) * 100:.1f}%")

memory_comparison(4096, 4096, 16)

Mathématiques de la Sélection du Rang​

Vue Théorique de l'Information​

Le rang détermine combien de "directions" indépendantes la mise à jour peut modifier :

  • Rang r permet r modifications indépendantes
  • Rang plus élevé = plus expressif mais plus de paramètres

Décomposition en Valeurs Singulières​

Toute matrice peut être décomposée :

M = UΣV^T

Si M a un rang effectif faible (la plupart des valeurs singulières sont petites), une approximation de faible rang fonctionne bien.

Sélection Pratique​

def estimate_rank(task_complexity, model_size):
"""Heuristique pour la sélection du rang."""
base_rank = 8 # Bon défaut

# Ajuster pour la complexité
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

# Exemples
print(estimate_rank("simple", "7B")) # 4
print(estimate_rank("moderate", "7B")) # 8
print(estimate_rank("complex", "7B")) # 16

Mathématiques de la Fusion​

Opération de Fusion​

Lors de l'inférence, nous pouvons fusionner LoRA dans les poids de base :

def merge_lora(W, B, A, alpha, r):
"""Fusionner LoRA dans les poids de base."""
scaling = alpha / r
W_merged = W + scaling * (B @ A)
return W_merged

Après Fusion​

h = W_merged × x

Équivalent à :

h = (W + (α/r) × BA) × x

Mais calculé avec une seule multiplication de matrice—aucune surcharge.


Résumé des Équations Clés​

ConceptÉquation
Passage avanth = Wx + (α/r)BAx
Nombre de paramètres(d + k) × r
Facteur d'échelleα/r
Poids fusionnésW' = W + (α/r)BA
À l'initialisationBA = 0 (B est zéro)

Points Clés à Retenir​

✅ LoRA approxime les mises à jour de poids comme des produits de matrices de faible rang

✅ Le rang r contrôle la capacité ; α contrôle l'amplitude

✅ Échelle effective = α/r ; valeur courante est 2

✅ L'initialisation à zéro de B assure un démarrage propre

✅ Les économies de mémoire proviennent de l'absence de stockage des gradients/optimiseur pour les poids figés

✅ La fusion combine LoRA dans les poids de base sans surcharge


Prochaines Étapes​

Continuez vers Chapitre 3 : Implémentation de LoRA avec PEFT pour une implémentation pratique.


Vérification Rapide​

Avant de continuer, assurez-vous de pouvoir répondre :

  1. Quel est le facteur d'échelle effectif pour r=16, alpha=32 ?
  2. Combien de paramètres LoRA ajoute-t-il pour une matrice 4096×4096 avec r=8 ?
  3. Pourquoi B est-il initialisé à zéro au lieu de A ?