Partie 4
Adapter sans tout réentraîner (LoRA)
Après la pause : adapter DrBERT à MorFITT sans réentraîner ses 110 millions de poids. Rappel des façons déjà vues, avec le nombre de poids qui apprennent (4 octets par poids) :
| Façon d'adapter DrBERT | Ce qui apprend | Poids entraînés | À stocker par tâche |
|---|---|---|---|
| Extraction (J3) | la tête seule | 9 228 | 37 ko |
| Dernier bloc (J3) | le dernier des 12 blocs et la tête | 7 097 100 | 28,4 Mo |
| Tout le modèle | les 12 blocs, la table d'embeddings et la tête | 110 040 588 | 440 Mo |
L'extraction ne change rien dans DrBERT ; le dernier bloc n'en adapte que la fin ; tout réentraîner coûte une copie complète par tâche (et beaucoup trop de temps sur processeur). Peut-on adapter tous les blocs en n'entraînant que peu de poids ? C'est LoRA.
Prenons la couche dense des requêtes de l'attention : elle reçoit 768 nombres et rend y = x W₀, 768 nombres. Le fine-tuning classique modifie directement W₀ (à chaque pas, W devient W − η × pente) : on obtient une nouvelle matrice de 768 × 768 qui remplace W₀. LoRA gèle W₀ et apprend une correction ΔW ; la couche calcule avec les poids effectifs W₀ + ΔW, et W₀ reste intacte.
La correction s'écrit ΔW = A B
Une correction ΔW libre aurait 768 × 768 = 589 824 nombres, autant que W₀. LoRA lui impose une structure : ΔW = A B, où A a 768 lignes et r colonnes et B a r lignes et 768 colonnes. Le rang r est choisi petit, ici 8. Le produit A B a bien 768 × 768 cases, mais il est déterminé par 768 × 8 + 8 × 768 = 12 288 nombres : 48 fois moins. En pratique la bibliothèque multiplie la correction par α / r (ici 16 / 8 = 2) ; cela ne change pas le principe.
À chaque pas : (1) prédire avec W₀ + A B, (2) mesurer la perte comme en J3, (3) rétropropager : on calcule les pentes de A et B (W₀, gelée, n'en a pas), (4) l'optimiseur modifie A et B, et la correction A B suit. LoRA ne rajoute pas une nouvelle correction à chaque pas : il met à jour A et B. Au départ B = 0 et A est aléatoire : A B est nulle, le modèle se comporte exactement comme DrBERT.
À toi de coder
À toi de coder
Où placer LoRA dans DrBERT
Dans chaque bloc, l'attention calcule pour chaque token trois vecteurs de 768 nombres, chacun avec sa couche dense 768 → 768 : la requête q (son score avec un token j est q · k(j)), la clé k (à quoi les requêtes sont comparées) et la valeur v (ce que le token transmet : la sortie est la moyenne des valeurs pondérée par les poids d'attention). Le bloc contient aussi un MLP.
LoRA, comme dans son article (Hu et al., 2021), ajoute A et B aux couches des requêtes et des valeurs : corriger q change les poids d'attention, corriger v change ce que chaque token transmet. Cela fait 2 couches par bloc, 24 en tout : 12 × 2 × 12 288 = 294 912 poids, plus 9 228 pour la tête, soit 304 140. C'est 23 fois moins que le dernier bloc de J3, et les 12 blocs sont adaptés.
En PyTorch : la bibliothèque PEFT
from peft import LoraConfig, get_peft_model config = LoraConfig(r=8, lora_alpha=16, target_modules=["query", "value"]) drbert = get_peft_model(drbert, config) # gèle DrBERT, ajoute A et B aux 24 couches drbert.print_trainable_parameters() # 294 912 poids entraînables tete = nn.Linear(768, 12) # la tête de J3 : 9 228 poids entrainables = [p for p in drbert.parameters() if p.requires_grad] + list(tete.parameters()) opt = torch.optim.AdamW(entrainables, lr=5e-4) # A, B et la tête seulement
Tu écris ces lignes, puis la boucle d'entraînement de J3 sans changement. lora_alpha (α) règle la taille de la correction : PEFT multiplie x A B par α / r (16 / 8 = 2). On fixe α une fois, ce qui permet de changer r sans changer le pas d'apprentissage.
Fine-tuning classique ou LoRA ? LoRA échange un peu de liberté contre beaucoup moins de poids à entraîner, de mémoire et de stockage. Après l'entraînement, on peut calculer une fois W₀ + 2 × A B et la mettre à la place de W₀ : le modèle adapté est alors aussi rapide que DrBERT.