- Je implementeert een lage-rangaanpassing zonder de basismatrix te wijzigen.
- Je bewijst numeriek dat de backward en het samenvoegen van gewichten kloppen.
Je basismodel heeft bruikbare kennis, maar een nieuwe taak vraagt andere uitkomsten. Moet daarvoor iedere grote gewichtsmatrix leren? Low-Rank Adaptation (LoRA) leert een kleine extra route en bewaart de oorspronkelijke basisgewichten.
Denk aan een vaste hoofdweg met een smalle omleiding. De uitvoer bevat beide routes, maar alleen de omleiding krijgt nieuwe instellingen. Hier bouwen we één lineaire laag met rijvectoren; een heel transformer-model vraagt adapters op bewust gekozen lagen.
1. Voeg een kleine route toe
Onze laag gebruikt W van vorm (Din,Dout), A van (Din,r) en B van (r,Dout). r is de adapterrang en α een positieve schaalinstelling; s=α/r schaalt de bijdrage. De oorspronkelijke LoRA-publicatie gebruikt kolomvectoren en schrijft de factoren daarom in een andere volgorde.
X is de inputbatch en Y de uitvoer. Initialiseer A klein en willekeurig, B met nullen. Dan is de extra route aanvankelijk nul en krijg je exact de basisfunctie; beide factoren nul maken zou ook de eerste gradiënten blokkeren.
x = np.array([[1.,2.]])
w = np.eye(2)
adapter = init_lora(2,2,1)
print(lora_forward(x,w,adapter,1.))
# [[1. 2.]]
Neem daarna A=[1,0]ᵀ en B=[2,−1], met s=1. XA is één, de extra route [2,−1] en de totale uitvoer [3,1].
adapter = {'A':np.array([[1.],[0.]]),
'B':np.array([[2.,-1.]])}
print(lora_forward(x,w,adapter,1.))
# [[3. 1.]]
Maak init_lora en lora_forward. Er leren r×(Din+Dout) getallen: in dit kleine voorbeeld vier, evenveel als W. Besparing ontstaat dus niet automatisch; bij Din=Dout=128 en r=1 zijn het 256 tegenover 16.384 basisgewichten.
Eigen getallenvoorbeeld met X = [1, 2, −1, 0.5]. Je stelt B zelf in; dit diagram traint niet.
| Route | 0 | 1 | 2 |
|---|---|---|---|
| XW | 1.250 | 2.250 | -0.750 |
| (α/r)XAB | 0.000 | 0.000 | 0.000 |
| Som | 1.250 | 2.250 | -0.750 |
Uitvoer: 1.2500, 2.2500, -0.7500. Bij B-factor nul draagt de adapter niets bij. De adapter heeft hier minder trainbare gewichten dan de basismatrix.
2. Laat ook de vaste route terugpraten
Een bevroren W krijgt geen update, maar beïnvloedt nog steeds de gradiënt naar X. Die inputgradiënt telt een directe bijdrage dY·Wᵀ en een adapterbijdrage s(dY·Bᵀ)Aᵀ op. Vergeet je de directe route, dan leren eerdere lagen van een andere functie.
De twee adaptergradiënten volgen dezelfde matrixkettingregel. dY is de aangeleverde uitvoergradiënt; superscript T betekent transponeren. Met dezelfde schaal s=α/r krijg je:
Voor dezelfde handgetallen en dY=[1,1] wordt dY·Bᵀ één. Daardoor is dA=[1,2]ᵀ, dB=[1,1] en dX=[2,1]. De schaalfactor geldt voor beide adaptergradiënten.
dx,g = lora_backward(
x,w,adapter,1.,np.ones((1,2)))
print(dx) # [[2. 1.]]
print(g['A'].ravel()) # [1. 2.]
print(g['B']) # [[1. 1.]]
Maak lora_backward zonder opnieuw over voorbeelden te middelen: dY bevat al de afgeleide van de gekozen loss. Bij B=0 begint dA op nul, terwijl dB al kan leren. Controleer daarnaast niet-nul A én B numeriek tegen een onafhankelijk juiste forward.
023. Voeg routes samen voor inferentie
Zonder adapter-dropout kun je één nieuwe matrix Wmerged=W+sAB maken. De uitvoer XWmerged is dan gelijk aan de twee routes, op afronding na. Maak een nieuwe array zodat je W kunt bewaren voor andere adapters.
merged = merge_lora(w,adapter,1.)
print(x@merged) # [[3. 1.]]
print(w) # blijft de identiteitsmatrix
Maak merge_lora zonder W, A of B te wijzigen. Een gekwantiseerd basismodel kan bij samenvoegen dequantisatie en nieuwe afrondfouten vragen. Deze proef blijft float en meet geen apparaatversnelling.
034. Controleer dat alleen de adapter leert
We trainen op een haalbare synthetische rang-één-aanpassing. Voor mean squared error is de upstream 2×(prediction−target)/error.size; de noemer omvat voorbeelden én outputkanalen. Bereken eerst alle gradiënten met de huidige factoren, en werk A en B daarna samen bij.
rng = np.random.default_rng(42)
x = rng.normal(size=(5,4))
w = rng.normal(size=(4,3))
a = np.array([.2,-.4,.1,.3])
b = np.array([.5,-.2,.4])
y = x@(w+np.outer(a,b))
start = init_lora(4,3,1)
learned,h = train_lora(
x,y,w,start,1.,400,.2)
print(h[-1]<h[0]/100) # True
Maak train_lora met een gekopieerde adapter en steps+1 echte losses: vóór iedere update en eenmaal na de laatste. Bewaar W en de oorspronkelijke adapter onveranderd. De dalende speelgoedloss bewijst dat deze adapter leert, niet dat rang één voor iedere taaltaak genoeg is.
04Wat neem je mee?
- LoRA voegt een geschaalde lage-rangroute toe aan vaste basisgewichten.
- Bevroren gewichten dragen nog steeds bij aan de inputgradiënt.
- Echte lossmetingen en invoerbehoud horen bij het trainingsbewijs.
Hierna bepaal je welke tokens tijdens instructietuning een trainingsdoel zijn.