sourcedemos/lora-hello/lora-hello.xtl
1⍝!/usr/bin/env xetal
2⍝│ LoRA hello: what does a small adapter add to a layer whose weights
3⍝│ do not change? The question behind it: how can a model learn
4⍝│ something new without every one of its numbers being retrained (the
5⍝│ cost) or overwritten (the forgetting)? LoRA's answer is to freeze
6⍝│ the weights and train a small correction beside them. Here is one
7⍝│ layer of that, shrunk to 4 rows and 4 columns so every number can be
8⍝│ read: a frozen table W and an adapter of rank 1, with X_eTaL-ML's
9⍝│ LoRA library. Nothing is trained here (the adapter's numbers are
10⍝│ made up): this shows what an adapter is, where it sits, what it adds
11⍝│ and what it costs. The layer is y = W x + s B (A x): W stays as it
12⍝│ is, and only A (1 row of 4) and B (1 column of 4) would be trained.
13
14"lo:" u̲se< "LoRA"
15
16⍝━━ The layer
17
18⍝│ The frozen table W (16 made-up numbers, from a sine so they need
19⍝│ not be typed), the adapter's two thin tables A and B, one input x,
20⍝│ and the strength s = alpha / r (alpha is 2, the rank r is 1).
21W ← 4 4 r̲eshape s̲in 0.7 × f̲loat r̲ange 16
22A ← 1 4 r̲eshape 0.5 -0.5 1.0 0.25
23B ← 4 1 r̲eshape 1.0 0.0 -1.0 0.5
24x ← 1.0 2.0 3.0 4.0
25s ← 2.0 ˡᵒs̲caling 1
26⍝ The numbers of the layer, for the page to show: W row by row, the
27⍝ one row of A, the one column of B.
28r̲avel W
29r̲avel A
30r̲avel B
31
32⍝━━ What the adapter adds
33
34⍝│ The squeeze: h = A x, the input brought down to one number (the
35⍝│ rank is 1). Printed after the strength s.
36h ← A '+ '× i̲nner x
37s c̲at h
38⍝ The squeeze written out: each number of A times the matching number
39⍝ of x (h is their sum). Then that one number times the strength.
40x × r̲avel A
41s × h
42
43⍝│ What the frozen layer gives, W x; what the adapted layer gives,
44⍝│ W x + s B (A x); and the correction the adapter adds, s B (A x).
45⍝│ Then a check that the two outputs differ by exactly the correction:
46⍝│ 1 means yes.
47base ← W '+ '× i̲nner x
48y ← (W, A, B, s) ˡᵒf̲orward x
49correction ← (ˡᵒd̲elta (A, B, s)) '+ '× i̲nner x
50⍝ The first row of W times x written out: its sum is the first number
51⍝ of base.
52x × 4 t̲ake r̲avel W
53base
54y
55correction
560.000000000001 > 'm̲ax r̲/ a̲bs correction − y − base
57
58⍝━━ The correction as one table, and the merge
59
60⍝│ Written out as a table, the correction is s B A: 4 rows and 4
61⍝│ columns, every row a multiple of A, so its rank is 1. Added to W it
62⍝│ gives one table, the merged one, whose output is the same as W and
63⍝│ the adapter together: 1 means yes.
64table ← ˡᵒd̲elta (A, B, s)
65merged ← ˡᵒm̲erge (W, A, B, s)
66r̲avel table
67ˡᵒr̲ank table
680.000000000001 > 'm̲ax r̲/ a̲bs y − merged '+ '× i̲nner x
69
70⍝━━ What it costs
71
72⍝│ The numbers an adapter of rank 1 trains on a table of 4 rows and 4
73⍝│ columns, r (rows + columns), beside the 16 of the table itself.
74counts ← (1 ˡᵒp̲arams 4 4) c̲at 16
75counts
76
77⍝━━ On a layer of a real model
78
79⍝│ The same count where it matters: one table of 4096 rows and 4096
80⍝│ columns (a size found in a 7-billion-parameter model) at rank 16:
81⍝│ the numbers the adapter trains, the numbers of the table, and the
82⍝│ first as a percentage of the second.
83big ← (16 ˡᵒp̲arams 4096 4096) c̲at 4096 × 4096
84big
85100.0 × (f̲loat 16 ˡᵒp̲arams 4096 4096) ÷ f̲loat 4096 × 4096