Hva er LoRA, og hvorfor bruker alle det?
En kort, bildedrevet intro til Low-Rank Adaptation — trikset som gjør finjustering av store modeller mulig på én GPU.
Du har et stort, forhåndstrent nevralt nettverk. Hundrevis av millioner parametere. Det er nesten riktig for oppgaven din — men ikke helt. Du vil lære det noe nytt: et nytt språk, en ny stil, et nytt domene. Og helst uten å leie en rack med A100-er.
Det er problemet LoRA løser. Kort for Low-Rank Adaptation, introdusert i en Microsoft-artikkel fra 2021, i dag standardmetoden for finjustering av store modeller i praktisk talt alle open-source-verktøykasser. Den norske stemmen for CosyVoice 3 jeg nettopp slapp? Trent med LoRA på en enkelt RTX 3090. Hele modellen har ~500 millioner parametere; delen jeg faktisk trente er ~13 millioner. Det forholdet er grunnen til at dette i det hele tatt går an på én forbruker-GPU.
Kjerneideen
Rett til poenget: istedenfor å endre modellens vekter direkte, lærer LoRA en liten tilleggsmatrise til vektene, uttrykt som produktet av to tynne matriser.
Hvis en vektmatrise et sted inne i modellen har form — si — er det naive valget under finjustering å oppdatere alle tallene. Det er rundt 16 millioner parametere per lag, og en ekte modell har dusinvis av lag.
LoRA parameteriserer endringen som et produkt av to tynne matriser:
der ranken er liten — typisk 8, 16 eller 24.
Når du multipliserer og får du noe med samme form som , men parameterisert av bare tall istedenfor . For på en -matrise er det rundt 65 tusen tall istedenfor 16 millioner — omtrent 250× færre.
Under trening er frossen, og bare og får gradient-oppdateringer. Under inferens kan du enten holde dem separate som , eller flette dem én gang inn i en ny effektiv vekt og kjøre modellen som vanlig.
Et konkret eksempel: rank-1 på en bitteliten matrise
Tallene over er enklere å tro på hvis man ser dem rulle ut én gang. Si at er en -matrise — 16 tall hvis vi skulle finjustert den naivt. Vi parameteriserer istedenfor en rank-1 LoRA ():
Til sammen 8 trenbare tall. Produktet blir et ytre produkt:
16 tall ut — samme form som — men hver eneste rad er en skalert kopi av . Skaleringen for rad er ganske enkelt . Det er hva rank 1 betyr: hele matrisen ligger på én enkelt linje gjennom origo i radrommet. 8 parametere koder for 16 verdier, men de 16 verdiene er bundet sammen — du kan ikke sette dem uavhengig av hverandre.
Skru opp og du gir LoRA-en flere uavhengige retninger å bevege seg langs. Med får du summen av to slike rank-1-matriser, med (det jeg brukte for CosyVoice) får du 24. Hver økning i koster ekstra parametere; til gjengjeld øker uttrykksevnen.
Hvorfor funker det?
Den empiriske observasjonen bak LoRA: når du finjusterer en forhåndstrent modell, viser endringen i vektene seg å være sterkt strukturert. Selv om finjustering i prinsippet kan endre hver eneste parameter, oppfører den faktiske endringen seg som om den har mye lavere rank enn de opprinnelige vektene. Du utforsker ikke alle 16 millioner dimensjoner av endringsrommet — du beveger deg langs en håndfull meningsfulle retninger.
Så du baker den low-rank-antagelsen direkte inn i parameteriseringen. Det er ikke perfekt for alle oppgaver, men overraskende nært full finjustering for en brøkdel av parametrene.
Hva det gir deg i praksis
Besparelsene stables:
- Minne. Du trenger bare gradienter og optimizer-state for LoRA-matrisene, ikke hele modellen. På en halvmilliardparameter-modell med
r = 24er det forskjellen på å trenge 40+ GB VRAM og å få det til å passe komfortabelt på et 24 GB consumer-kort. - Treningshastighet. Færre parametere → færre gradientberegninger → raskere steg.
- Bittesmå output-filer. En LoRA-adapter for en 500M-modell er rundt 50 MB istedenfor 2 GB. Du kan dele dem, bytte mellom dem, stable flere oppå samme basemodell.
- Plugin-stil finjustering. Fordi de opprinnelige vektene er urørte, kan du holde én basemodell i minnet og bytte LoRA-er inn og ut etter behov — forskjellige stemmer, forskjellige domener, forskjellige språk.
Kostnaden er reell, men som regel liten: du gir slipp på litt kapasitet. Det finnes oppgaver der full finjustering slår LoRA tydelig, særlig når måldistribusjonen ligger langt unna det modellen opprinnelig ble forhåndstrent på. For de fleste tilpasningsoppgaver — inkludert å lære en flerspråklig TTS-modell et nytt språk — er byttet bra.
Hva jeg brukte for CosyVoice 3
For den norske bokmåls-LoRAen på Fun-CosyVoice3-0.5B-2512:
- Mål: Qwen2-0.5B-språkmodell-frontenden. Flow-matcher-dekoderen nedstrøms er urørt.
- Rank:
r = 24. - Dekning: anvendt på alle 24 transformer-blokker av LLM-en.
- Trenbare parametere: ~13,2 millioner av ~500 millioner totalt — cirka 2,6 % av modellen.
Det er delen av modellen som lærer å mappe norsk tekst til semantiske tale-tokens. Dekoderen nedstrøms vet allerede hvordan disse tokensene skal renderes til lyd; det som trengte å læres var “hvordan høres norsk ut” i steget før det.