Se i checkpoint sono la ricetta base, i LoRA sono i condimenti. Un checkpoint SDXL da 6GB contiene i pesi di una rete neurale addestrata su miliardi di immagini pubbliche. Ma se vuoi che disegni in uno stile specifico, o con caratteristiche particolari, puoi non ricaricare l’intero modello, puoi caricare un file piccolo (5-500MB) che dice alla rete di modificare i suoi comportamenti.
I LoRA sono la feature che ha reso Stable Diffusion non solo utile, ma personalizzabile. E la sfida di Private Vision non era capire che cosa fossero, quella parte è ingegneristica, ma come renderli usabili senza che l’interfaccia diventasse incomprensibile.
Cosa sono davvero
LoRA sta per “Low-Rank Adaptation“. Suona impressionante, significa qualcosa di semplice, invece di riscrivere i pesi della rete neurale (operazione costosa, impossibile senza i dati di addestramento), aggiungi un piccolissimo strato di modifiche che si somma ai pesi originali.
Matematicamente: se il modello originale calcola Y = W·X (peso moltiplicato input), un LoRA dice “calcola Y = W·X + s·B·A·X“, dove s è la forza (uno slider), e B·A è il file LoRA, una piccola matrice che contiene solo le modifiche necessarie per cambiar il comportamento.
Il dettaglio geniale è che B·A pesa 1/100 di W. Non devi ricaricare 6GB, carichi 50MB.
Nel contesto di Private Vision, questo ha una conseguenza diretta sulla progettazione perché posso permettere all’utente di caricare fino a 7 LoRA contemporaneamente. Sette file da 50MB ciascuno pesano ancora meno di un checkpoint. La GPU ha lo spazio.
In che ordine applicare i LoRA?
Quando ho iniziato a implementare i LoRA in Private Vision, c’era una domanda che sembrava semplice, in che ordine applichiamo i LoRA?
La risposta che avevo in testa era “da sinistra a destra, come l’utente li lista”. Ma poi ho fatto la cosa che ogni architetto dovrebbe fare, ho controllato la formula matematica e ho notato una cosa… è una somma. Una somma è commutativa. L’ordine non importa. Permutare i LoRA non cambia un singolo pixel.
Questo è uno di quei momenti in cui la teoria tocca la pratica in modo sgradevole. Perché se l’ordine non importa matematicamente, perché l’ho messo nell’interfaccia? La risposta è che non è per la matematica ma è per l’utente.
L’ordine che l’interfaccia impone ha due ruoli reali:
- Stabilità dei metadati: quando salvi un’immagine, i LoRA attivi vengono scritti in un file JSON accanto al PNG. Se cambi l’ordine ogni volta, il JSON cambia ogni volta, rendendo difficile notare quali sono le vere differenze tra due generazioni.
- Ordine del prompt: i LoRA hanno spesso “trigger word”, parole che attivano il loro effetto. Se inserisci le trigger word nel prompt (cosa che il frontend fa con un clic), l’ordine in cui compaiono nel testo importa davvero, perché CLIP pesa più i primi token di una frase.
Quindi quello che il frontend fa è: organizza i LoRA per categoria (character, style, pose, ecc.). Dentro ogni categoria l’ordine mathematicamente non conta, ma l’interfaccia lo mantiene stabile. E quando costruisce il prompt finale, concatena le trigger word nello stesso ordine, così che il prompt rimane coerente.
Una singola scelta architetturale che sembra invisibile, “ordina per categoria”, risolve un problema nascosto senza che l’utente sappia che il problema esista.
Le categorie: quando l’UX risolve il caos
Quando Private Vision parte, scansiona la cartella loras/ e tratta ogni sottocartella come una categoria:
loras/
character/ → LoRA che modificano personaggi
style/ → stili artistici e estetici
slider/ → modificatori numerici (età, grandezza, ecc.)
pose/ → pose e posture
details/ → dettagli aggiuntivi
background/ → sfondiNiente manifest YAML, niente configurazione da mantenere allineata a mano. Una cartella nuova viene automaticamente riconosciuta e mostrata nell’interfaccia.
Il valore di questa struttura emerge quando l’utente ha 20 LoRA attive e l’immagine risulta brutta. Come diagnostica? Non “quale LoRA è sbagliata”, ma “quale categoria di LoRA è sbagliata”. Disattiva tutta la categoria style/, riprova. Riattiva una LoRA alla volta. La struttura diventa un’impalcatura di debugging.
La trappola del text encoder
Qui le cose diventano insidiose.
Un LoRA può avere pesi per due componenti del modello:
- L’UNet — la rete che genera l’immagine dal testo.
- I text encoder — i modelli che convertono il testo in numeri che l’UNet capisce.
Uno stile LoRA potrebbe toccare solo l’UNet (cambia come le immagini si formano). Un LoRA di carattere potrebbe toccare sia l’UNet che i text encoder (modifica il significato di certe parole oltre che come viene disegnato).
Nella UI di Private Vision, ogni LoRA ha uno slider di forza principale. Ma se il LoRA tocca anche i text encoder, appare un secondo slider. Se non lo tocca, il secondo slider non lo vedi nemmeno.
Il backend legge l’intestazione del file .safetensors (non i pesi, costerebbe millisecondi anche su file da 300MB) e capisce quale slider mostrare. È un piccolo dettaglio che rende l’interfaccia responsiva senza esporre la complessità sottostante.
Qui viene la trappola: quando applichi i LoRA, devi passare al modello un dizionario con le forze:
set_adapters({
"unet": 1.0,
"text_encoder": 0.8,
"text_encoder_2": 0.8
})Se ometti una chiave, diffusers non mette 0, mette 1.0. Se dimentichi di specificare text_encoder_2 perché il tuo LoRA non lo ha, la LoRA lo modificherà comunque a forza piena, producendo risultati confusi.
Nel codice di engine.py, questo è affinato, sempre passo tutti e tre i valori, anche se so che uno non sarà usato. È il tipo di “guardrail silenzioso” che evita ore di debug.
La regola empirica della somma
Quando un utente attiva 7 LoRA a forza massima, succede una cosa: l’immagine inizia a degradare.
Somma le forze: se ogni LoRA è a 1.0, la formula diventa W + 7·(piccole modifiche). A un certo punto il modello inizia a litigare con se stesso, un LoRA dice “colore rosso”, un altro dice “colore blu”, e l’UNet tira indietro con il risultato che è qualcosa di strano nel mezzo.
Ho stabilito una regola empirica (basata sulla sperimentazione, non su teoria): sopra ~3 di somma totale, l’immagine tende a degradare. Non è una soglia fisica, è un’osservazione pratica.
L’interfaccia avvisa quando superi 3. Non blocca, un utente esperto potrebbe volere 4 per un effetto specifico. Ma avvisa: “Stai usando una somma alta, la qualità potrebbe soffrire.” È il compromesso tra proteggere l’utente inesperto e non censurare l’utente consapevole.
Il trigger word: dedurre quello che non è scritto
Nel file .safetensors di un LoRA non esiste un campo “trigger word”. Non è previsto dal formato, non è nei metadati.
Ma chi ha addestrato il LoRA (l’autore) ha incluso i metadati di addestramento, incluso ss_tag_frequency, un elenco delle tag che hanno apparso nelle didascalie del training set, con le loro frequenze. Se una LoRA è stata addestrata su immagini con la tag “xyz” nel 95% dei casi, probabilmente “xyz” è la trigger word.
engine.trigger_candidati() fa esattamente questo:
- Cerca tag che appaiono nel 90%+ dei dati.
- Scarta tag generiche (1girl, 2girls, ecc.).
- Ordina mettendo davanti le tag che appaiono anche nel nome del file.
Quest’ultimo segnale è decisivo. Se il file si chiama “belldandy_lora” e tra le tag frequenti c’è “belldandy“, allora non è una coincidenza, quella è la trigger.
Se il LoRA ha una tag candidata, il frontend la mostra come suggerimento e con un clic e la aggiunge al prompt. Mai automaticamente. Inserire una parola indovinata non darebbe errore, darebbe solo immagini peggiori, e l’utente non capirebbe perché.
Delle 13 LoRA presenti nel progetto, 6 hanno un candidato confermato anche dal nome. 1 ha un candidato dubbio. 6 non hanno candidati. Restano ipotesi, il dato certo esiste online (nei database di Civitai), ma recuperarlo in automatico romprebbe la premessa offline del progetto.
Semplificare la complessità
La complessità dei LoRA non è sparita portandoli in Private Vision. È stata ridistribuita.
Non l’ho nascosta dietro un’interfaccia stupida (“usa i LoRA con questo bottone”). L’ho esposta in un modo che ha senso: categorie facili da capire, slider con avvisi, suggerimenti di trigger word invece di magie nascoste.
Ogni scelta architetturale, le categorie, il registro di adapter residenti, il controllo del text encoder, è una risposta a “come faccio a rendere potente il sistema senza confondere chi lo usa?”
Quando vedi un’interfaccia semplice, spesso c’è dietro un’architettura complessa che è stata consapevolmente disegnata per apparire semplice. Questa volta, quella architettura vive in engine.py e nella struttura delle cartelle di loras/.
Se l’articolo ti è piaciuto restiamo in contatto su linkedin a: https://www.linkedin.com/in/andreatonin/
| Editorial Manager at Pills for Nerds | CTO & Technology Consultant |
Technology professional, editor and lifelong nerd with over 30 years of experience in the digital and innovation sectors. I work as a CTO and technology consultant, designing complex and scalable software ecosystems, and I am also active in IT education and professional training. Alongside my technology career, I serve as Editorial Manager and contributor at Pills for Nerds, online publication covering video games, game development, emerging technologies, artificial intelligence, digital culture and industry events. In my editorial role, I coordinate content planning and contribute articles, interviews, event reports and in-depth features. You can learn more about my consulting work at lucedigitale.com and connect with me on LinkedIn


















