File size: 7,525 Bytes
29e60f4
 
ba9bed3
 
 
 
 
 
 
 
 
 
29e60f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# TensorPEFT

   ---
   license: apache-2.0
   library_name: tensorpeft
   tags:
   - peft
   - fine-tuning
   - tensor-decomposition
   - lora-alternative
   ---
     
Parameter-efficient fine-tuning with **tensor gradient-pursuit adapters**. TensorPEFT is a drop-in
alternative to LoRA for Hugging Face models, designed for very small trainable-parameter budgets.

LoRA gives every weight matrix its own low-rank update. TensorPEFT instead stacks all
equal-shaped matrices of the network into one tensor, for example
`layers x {q,k,v,o} x d_in x d_out`, and learns a few **rank-one CP atoms** of that tensor:

    dW = sum_i  a_i  u_i(layer) o u_i(role) o u_i(in) o u_i(out)

Each atom couples every layer and projection while costing only
`L + P + d_in + d_out + 1` parameters. The atoms are not random. They are the leading rank-one
CP components of the negative gradient tensor (higher-order power iteration with deflation),
computed from **one gradient probe** before training. The coefficients start at zero, so the
model is unchanged at step 0, and every atom starts on a steepest-descent direction. After that,
training is ordinary: any optimiser and the HF `Trainer` both work.

## Install

    pip install tensorpeft          # once published; until then: pip install .

It requires `torch>=2.1`, `transformers>=4.40`, `safetensors` and `huggingface_hub`.

## Quick start

```python
from transformers import AutoModelForCausalLM
from tensorpeft import TensorPEFTConfig, get_tensorpeft_model, TensorPEFTModel

base = AutoModelForCausalLM.from_pretrained("gpt2")
model = get_tensorpeft_model(base, TensorPEFTConfig(atoms_per_group=4))

model.init_atoms(train_dataloader, n_batches=4)   # the one step LoRA does not have
model.print_trainable_parameters()                # trainable params: 37,036 || ... || 0.0298%

# train as usual: AdamW, lr ~3e-3, short warm-up, weight_decay 0  (or TensorPEFTTrainer below)

model.save_pretrained("my-adapter")               # adapter_config.json + adapter_model.safetensors
model.push_to_hub("your-name/my-adapter")

base = AutoModelForCausalLM.from_pretrained("gpt2")
model = TensorPEFTModel.from_pretrained(base, "your-name/my-adapter")   # local dir or Hub id
merged = model.merge_and_unload()                 # plain HF model, zero inference overhead
```

`train_dataloader` can yield dicts (`input_ids`, optional `attention_mask` / `labels`) or plain
input-id tensors. For causal LMs, labels default to the inputs.

### With the Hugging Face Trainer

```python
from tensorpeft import TensorPEFTTrainer
trainer = TensorPEFTTrainer(model=model, args=training_args, train_dataset=ds, data_collator=collator)
trainer.train()             # checkpoints contain only the adapter
```

`TensorPEFTTrainer` is the stock `Trainer` except that it saves adapters instead of full model
weights. A complete script is in `examples/train_with_trainer.py`.

## Configuration

| `TensorPEFTConfig` field | default | meaning |
|---|---|---|
| `atoms_per_group` | 4 | rank-one atoms per tensor group; this sets the budget |
| `target_modules` | `"all-linear"` | or a list of name suffixes, e.g. `["q_proj", "v_proj"]` (`"c_attn.q"` for GPT-2) |
| `exclude_modules` | `lm_head`, embeddings | never adapted |
| `split_fused_qkv` | True | split GPT-2's fused `c_attn` into q, k and v |
| `init_batches` | 4 | batches used by `init_atoms` |
| `hopm_iters` | 30 | power-iteration sweeps per atom |
| `alpha_scale` | 33.3 | coefficient reparametrisation so that one learning rate (~3e-3) fits coefficients and factors |

**Budget.** Groups are formed automatically by matrix shape. GPT-2 small has three groups:
`{q,k,v,o}` (12x4x768x768), MLP-in (12x768x3072) and MLP-out (12x3072x768). One atom per group
is 9,259 parameters, four per group is 37,036, and six per group is 55,554.
`print_trainable_parameters()` shows the exact count for any model.

**Recipe.** Use AdamW at lr 3e-3 with about 10 warm-up steps, then a constant schedule and
weight decay 0. Use all atoms from the start: adding atoms gradually during training was clearly
worse in our tests.

## Results

Setup: GPT-2 124M (English base) fine-tuned on Bengali news (3M tokens), 300 AdamW steps,
batch 8 x 128 tokens. Validation cross-entropy is in nats/token, lower is better; the base model
scores 1.708. All methods saw identical data, and baseline learning rates follow their papers.

Three seeds, best baseline per budget:

| budget | TensorPEFT | best baseline | difference | seeds won | p (paired t) |
|---|---:|---|---:|:---:|---:|
| ~9k params | **1.3245 ± 0.004** | prompt tuning: 1.3684 ± 0.010 | **-0.044** | 3/3 | 0.008 |
| ~37k | 1.2558 ± 0.010 | LayerNorm tuning (38.4k): 1.2504 ± 0.003 | +0.005 | 1/3 | 0.37 (tie) |
| ~55k | **1.2368 ± 0.017** | (IA)³: 1.2880 ± 0.003 | **-0.051** | 3/3 | 0.026 |

All baselines at each budget (seed 0):

| budget | TensorPEFT | LoRA | QLoRA | VeRA | (IA)³ | prompt tuning | LayerNorm tuning | BitFit |
|---|---:|---:|---:|---:|---:|---:|---:|---:|
| ~1.5k | **1.461** | - | - | - | - | 1.493 (768 params) | 1.600 | - |
| ~9k | **1.327** | 1.430 | - | - | 1.432 | 1.370 | 1.407 | - |
| ~37k | 1.255 | 1.318 | 1.335 | 1.393 (24.6k) | - | 1.327 | **1.253** | - |
| ~55k | **1.231** | - | - | - | 1.289 | - | - | - |
| ~83k | - | - | - | - | - | - | - | 1.326 |

TensorPEFT budgets map to configs as follows: ~1.5k is one atom on the attention tensor only
(`target_modules=["c_attn.q", "c_attn.k", "c_attn.v", "attn.c_proj"]`, `atoms_per_group=1`),
~9k is `atoms_per_group=1`, ~37k is 4 and ~55k is 6. A dash means the method cannot be
configured at that budget on GPT-2, or was not run there.
LoRA's smallest all-layer configuration is r=1 on q,v (37k parameters). At 9k it is LoRA r=1 on
q,v of the last 3 layers.

**What this does and does not show.** TensorPEFT beats every baseline from about 1.5k to
about 9k parameters and at about 55k, and ties LayerNorm tuning at about 37k. With larger
budgets (≥ 115k) LoRA was better in our tests. The evidence covers one base model, one task and
short training. Results on other models and downstream tasks are not yet available.

`examples/reproduce_gpt2_bengali.py` reruns these benchmarks with this package
(`--atoms-per-group 1 | 4 | 6`). The
data comes from the Bengali news corpus at github.com/soham96/Bengali_news_classifier,
tokenised with the GPT-2 tokenizer.

## Supported models

Any model whose transformer blocks use `nn.Linear` or GPT-2-style `Conv1D` layers inside
numbered blocks (`...layers.N...` / `...h.N...`). Tested on GPT-2, Llama (GQA), Qwen2, Mistral,
Phi and Gemma configurations. Fused projections other than GPT-2's `c_attn` are treated as a
single matrix.

## Limitations (v0.1)

- **GPU support is untested.** The code is device- and dtype-agnostic (adapter parameters in
  fp32, bf16/fp16 bases supported), but all tests and benchmarks so far ran on CPU.
- **Probe memory.** `init_atoms` needs one backward pass with gradients on the targeted weights,
  plus an fp32 copy of each group's gradient tensor. That is roughly 2x the size of the targeted
  weights, temporarily. Use `target_modules` to limit it for large models.
- **Quantised bases** (bitsandbytes 4/8-bit) are not supported yet, because the probe needs
  weight gradients.
- **No checkpoint resume.** `Trainer` resume-from-checkpoint is not implemented; reload with
  `TensorPEFTModel.from_pretrained(..., is_trainable=True)`.

## Tests

    pip install .[test]
    pytest tests

## License

Apache-2.0. Copyright 2026 Angshul Majumdar.