Fine-tuning con LoRA

LoRA congela le parti pesanti preaddestrate di un modello e addestra accanto a esse piccoli adapter a basso rango, più i layer che devono restare densi. In LibreYOLO l'intera interfaccia pubblica è un booleano.

Installazione

LoRA si appoggia alla dipendenza opzionale peft.

pip
pip install "libreyolo[lora]"

Senza di essa, lora=True solleva un ImportError che nomina quel comando, invece di eseguire per sbaglio un fine-tuning completo.

Come si usa

Python
from libreyolo import LibreYOLO model = LibreYOLO("LibreRFDETRs.pt")model.train(data="my-dataset.yaml", epochs=50, lora=True)
CLI
libreyolo train model=LibreRFDETRs.pt data=my-dataset.yaml \  epochs=50 lora=true

lora=True è tutta l'interfaccia. Rango, alpha, dropout e moduli target sono fissati per famiglia in modo da corrispondere a ciascun riferimento upstream, e non sono parametri esposti all'utente.

Una famiglia che non supporta LoRA solleva un errore al setup invece di ignorare il flag:

LoRA fine-tuning (lora=True) is not supported for yolo9. LoRA targets
transformer components with nn.Linear layers (e.g. RF-DETR, D-FINE, DEIM).

La CLI lo rifiuta prima ancora che il modello venga costruito, usando una propria allowlist delle stesse nove famiglie.

Quali famiglie

RF-DETR, D-FINE, DEIM, DEIMv2, RT-DETR v1, v2 e v4, EC e ConvNeXt. Il controllo è l'attributo supports_lora sulla classe trainer di ogni famiglia, e la CLI porta con sé un'allowlist corrispondente.

La copertura dei task è più ristretta di quella delle famiglie. D-FINE ed EC supportano solo il rilevamento, e i loro percorsi segment e pose sollevano un errore. Il percorso semantico di RF-DETR solleva un errore. ConvNeXt è classificazione.

Tutto il resto solleva un errore. Non esiste una modalità parziale o silenziosa.

Cosa fa ogni ricetta

Le ricette differiscono perché differiscono le architetture, e una ricetta che funziona su un backbone ViT non ha nulla a cui agganciarsi su uno convoluzionale.

RF-DETR usa DoRA, LoRA con decomposizione dei pesi, a rango 16 e alpha 16 sulle proiezioni di attenzione query, key e value del backbone DINOv2, in linea con il riferimento RF-DETR. Il backbone ViT si congela; il projector, il decoder e la testa di rilevamento continuano ad addestrarsi normalmente.

D-FINE, DEIM e RT-DETR v1, v2 e v4 abbinano un backbone convoluzionale a un encoder ibrido transformer e a un decoder deformabile, quindi la divisione si sposta. Il backbone convoluzionale si congela per intero, il che ne evita anche il backward pass. I blocchi transformer congelano i loro pesi di base e addestrano semplici adapter LoRA allo stesso rango 16 e alpha 16 sui loro layer lineari: i feed-forward linear1 e linear2, il gate e le proiezioni di deformable attention. Tutto il resto, la fusione convoluzionale dell'encoder, le proiezioni di input, le teste di predizione e i query embedding, continua ad addestrarsi in modo denso.

Due dettagli di quella ricetta sono voluti. La self-attention del decoder resta congelata senza adapter, perché nn.MultiheadAttention di PyTorch legge direttamente out_proj.weight e aggirerebbe silenziosamente un adapter iniettato. Ed è LoRA semplice invece di DoRA, perché diversi layer lineari del decoder sono inizializzati a zero per scelta progettuale e la normalizzazione della magnitudine di DoRA divide per la norma dei pesi.

DEIMv2 adotta la stessa ricetta con i suoi layer feed-forward SwiGLU w12 e w3 come target. Le sue taglie S, M, L e X portano anche un backbone ViT DINOv3, dove la base ViT si congela e i suoi layer di attenzione fusa qkv ricevono gli adapter, mentre la piramide di convoluzioni dello Spatial Tuning Adapter continua ad addestrarsi come analogo del projector. Quegli adapter su qkv entrano anche quando la config arriva con il ViT già congelato, dato che adattare un backbone congelato è proprio lo scopo. Le taglie sotto la S usano un backbone convoluzionale e prendono la ricetta semplice.

EC è un DETR il cui backbone è un ViT circondato da una piramide addestrabile di projector convoluzionali. La base ViT si congela e i suoi layer qkv ricevono gli adapter, i blocchi transformer prendono la ricetta condivisa, e il projector e le teste restano densi.

I blocchi ConvNeXt portano MLP lineari in formato channels-last, fc1 e fc2, e quelli prendono adapter semplici. Le convoluzioni depthwise, le norm e i parametri di layer-scale si congelano. La testa di classificazione resta densa, così i conteggi di classi personalizzati continuano a funzionare.

Le teste di rilevamento e di classificazione restano sempre addestrabili in ogni ricetta, perché un conteggio di classi personalizzato ha bisogno di una testa addestrata ex novo.

Checkpoint ed esportazione

best.pt e last.pt conservano i tensori degli adapter, così un run LoRA si riprende o si ispeziona come qualsiasi altro. Caricare uno di quei checkpoint richiede l'extra lora installato, perché il loader ripete l'iniezione degli adapter in modo che le chiavi combacino.

export() unisce gli adapter in pesi densi, così un artefatto esportato non porta alcuna dipendenza da peft. Lo stesso merge è disponibile direttamente per un modello in memoria.

L'esportazione unisce gli adapter
from libreyolo import LibreYOLO model = LibreYOLO("runs/train/exp/weights/best.pt")model.export(format="onnx")
Unire in memoria
from libreyolo import LibreYOLOfrom libreyolo.training.lora import merge_lora_adapters model = LibreYOLO("runs/train/exp/weights/best.pt")merged = merge_lora_adapters(model.model) print(f"{merged} adapter layers folded into dense weights")

Dopo un merge l'albero dei moduli è completamente denso e un secondo merge non fa nulla.

Cosa fa risparmiare, e cosa no

LoRA taglia la memoria dell'optimizer e dei gradienti, e sulle famiglie che congelano del tutto il backbone salta anche il backward pass di quel backbone.

La memoria delle attivazioni non cambia. Le attivazioni del forward vanno comunque mantenute per tutto ciò che resta addestrabile, e di solito è questo a determinare il picco. Per il budget di VRAM più ristretto, abbassa anche batch o imgsz.

Correlati

  • Congelamento dei layer per l'altro modo di addestrare un sottoinsieme dei pesi, che funziona su ogni famiglia e non richiede dipendenze extra. freeze e lora=True si combinano: i parametri degli adapter restano addestrabili anche quando il gruppo backbone che li contiene è congelato.
  • Iperparametri per batch, imgsz e il resto di train().

Verificato con LibreYOLO v1.5.0.