Text Classification
Transformers
lora
fine-tuning
adaptive
research
nested-lora
synaptic-plasticity
rank-adaptation
Instructions to use Simo76/Unified-LoRA with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Simo76/Unified-LoRA with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="Simo76/Unified-LoRA")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Simo76/Unified-LoRA", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Rename project to Unified-LoRA and enhance README
Browse filesUpdated README to reflect project name change and improved clarity on functionality and results.
README.md
CHANGED
|
@@ -1,120 +1,157 @@
|
|
| 1 |
-
# Unified
|
| 2 |
|
| 3 |
-
**Adaptive
|
| 4 |
|
| 5 |
-
|
| 6 |
|
| 7 |
-
##
|
| 8 |
|
| 9 |
-
|
| 10 |
|
| 11 |
-
|
| 12 |
-
- **Mode 1 (Multi)**: Task-specific LoRA adapters for moderate stress
|
| 13 |
-
- **Mode 2 (Mirror)**: Stability snapshots for catastrophic forgetting prevention
|
| 14 |
|
| 15 |
-
|
| 16 |
-
- **C**: Task conflict (weight space variance)
|
| 17 |
-
- **E**: Multi-task error
|
| 18 |
-
- **S**: Memory stability
|
| 19 |
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
|
| 23 |
|
| 24 |
-
##
|
| 25 |
|
| 26 |
-
|
| 27 |
-
This experiment illustrates the behavior of **φ(t)** under induced instability; it is **not a production deployment claim**.
|
| 28 |
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
φ returns close to its pre-shock regime after recovery (example: 0.33 → 0.83 → 0.33), indicating reversible stress handling within this setup.
|
| 38 |
|
| 39 |
-
##
|
| 40 |
|
| 41 |
-
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
| Unified LoRA | 0.785 | 0.646 | 0.367 | 1 |
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
##
|
| 52 |
|
| 53 |
-
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
theta0=0.3, # Single/Multi threshold
|
| 60 |
-
theta1=0.7 # Multi/Mirror threshold
|
| 61 |
-
)
|
| 62 |
|
| 63 |
-
|
| 64 |
-
for step, batch in enumerate(train_loader):
|
| 65 |
-
outputs = model(**batch)
|
| 66 |
-
loss = outputs.loss
|
| 67 |
|
| 68 |
-
|
| 69 |
-
new_lr = controller.update(loss.item())
|
| 70 |
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
|
| 76 |
-
loss.backward()
|
| 77 |
-
optimizer.step()
|
| 78 |
-
optimizer.zero_grad()
|
| 79 |
|
| 80 |
-
##
|
| 81 |
|
| 82 |
-
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
| 87 |
|
| 88 |
-
##
|
| 89 |
|
| 90 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
|
| 92 |
-
|
| 93 |
-
D = E_smooth / (1 + E_smooth)
|
| 94 |
-
φ = (1 - α) * φ + α * D
|
| 95 |
|
| 96 |
-
|
| 97 |
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
## Citation
|
| 103 |
|
|
|
|
|
|
|
|
|
|
| 104 |
@software{unified_lora_2025,
|
| 105 |
author = {Simona Vargiu},
|
| 106 |
-
title = {Unified
|
| 107 |
year = {2025},
|
| 108 |
-
url = {https://github.com/Sva76/Unified-
|
| 109 |
}
|
| 110 |
-
|
| 111 |
-
## License
|
| 112 |
-
|
| 113 |
-
Apache License 2.0 — see LICENSE for details.
|
| 114 |
|
| 115 |
## Contact
|
| 116 |
|
| 117 |
-
Simona Vargiu (Independent Researcher)
|
| 118 |
For collaboration inquiries: simona.vargiu.malta@gmail.com
|
| 119 |
|
| 120 |
-
|
|
|
|
|
|
|
|
|
| 1 |
+
# Unified-LoRA
|
| 2 |
|
| 3 |
+
**Adaptive rank controller for LoRA fine-tuning.**
|
| 4 |
|
| 5 |
+
A lightweight per-layer controller that dynamically adjusts LoRA rank during training based on gradient stress, eliminating manual rank selection.
|
| 6 |
|
| 7 |
+
## What it does
|
| 8 |
|
| 9 |
+
Instead of fixing `rank=8` or `rank=16` and hoping it works, Unified-LoRA adapts the rank of each layer independently during training. Layers under stress get more capacity; stable layers get less. No grid search, no guessing.
|
| 10 |
|
| 11 |
+
## Key results
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
Evaluated on 4 GLUE tasks with DistilBERT-base-uncased, 3 epochs, LR=5e-4, α=16:
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
+
| Task | Metric | Baseline (r=16) | Adaptive | Avg Rank | Rank Reduction |
|
| 16 |
+
|------|--------|-----------------|----------|----------|----------------|
|
| 17 |
+
| MRPC | F1 | 0.882 | **0.886** | 9.3 | 42% |
|
| 18 |
+
| SST-2 | Accuracy | **0.898** | 0.885 | 7.0 | 56% |
|
| 19 |
+
| CoLA | MCC | 0.488 | **0.491** | 7.1 | 56% |
|
| 20 |
+
| RTE | Accuracy | 0.556 | **0.592** | 10.8 | 33% |
|
| 21 |
|
| 22 |
+
**Summary:** Comparable or better performance on 3/4 tasks with 33-56% fewer active rank parameters.
|
| 23 |
|
| 24 |
+
## How it works
|
| 25 |
|
| 26 |
+
Each LoRA adapter tracks the exponential moving average of its gradient norm. When the gradient stress increases (loss landscape is rough), the controller increases rank. When stress decreases (training is stable), rank is reduced.
|
|
|
|
| 27 |
|
| 28 |
+
```
|
| 29 |
+
For each layer, at each step:
|
| 30 |
+
1. Compute grad_norm of LoRA parameters
|
| 31 |
+
2. Update EMA: stress = 0.9 * stress + 0.1 * grad_norm
|
| 32 |
+
3. If stress trend is increasing → rank += 2
|
| 33 |
+
4. If stress trend is decreasing → rank -= 2
|
| 34 |
+
5. Forward pass uses α/r scaling (standard LoRA)
|
| 35 |
+
```
|
| 36 |
|
| 37 |
+
The controller adds ~30 lines of code and zero computational overhead beyond gradient norm computation.
|
|
|
|
| 38 |
|
| 39 |
+
## Per-layer behavior
|
| 40 |
|
| 41 |
+
The controller discovers meaningful patterns automatically:
|
| 42 |
|
| 43 |
+
- **v_proj consistently needs more rank than q_proj** across all tasks
|
| 44 |
+
- **Deep layers (4-5) need more rank** than early layers on complex tasks
|
| 45 |
+
- **Easier tasks (SST-2) converge to lower rank** than harder tasks (RTE)
|
|
|
|
| 46 |
|
| 47 |
+
Example per-layer rank on MRPC:
|
| 48 |
+
```
|
| 49 |
+
layer0.q: 8.5 layer0.v: 8.3
|
| 50 |
+
layer1.q: 7.8 layer1.v: 7.6
|
| 51 |
+
layer2.q: 10.1 layer2.v: 8.3
|
| 52 |
+
layer3.q: 8.1 layer3.v: 10.1
|
| 53 |
+
layer4.q: 10.0 layer4.v: 12.1
|
| 54 |
+
layer5.q: 8.1 layer5.v: 12.3
|
| 55 |
+
```
|
| 56 |
|
| 57 |
+
## What was tested and didn't improve results
|
| 58 |
|
| 59 |
+
In the interest of scientific honesty, the following extensions were tested and did **not** outperform the simple Adaptive controller:
|
| 60 |
|
| 61 |
+
- **Fluid dynamics metrics** (shock, vorticity, swirl as stress signal): controller became too conservative, suppressing rank across all tasks
|
| 62 |
+
- **Budget redistribution** (fixed total rank budget shared across layers): "winner takes all" problem — high-stress layers starved low-stress layers
|
| 63 |
+
- **Adaptive gradient clipping** driven by swirl: helped on small tasks (RTE +2.5%), hurt on large tasks (SST-2 -1.7%)
|
| 64 |
+
- **Scaling without α/r**: performance came from implicit norm regulation, not true capacity control
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
The simple version works best. Complexity did not pay.
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
## Comparison with existing methods
|
|
|
|
| 69 |
|
| 70 |
+
| Method | Approach | Overhead | Our advantage |
|
| 71 |
+
|--------|----------|----------|---------------|
|
| 72 |
+
| AdaLoRA | SVD importance scoring per layer | High (SVD each step) | ~30 lines, zero SVD |
|
| 73 |
+
| DyLoRA | Train on multiple ranks simultaneously | Medium | Runtime adaptation, not post-hoc |
|
| 74 |
+
| Fixed LoRA | Manual rank selection | None | No guessing required |
|
| 75 |
|
| 76 |
+
Note: Direct numerical comparison with AdaLoRA was attempted but AdaLoRA did not function correctly in our setup (no rank pruning occurred). A fair comparison requires architecture-specific tuning of AdaLoRA scheduling parameters.
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
+
## Reproduce
|
| 79 |
|
| 80 |
+
Run `benchmark.py` on Google Colab with a T4 GPU (~30 min):
|
| 81 |
|
| 82 |
+
```bash
|
| 83 |
+
pip install transformers datasets evaluate accelerate scikit-learn
|
| 84 |
+
python benchmark.py
|
| 85 |
+
```
|
| 86 |
|
| 87 |
+
## Limitations
|
| 88 |
|
| 89 |
+
- Validated on DistilBERT (67M) and TinyLlama (1.1B) only
|
| 90 |
+
- Single-seed runs (variance not quantified)
|
| 91 |
+
- GLUE tasks only — no generation or instruction-following evaluation
|
| 92 |
+
- Rank changes don't reduce peak memory (matrices allocated at max_rank)
|
| 93 |
+
- Throughput overhead from dynamic slicing on small models
|
| 94 |
|
| 95 |
+
## Adapter size reduction
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
With average rank ~7 vs fixed rank 16:
|
| 98 |
|
| 99 |
+
| Model | r=16 adapter | r=7 adapter | Reduction |
|
| 100 |
+
|-------|-------------|-------------|-----------|
|
| 101 |
+
| DistilBERT | 4.3 MB | 1.9 MB | 56% |
|
| 102 |
+
| 7B (projected) | ~70 MB | ~31 MB | 56% |
|
| 103 |
+
| 70B × 100 tenants | ~7 GB | ~3.1 GB | 56% |
|
| 104 |
|
| 105 |
+
## Two validated systems
|
| 106 |
+
|
| 107 |
+
Unified-LoRA contains two complementary approaches, both validated:
|
| 108 |
+
|
| 109 |
+
### 1. FSM Mode Controller (φ(t))
|
| 110 |
+
|
| 111 |
+
Validated on Tinker with Llama-3.2-1B. A finite state machine driven by a synaptic stress parameter φ(t) = f(C, E, S) that switches between three operational modes:
|
| 112 |
+
|
| 113 |
+
- **Mode 0 (Single):** shared adapter, low stress (φ < 0.3)
|
| 114 |
+
- **Mode 1 (Multi):** task-specific adapters, moderate stress (φ < 0.7)
|
| 115 |
+
- **Mode 2 (Mirror):** stability snapshots, high stress (φ ≥ 0.7)
|
| 116 |
+
|
| 117 |
+
Demonstrated full stress → recovery cycle:
|
| 118 |
+
```
|
| 119 |
+
[250] Mode=1 φ=0.333 (stable)
|
| 120 |
+
SHOCK @ step 300
|
| 121 |
+
[350] Mode=2 φ=0.827 (Mirror activated)
|
| 122 |
+
RECOVERY @ step 500
|
| 123 |
+
[550] Mode=1 φ=0.371 (return)
|
| 124 |
+
[700] Mode=1 φ=0.333 (baseline restored)
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
Key finding: φ returns to pre-shock regime after recovery (0.33 → 0.83 → 0.33), indicating reversible stress handling.
|
| 128 |
+
|
| 129 |
+
### 2. Per-layer Adaptive Rank Controller
|
| 130 |
+
|
| 131 |
+
Validated on DistilBERT across 4 GLUE tasks (results table above). Each layer independently adjusts its LoRA rank based on gradient stress EMA. This is the simpler, more broadly validated system.
|
| 132 |
+
|
| 133 |
+
### Evolution
|
| 134 |
+
|
| 135 |
+
The project progressed from discrete mode switching (FSM) to continuous per-layer rank adaptation. Intermediate explorations included fluid dynamics metrics (shock, vorticity, swirl) and budget redistribution — these were tested rigorously but did not outperform the simple per-layer EMA approach. Details in "What was tested" above.
|
| 136 |
|
| 137 |
## Citation
|
| 138 |
|
| 139 |
+
If you use this work:
|
| 140 |
+
|
| 141 |
+
```
|
| 142 |
@software{unified_lora_2025,
|
| 143 |
author = {Simona Vargiu},
|
| 144 |
+
title = {Unified-LoRA: Adaptive Rank Controller for LoRA Fine-tuning},
|
| 145 |
year = {2025},
|
| 146 |
+
url = {https://github.com/Sva76/Unified-LoRa}
|
| 147 |
}
|
| 148 |
+
```
|
|
|
|
|
|
|
|
|
|
| 149 |
|
| 150 |
## Contact
|
| 151 |
|
| 152 |
+
Simona Vargiu (Independent Researcher)
|
| 153 |
For collaboration inquiries: simona.vargiu.malta@gmail.com
|
| 154 |
|
| 155 |
+
## License
|
| 156 |
+
|
| 157 |
+
Apache License 2.0 — see LICENSE for details.
|