Angshul commited on
Commit
2320bb1
·
verified ·
1 Parent(s): d08983c

Upload 6 files

Browse files
README.md CHANGED
@@ -1,14 +1,30 @@
1
  ---
2
  title: TensorPEFT
3
- emoji: 🐢
4
- colorFrom: yellow
5
- colorTo: pink
6
  sdk: gradio
7
  sdk_version: 6.31.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  license: apache-2.0
 
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: TensorPEFT
3
+ emoji: 🧊
4
+ colorFrom: purple
5
+ colorTo: blue
6
  sdk: gradio
7
  sdk_version: 6.31.0
 
8
  app_file: app.py
9
  pinned: false
10
  license: apache-2.0
11
+ short_description: Tensor-pursuit fine-tuning vs LoRA at tiny budgets
12
+ tags:
13
+ - peft
14
+ - fine-tuning
15
+ - tensor-decomposition
16
+ - lora-alternative
17
  ---
18
 
19
+ # TensorPEFT live demo
20
+
21
+ Live race between **TensorPEFT** (tensor gradient-pursuit adapters) and matched-budget PEFT baselines
22
+ (prompt tuning, LoRA, (IA)³, LayerNorm tuning): GPT-2 124M is fine-tuned on Bengali news with the same number of
23
+ trainable parameters for every method, and validation cross-entropy decides.
24
+
25
+ - Library: [Angshul/TensorPEFT](https://huggingface.co/Angshul/TensorPEFT) — `pip install git+https://huggingface.co/Angshul/TensorPEFT`
26
+ - Data: Bengali news articles from [soham96/Bengali_news_classifier](https://github.com/soham96/Bengali_news_classifier),
27
+ downloaded at start-up (not redistributed), tokenised with the GPT-2 tokenizer; 3M training tokens, held-out articles for validation.
28
+ - Base model: [openai-community/gpt2](https://huggingface.co/openai-community/gpt2).
29
+
30
+ Runs on ZeroGPU. Benchmark numbers in the *Benchmark results* tab were measured on CPU (fp32, 300 steps).
__pycache__/app.cpython-313.pyc ADDED
Binary file (28.9 kB). View file
 
__pycache__/baselines.cpython-313.pyc ADDED
Binary file (10.2 kB). View file
 
app.py ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TensorPEFT live demo: tensor gradient-pursuit adapters vs. matched-budget PEFT baselines.
2
+
3
+ GPT-2 (124M, English) is fine-tuned on Bengali news with the same number of trainable
4
+ parameters for every method; validation cross-entropy (nats/token) decides.
5
+ """
6
+ import copy
7
+ import glob
8
+ import io
9
+ import json
10
+ import math
11
+ import os
12
+ import random
13
+ import re
14
+ import time
15
+ import unicodedata
16
+ import urllib.request
17
+ import zipfile
18
+
19
+ import gradio as gr
20
+ import matplotlib
21
+
22
+ matplotlib.use("Agg")
23
+ import matplotlib.pyplot as plt # noqa: E402
24
+ import numpy as np # noqa: E402
25
+ import torch # noqa: E402
26
+ import torch.nn.functional as F # noqa: E402
27
+ from transformers import AutoModelForCausalLM, AutoTokenizer # noqa: E402
28
+
29
+ from baselines import IA3, LNTune, LoRA, Prompt, _Adapter # noqa: E402
30
+ from tensorpeft import TensorPEFTConfig, get_tensorpeft_model # noqa: E402
31
+
32
+ try:
33
+ import spaces
34
+ GPU = spaces.GPU
35
+ except Exception: # running outside a ZeroGPU Space
36
+ def GPU(fn=None, **kw):
37
+ return fn if fn is not None else (lambda f: f)
38
+
39
+ MODEL_ID = os.environ.get("TP_MODEL", "openai-community/gpt2")
40
+ DATA_URL = os.environ.get(
41
+ "TP_DATA_URL",
42
+ "https://raw.githubusercontent.com/soham96/Bengali_news_classifier/"
43
+ "4c2a51b51fc56208426ef19368bfcb08acc9d0c4/data/data.zip")
44
+ CACHE = os.environ.get("TP_CACHE", "data_cache")
45
+ SEQ, BATCH, VAL_SEQS = 128, 8, 64
46
+
47
+ # ------------------------------------------------------------------ data (built once at start-up)
48
+ tok = AutoTokenizer.from_pretrained(MODEL_ID)
49
+
50
+
51
+ def build_data():
52
+ os.makedirs(CACHE, exist_ok=True)
53
+ tr, va = os.path.join(CACHE, "train.bin"), os.path.join(CACHE, "val.bin")
54
+ if os.path.exists(tr) and os.path.exists(va):
55
+ return
56
+ src = DATA_URL
57
+ raw = open(src, "rb").read() if os.path.exists(src) else urllib.request.urlopen(src).read()
58
+ docs = []
59
+ with zipfile.ZipFile(io.BytesIO(raw)) as z:
60
+ for name in sorted(n for n in z.namelist() if n.endswith("_articles.txt") and "__MACOSX" not in n):
61
+ for a in json.loads(z.read(name).decode("utf-8"))["articles"]:
62
+ t = unicodedata.normalize("NFC", (a.get("title", "") + "। " + a.get("body", "")).strip())
63
+ t = re.sub(r"\s+", " ", t)
64
+ if len(t) > 200:
65
+ docs.append(t)
66
+ random.Random(0).shuffle(docs)
67
+
68
+ def enc(ds, cap):
69
+ ids = []
70
+ for d in ds:
71
+ ids += tok(d).input_ids + [tok.eos_token_id]
72
+ if len(ids) >= cap:
73
+ break
74
+ return np.array(ids[:cap], dtype=np.uint16)
75
+
76
+ enc(docs[:400], 200_000).tofile(va)
77
+ enc(docs[400:], 3_000_000).tofile(tr)
78
+
79
+
80
+ build_data()
81
+ TRAIN = np.fromfile(os.path.join(CACHE, "train.bin"), dtype=np.uint16)
82
+ VAL = torch.from_numpy(np.fromfile(os.path.join(CACHE, "val.bin"), dtype=np.uint16)[: VAL_SEQS * (SEQ + 1)]
83
+ .astype(np.int64).reshape(VAL_SEQS, SEQ + 1))
84
+ BASE = AutoModelForCausalLM.from_pretrained(MODEL_ID, dtype=torch.float32).eval()
85
+ for _p in BASE.parameters():
86
+ _p.requires_grad_(False)
87
+
88
+
89
+ def sampler(seed):
90
+ rng = np.random.default_rng(seed)
91
+ while True:
92
+ ix = rng.integers(0, len(TRAIN) - SEQ - 1, BATCH)
93
+ yield torch.from_numpy(np.stack([TRAIN[i:i + SEQ + 1] for i in ix]).astype(np.int64))
94
+
95
+
96
+ # ------------------------------------------------------------------ methods per budget
97
+ class TP(_Adapter):
98
+ lr, warmup = 3e-3, 10
99
+
100
+ def __init__(self, model, **cfg):
101
+ super().__init__()
102
+ self.model = [model]
103
+ self.tp = get_tensorpeft_model(model, TensorPEFTConfig(**cfg))
104
+
105
+ def setup(self, probe_batches):
106
+ self.tp.init_atoms(probe_batches, verbose=False)
107
+
108
+ def trainable(self):
109
+ return [p for p in self.tp.parameters() if p.requires_grad]
110
+
111
+ def mark_ready(self):
112
+ for g in self.tp.groups:
113
+ g.initialized.fill_(True)
114
+
115
+
116
+ ATTN = ["c_attn.q", "c_attn.k", "c_attn.v", "attn.c_proj"]
117
+ BUDGETS = {
118
+ "≈1.5k parameters": [
119
+ ("TensorPEFT · 1 attention atom", lambda m: TP(m, target_modules=ATTN, atoms_per_group=1)),
120
+ ("Prompt tuning · 1 token", lambda m: Prompt(m, 1)),
121
+ ("LayerNorm tuning · final LN", lambda m: LNTune(m, layers=[], final=True)),
122
+ ],
123
+ "≈9k parameters": [
124
+ ("TensorPEFT · 3 atoms", lambda m: TP(m, atoms_per_group=1)),
125
+ ("Prompt tuning · 12 tokens", lambda m: Prompt(m, 12)),
126
+ ("LoRA r=1 · q,v of last 3 layers", lambda m: LoRA(m, 1, layers=[9, 10, 11])),
127
+ ("(IA)³ · values", lambda m: IA3(m, kv=("c_attn.v",), ff=False)),
128
+ ("LayerNorm tuning · last 3 layers", lambda m: LNTune(m, layers=[9, 10, 11], final=False)),
129
+ ],
130
+ "≈55k parameters": [
131
+ ("TensorPEFT · 18 atoms", lambda m: TP(m, atoms_per_group=6)),
132
+ ("(IA)³ · keys, values, MLP", lambda m: IA3(m)),
133
+ ],
134
+ }
135
+ COLORS = ["#7c3aed", "#0ea5e9", "#f59e0b", "#10b981", "#ef4444"]
136
+
137
+
138
+ def ce(adapter, x):
139
+ return F.cross_entropy(adapter.logits(x[:, :-1]).flatten(0, 1), x[:, 1:].flatten())
140
+
141
+
142
+ @torch.no_grad()
143
+ def evaluate(adapter, dev):
144
+ V = VAL.to(dev)
145
+ return sum(float(ce(adapter, V[i:i + 16])) * len(V[i:i + 16]) for i in range(0, len(V), 16)) / len(V)
146
+
147
+
148
+ def _duration(budget, steps, seed):
149
+ return int(min(280, 30 + len(BUDGETS[budget]) * steps * 0.06))
150
+
151
+
152
+ @GPU(duration=_duration)
153
+ def race_gpu(budget, steps, seed):
154
+ dev = "cuda" if torch.cuda.is_available() else "cpu"
155
+ if dev == "cuda":
156
+ torch.backends.cuda.matmul.allow_tf32 = False
157
+ torch.backends.cudnn.allow_tf32 = False
158
+ out = []
159
+ plain = _Adapter()
160
+ plain.model = [copy.deepcopy(BASE).to(dev)]
161
+ base_val = evaluate(plain, dev)
162
+ del plain
163
+ for mi, (name, build) in enumerate(BUDGETS[budget]):
164
+ torch.manual_seed(seed)
165
+ model = copy.deepcopy(BASE).to(dev).eval()
166
+ ad = build(model)
167
+ t0 = time.time()
168
+ if isinstance(ad, TP):
169
+ ad.setup([b.to(dev) for b, _ in zip(sampler(seed + 12345), range(4))])
170
+ params = ad.trainable()
171
+ opt = torch.optim.AdamW(params, lr=ad.lr, weight_decay=0.0)
172
+ warm = getattr(ad, "warmup", 0)
173
+ batches = sampler(seed)
174
+ curve = [(0, evaluate(ad, dev))]
175
+ for step in range(1, steps + 1):
176
+ if warm:
177
+ for g in opt.param_groups:
178
+ g["lr"] = ad.lr * min(1.0, step / warm)
179
+ loss = ce(ad, next(batches).to(dev))
180
+ opt.zero_grad(set_to_none=True)
181
+ loss.backward()
182
+ opt.step()
183
+ if step % 25 == 0 or step == steps:
184
+ curve.append((step, evaluate(ad, dev)))
185
+ out.append(dict(name=name, n_params=ad.n_params(), curve=curve, final=curve[-1][1],
186
+ seconds=time.time() - t0,
187
+ state=[p.detach().float().cpu().clone() for p in params]))
188
+ del model, ad, opt
189
+ return dict(budget=budget, steps=steps, seed=seed, base=base_val, runs=out)
190
+
191
+
192
+ def run_race(budget, steps, seed):
193
+ res = race_gpu(budget, int(steps), int(seed))
194
+ fig, ax = plt.subplots(figsize=(8, 4.6))
195
+ for i, r in enumerate(res["runs"]):
196
+ xs, ys = zip(*r["curve"])
197
+ ax.plot(xs, ys, color=COLORS[i % len(COLORS)], lw=2.6 if r["name"].startswith("TensorPEFT") else 1.6,
198
+ label=f"{r['name']} ({r['n_params']:,} params)")
199
+ ax.axhline(res["base"], color="#9ca3af", ls="--", lw=1, label=f"GPT-2 before fine-tuning ({res['base']:.3f})")
200
+ ax.set_xlabel("training step")
201
+ ax.set_ylabel("validation loss (nats/token) ↓ better")
202
+ ax.set_title(f"Bengali fine-tuning of GPT-2 at {res['budget']}")
203
+ ax.grid(alpha=0.25)
204
+ ax.legend(fontsize=8, loc="upper right")
205
+ fig.tight_layout()
206
+ ranked = sorted(res["runs"], key=lambda r: r["final"])
207
+ best = ranked[0]
208
+ rows = [[i + 1, r["name"], f"{r['n_params']:,}", f"{r['final']:.4f}", f"{r['final'] - best['final']:+.4f}",
209
+ f"{r['seconds']:.0f}s"] for i, r in enumerate(ranked)]
210
+ tp = next(r for r in res["runs"] if r["name"].startswith("TensorPEFT"))
211
+ others = [r for r in res["runs"] if r is not tp]
212
+ nxt = min(others, key=lambda r: r["final"])
213
+ verdict = (f"### {'🏆 TensorPEFT wins' if tp is best else 'TensorPEFT does not win this run'}\n"
214
+ f"TensorPEFT **{tp['final']:.4f}** vs best baseline ({nxt['name']}) **{nxt['final']:.4f}** "
215
+ f"→ difference **{tp['final'] - nxt['final']:+.4f}** nats/token after {res['steps']} steps (seed {res['seed']}).")
216
+ store = {"budget": res["budget"], "runs": [{"name": r["name"], "state": r["state"]} for r in res["runs"]]}
217
+ return fig, rows, verdict, store
218
+
219
+
220
+ @GPU(duration=90)
221
+ def generate_gpu(store, prompt, n_new, temperature, seed):
222
+ dev = "cuda" if torch.cuda.is_available() else "cpu"
223
+ ids0 = tok(prompt, return_tensors="pt").input_ids.to(dev)
224
+ builders = dict(BUDGETS[store["budget"]]) if store else {}
225
+ entries = [("GPT-2 before fine-tuning", None, None)] + \
226
+ [(r["name"], builders[r["name"]], r["state"]) for r in (store["runs"] if store else [])]
227
+ outs = []
228
+ for name, build, state in entries:
229
+ model = copy.deepcopy(BASE).to(dev).eval()
230
+ if build is None:
231
+ ad = _Adapter()
232
+ ad.model = [model]
233
+ else:
234
+ ad = build(model)
235
+ if isinstance(ad, TP):
236
+ ad.mark_ready()
237
+ with torch.no_grad():
238
+ for p, s in zip(ad.trainable(), state):
239
+ p.copy_(s.to(p.device, p.dtype))
240
+ g = torch.Generator(device=dev).manual_seed(int(seed))
241
+ ids = ids0.clone()
242
+ with torch.no_grad():
243
+ for _ in range(int(n_new)):
244
+ logits = ad.logits(ids[:, -512:])[:, -1].float() / max(float(temperature), 1e-3)
245
+ top = torch.topk(logits, 50)
246
+ probs = torch.softmax(top.values, -1)
247
+ nxt = top.indices.gather(-1, torch.multinomial(probs, 1, generator=g))
248
+ ids = torch.cat([ids, nxt], dim=1)
249
+ outs.append((name, tok.decode(ids[0, ids0.shape[1]:], skip_special_tokens=True)))
250
+ del model, ad
251
+ return outs
252
+
253
+
254
+ def run_generate(store, prompt, n_new, temperature, seed):
255
+ if not prompt.strip():
256
+ return "Enter a Bengali prompt."
257
+ outs = generate_gpu(store, prompt, n_new, temperature, seed)
258
+ note = "" if store else "*Run a race first to compare the fine-tuned models; showing the base model only.*\n\n"
259
+ return note + "\n\n".join(f"**{n}**\n\n> {prompt}{t}" for n, t in outs)
260
+
261
+
262
+ # ------------------------------------------------------------------ benchmark results (CPU, 300 steps)
263
+ RESULTS_MD = """
264
+ ### Three seeds (mean ± std of validation loss; lower is better)
265
+
266
+ | budget | TensorPEFT | best baseline | difference | seeds won | p (paired t) |
267
+ |---|---:|---|---:|:---:|---:|
268
+ | ≈9k | **1.3245 ± 0.004** | prompt tuning 1.3684 ± 0.010 | **−0.044** | 3/3 | 0.008 |
269
+ | ≈37k | 1.2558 ± 0.010 | LayerNorm tuning (38.4k) 1.2504 ± 0.003 | +0.005 | 1/3 | 0.37 (tie) |
270
+ | ≈55k | **1.2368 ± 0.017** | (IA)³ 1.2880 ± 0.003 | **−0.051** | 3/3 | 0.026 |
271
+
272
+ ### Every method at every budget (seed 0)
273
+
274
+ | budget | TensorPEFT | LoRA | QLoRA | VeRA | (IA)³ | prompt tuning | LayerNorm tuning | BitFit |
275
+ |---|---:|---:|---:|---:|---:|---:|---:|---:|
276
+ | ≈1.5k | **1.461** | – | – | – | – | 1.493 (768) | 1.600 | – |
277
+ | ≈9k | **1.327** | 1.430 | – | – | 1.432 | 1.370 | 1.407 | – |
278
+ | ≈37k | 1.255 | 1.318 | 1.335 | 1.393 | – | 1.327 | **1.253** | – |
279
+ | ≈55k | **1.231** | – | – | – | 1.289 | – | – | – |
280
+ | ≈83k | – | – | – | – | – | – | – | 1.326 |
281
+
282
+ GPT-2 124M before fine-tuning: 1.708. 300 AdamW steps, batch 8 × 128 tokens, identical data for all methods,
283
+ baseline learning rates from their papers. “–”: the method cannot be configured at that budget on GPT-2 or was
284
+ not run there (LoRA's smallest all-layer setting is 37k). At larger budgets (≥ 115k parameters) LoRA was
285
+ better in our tests — TensorPEFT is a method for very small budgets.
286
+ """
287
+
288
+
289
+ def results_figure():
290
+ pts = {
291
+ "TensorPEFT": [(1553, 1.461), (9259, 1.327), (37036, 1.255), (55554, 1.231)],
292
+ "LoRA / QLoRA": [(9216, 1.430), (36864, 1.318), (36864, 1.335)],
293
+ "Prompt tuning": [(768, 1.493), (9216, 1.370), (36864, 1.327)],
294
+ "LayerNorm tuning": [(1536, 1.600), (9216, 1.407), (38400, 1.253)],
295
+ "(IA)³": [(9216, 1.432), (55296, 1.289)],
296
+ "VeRA": [(24576, 1.393)],
297
+ "BitFit": [(82944, 1.326)],
298
+ }
299
+ fig, ax = plt.subplots(figsize=(8, 4.6))
300
+ for i, (k, v) in enumerate(pts.items()):
301
+ xs, ys = zip(*v)
302
+ if k == "TensorPEFT":
303
+ ax.plot(xs, ys, "-o", color=COLORS[0], lw=2.6, ms=7, label=k, zorder=5)
304
+ else:
305
+ ax.scatter(xs, ys, s=45, color=["#0ea5e9", "#f59e0b", "#10b981", "#ef4444", "#64748b", "#a16207"][i - 1],
306
+ label=k)
307
+ ax.set_xscale("log")
308
+ ax.set_xlabel("trainable parameters (log scale)")
309
+ ax.set_ylabel("validation loss (nats/token) ↓ better")
310
+ ax.set_title("GPT-2 → Bengali: loss vs. trainable parameters (seed 0)")
311
+ ax.grid(alpha=0.25, which="both")
312
+ ax.legend(fontsize=8)
313
+ fig.tight_layout()
314
+ return fig
315
+
316
+
317
+ # ------------------------------------------------------------------ UI
318
+ INTRO = """
319
+ # TensorPEFT · tensor gradient-pursuit fine-tuning
320
+ LoRA gives every weight matrix its own low-rank update. **TensorPEFT** stacks all equal-shaped matrices of the
321
+ network into one tensor (layers × {q,k,v,o} × d_in × d_out) and learns a few **rank-one CP atoms** of it, chosen from
322
+ one gradient probe before training. Each atom touches every layer for only L + P + d_in + d_out + 1 parameters.
323
+
324
+ Here GPT-2 (English) is taught **Bengali** with the *same number of trainable parameters* for every method.
325
+ `pip install git+https://huggingface.co/Angshul/TensorPEFT`
326
+ """
327
+
328
+ with gr.Blocks(title="TensorPEFT") as demo:
329
+ gr.Markdown(INTRO)
330
+ store = gr.State(None)
331
+ with gr.Tab("Live race"):
332
+ with gr.Row():
333
+ budget = gr.Radio(list(BUDGETS), value="≈9k parameters", label="Trainable-parameter budget")
334
+ steps = gr.Number(value=300, visible=False)
335
+ seed = gr.Number(value=0, precision=0, label="Seed")
336
+ gr.Markdown("Every method trains for **300 AdamW steps** (batch 8 × 128 tokens) on identical batches — the setting of the benchmark. "
337
+ "TensorPEFT starts from zero coefficients, so it trails in the first few dozen steps and overtakes later.")
338
+ go = gr.Button("Run the race", variant="primary")
339
+ verdict = gr.Markdown()
340
+ plot = gr.Plot()
341
+ table = gr.Dataframe(headers=["rank", "method", "params", "val loss", "vs best", "time"], interactive=False)
342
+ go.click(run_race, [budget, steps, seed], [plot, table, verdict, store])
343
+ with gr.Tab("Generate"):
344
+ gr.Markdown("Continuations from the models trained in your last race (top-k 50 sampling).")
345
+ prompt = gr.Textbox(value="কলকাতা শহরে আজ", label="Bengali prompt")
346
+ with gr.Row():
347
+ n_new = gr.Slider(20, 120, value=60, step=10, label="New tokens")
348
+ temp = gr.Slider(0.3, 1.2, value=0.8, step=0.1, label="Temperature")
349
+ gseed = gr.Number(value=0, precision=0, label="Seed")
350
+ gen = gr.Button("Generate", variant="primary")
351
+ gen_out = gr.Markdown()
352
+ gen.click(run_generate, [store, prompt, n_new, temp, gseed], gen_out)
353
+ with gr.Tab("Benchmark results"):
354
+ gr.Plot(value=results_figure())
355
+ gr.Markdown(RESULTS_MD)
356
+
357
+ if __name__ == "__main__":
358
+ demo.launch()
baselines.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Matched-budget PEFT baselines for the demo (GPT-2), built on tensorpeft's slot discovery."""
2
+ import math
3
+
4
+ import torch
5
+ import torch.nn as nn
6
+ from tensorpeft.layers import attach_hooks, find_slots
7
+
8
+
9
+ class _Adapter(nn.Module):
10
+ lr = 1e-3
11
+
12
+ def trainable(self):
13
+ return [p for p in self.parameters() if p.requires_grad]
14
+
15
+ def n_params(self):
16
+ return sum(p.numel() for p in self.trainable())
17
+
18
+ def logits(self, ids):
19
+ return self.model[0](ids).logits
20
+
21
+
22
+ class LoRA(_Adapter):
23
+ lr = 1e-3
24
+
25
+ def __init__(self, model, rank=1, targets=("c_attn.q", "c_attn.v"), layers=None, seed=0):
26
+ super().__init__()
27
+ self.model = [model]
28
+ self.slots = [s for s in find_slots(model, list(targets)) if layers is None or s.layer in layers]
29
+ g = torch.Generator().manual_seed(seed)
30
+ dev = next(model.parameters()).device
31
+ self.A = nn.ParameterList([nn.Parameter(((torch.rand(s.shape[0], rank, generator=g) * 2 - 1)
32
+ / math.sqrt(s.shape[0])).to(dev)) for s in self.slots])
33
+ self.B = nn.ParameterList([nn.Parameter(torch.zeros(rank, s.shape[1], device=dev)) for s in self.slots])
34
+ self.scale = 2.0
35
+ self._i = {id(s): i for i, s in enumerate(self.slots)}
36
+ self._h = attach_hooks(self.slots, lambda s, x: (x @ self.A[self._i[id(s)]]) @ self.B[self._i[id(s)]] * self.scale)
37
+
38
+
39
+ class IA3(_Adapter):
40
+ lr = 3e-3
41
+
42
+ def __init__(self, model, kv=("c_attn.k", "c_attn.v"), ff=True):
43
+ super().__init__()
44
+ self.model = [model]
45
+ self.out_slots = find_slots(model, list(kv))
46
+ self.ff_slots = find_slots(model, ["mlp.c_proj"]) if ff else []
47
+ dev = next(model.parameters()).device
48
+ self.l_out = nn.ParameterList([nn.Parameter(torch.ones(s.shape[1], device=dev)) for s in self.out_slots])
49
+ self.l_ff = nn.ParameterList([nn.Parameter(torch.ones(s.shape[0], device=dev)) for s in self.ff_slots])
50
+ io = {id(s): i for i, s in enumerate(self.out_slots)}
51
+ iff = {id(s): i for i, s in enumerate(self.ff_slots)}
52
+
53
+ def d_out(s, x):
54
+ y = x @ s.weight() + s.module.bias[s.c0:s.c1]
55
+ return y * (self.l_out[io[id(s)]] - 1)
56
+
57
+ def d_ff(s, x):
58
+ return (x * (self.l_ff[iff[id(s)]] - 1)) @ s.weight()
59
+
60
+ self._h = attach_hooks(self.out_slots, d_out) + attach_hooks(self.ff_slots, d_ff)
61
+
62
+
63
+ class Prompt(_Adapter):
64
+ lr = 3e-2
65
+
66
+ def __init__(self, model, n, seed=0):
67
+ super().__init__()
68
+ self.model = [model]
69
+ emb = model.get_input_embeddings().weight
70
+ g = torch.Generator().manual_seed(seed)
71
+ ids = torch.randint(0, emb.shape[0], (n,), generator=g).to(emb.device)
72
+ self.n = n
73
+ self.P = nn.Parameter(emb[ids].detach().clone())
74
+
75
+ def logits(self, ids):
76
+ m = self.model[0]
77
+ e = torch.cat([self.P.expand(ids.shape[0], -1, -1), m.get_input_embeddings()(ids)], dim=1)
78
+ return m(inputs_embeds=e).logits[:, self.n:]
79
+
80
+
81
+ class LNTune(_Adapter):
82
+ lr = 1e-3
83
+
84
+ def __init__(self, model, layers=None, final=True):
85
+ super().__init__()
86
+ self.model = [model]
87
+ self.params = []
88
+ for name, mod in model.named_modules():
89
+ if isinstance(mod, nn.LayerNorm):
90
+ parts = name.split(".")
91
+ layer = next((int(p) for p in parts if p.isdigit()), None)
92
+ if (layer is None and final) or (layer is not None and (layers is None or layer in layers)):
93
+ for p in mod.parameters():
94
+ p.requires_grad_(True)
95
+ self.params.append(p)
96
+
97
+ def trainable(self):
98
+ return self.params
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ git+https://huggingface.co/Angshul/TensorPEFT
2
+ torch
3
+ transformers>=4.40
4
+ matplotlib
5
+ numpy