gbra 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gbra/__init__.py +29 -0
- gbra/callback.py +32 -0
- gbra/layer.py +97 -0
- gbra/model.py +123 -0
- gbra-0.1.1.dist-info/METADATA +108 -0
- gbra-0.1.1.dist-info/RECORD +9 -0
- gbra-0.1.1.dist-info/WHEEL +5 -0
- gbra-0.1.1.dist-info/licenses/LICENSE +21 -0
- gbra-0.1.1.dist-info/top_level.txt +1 -0
gbra/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""GBRA - Gember-Berke Rank Adaptation ("Gember RA").
|
|
2
|
+
|
|
3
|
+
DoRA-style fine-tuning with a buffered (lazily refreshed) weight norm.
|
|
4
|
+
GEMBER && Apex Logic Studio - Berke T. Adanc
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .layer import GBRALinear
|
|
8
|
+
from .model import (
|
|
9
|
+
DEFAULT_TARGETS,
|
|
10
|
+
apply_gbra, refresh_norms, save_gbra, load_gbra, merge_gbra, gbra_state_dict,
|
|
11
|
+
gbra_ekle, tum_normlari_yenile, gbra_kaydet, gbra_yukle, gbra_birlestir,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def __getattr__(name):
|
|
18
|
+
# GBRACallback needs transformers; import it lazily so the core works without it.
|
|
19
|
+
if name == "GBRACallback":
|
|
20
|
+
from .callback import GBRACallback
|
|
21
|
+
return GBRACallback
|
|
22
|
+
raise AttributeError(name)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"GBRALinear", "GBRACallback", "DEFAULT_TARGETS",
|
|
27
|
+
"apply_gbra", "refresh_norms", "save_gbra", "load_gbra", "merge_gbra", "gbra_state_dict",
|
|
28
|
+
"gbra_ekle", "tum_normlari_yenile", "gbra_kaydet", "gbra_yukle", "gbra_birlestir",
|
|
29
|
+
]
|
gbra/callback.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Hugging Face Trainer integration."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from transformers import TrainerCallback
|
|
6
|
+
|
|
7
|
+
from .model import refresh_norms
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GBRACallback(TrainerCallback):
|
|
11
|
+
"""Refreshes GBRA norms every `refresh_every` optimizer steps (K).
|
|
12
|
+
|
|
13
|
+
Use refresh_every=None to never refresh during training (the norm stays at its
|
|
14
|
+
initial value). With refresh_on_end=True (default) the norm is refreshed once more
|
|
15
|
+
when training finishes, so a saved adapter always carries an up-to-date norm.
|
|
16
|
+
|
|
17
|
+
trainer = Trainer(..., callbacks=[GBRACallback(refresh_every=10)])
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, refresh_every: Optional[int] = 10, refresh_on_end: bool = True):
|
|
21
|
+
if refresh_every is not None and refresh_every < 1:
|
|
22
|
+
raise ValueError("refresh_every must be >= 1 or None")
|
|
23
|
+
self.k = refresh_every
|
|
24
|
+
self.refresh_on_end = refresh_on_end
|
|
25
|
+
|
|
26
|
+
def on_step_end(self, args, state, control, model=None, **kwargs):
|
|
27
|
+
if self.k is not None and model is not None and state.global_step % self.k == 0:
|
|
28
|
+
refresh_norms(model)
|
|
29
|
+
|
|
30
|
+
def on_train_end(self, args, state, control, model=None, **kwargs):
|
|
31
|
+
if self.refresh_on_end and model is not None:
|
|
32
|
+
refresh_norms(model)
|
gbra/layer.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""GBRALinear: the core GBRA layer.
|
|
2
|
+
|
|
3
|
+
GBRA (Gember-Berke Rank Adaptation, read "Gember RA") keeps DoRA's
|
|
4
|
+
magnitude/direction split but buffers the column-norm instead of
|
|
5
|
+
recomputing it on every forward pass:
|
|
6
|
+
|
|
7
|
+
DoRA: W' = m * (W + s*B@A) / ||W + s*B@A|| (norm recomputed every forward)
|
|
8
|
+
GBRA: W' = m * (W + s*B@A) / n (n is a buffer, refreshed every K optimizer steps)
|
|
9
|
+
|
|
10
|
+
Norms are taken per output row (W has shape [out, in]).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
import torch.nn as nn
|
|
15
|
+
import torch.nn.functional as F
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _is_4bit(layer: nn.Module) -> bool:
|
|
19
|
+
return hasattr(getattr(layer, "weight", None), "quant_state")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def base_weight(layer: nn.Linear) -> torch.Tensor:
|
|
23
|
+
"""Return the base weight as a dense float32 tensor (dequantizes 4-bit layers)."""
|
|
24
|
+
if _is_4bit(layer):
|
|
25
|
+
import bitsandbytes.functional as bnbF # optional dependency
|
|
26
|
+
return bnbF.dequantize_4bit(layer.weight.data, layer.weight.quant_state).float()
|
|
27
|
+
return layer.weight.detach().float()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class GBRALinear(nn.Module):
|
|
31
|
+
def __init__(self, taban: nn.Linear, r: int = 16, alpha: int = 16):
|
|
32
|
+
super().__init__()
|
|
33
|
+
if r <= 0:
|
|
34
|
+
raise ValueError("r must be a positive integer")
|
|
35
|
+
self.taban = taban # original layer (frozen)
|
|
36
|
+
for p in self.taban.parameters():
|
|
37
|
+
p.requires_grad = False
|
|
38
|
+
|
|
39
|
+
cikis, giris = taban.out_features, taban.in_features
|
|
40
|
+
self.r, self.alpha = r, alpha
|
|
41
|
+
self.s = alpha / r # LoRA scale
|
|
42
|
+
device = taban.weight.device
|
|
43
|
+
|
|
44
|
+
# LoRA matrices. B starts at zero, so at init GBRA == the original layer.
|
|
45
|
+
self.A = nn.Parameter(torch.empty(r, giris, device=device, dtype=torch.float32))
|
|
46
|
+
self.B = nn.Parameter(torch.zeros(cikis, r, device=device, dtype=torch.float32))
|
|
47
|
+
nn.init.kaiming_uniform_(self.A, a=5 ** 0.5)
|
|
48
|
+
|
|
49
|
+
# Magnitude vector m (learned), initialised to the row norm of W.
|
|
50
|
+
with torch.no_grad():
|
|
51
|
+
norm0 = base_weight(taban).norm(dim=1)
|
|
52
|
+
self.m = nn.Parameter(norm0.clone())
|
|
53
|
+
|
|
54
|
+
# Buffered norm n (NOT learned). Updated only by normu_yenile().
|
|
55
|
+
self.register_buffer("n", norm0.clone())
|
|
56
|
+
|
|
57
|
+
@torch.no_grad()
|
|
58
|
+
def normu_yenile(self):
|
|
59
|
+
"""Lazy norm refresh: compute ||W + s*B@A|| for real and store it in n."""
|
|
60
|
+
W = base_weight(self.taban)
|
|
61
|
+
delta = self.s * (self.B.float() @ self.A.float())
|
|
62
|
+
self.n.copy_((W + delta).norm(dim=1))
|
|
63
|
+
|
|
64
|
+
refresh_norm = normu_yenile # English alias
|
|
65
|
+
|
|
66
|
+
def _ana(self, x):
|
|
67
|
+
"""x @ W.T without bias (works for normal and 4-bit layers)."""
|
|
68
|
+
if _is_4bit(self.taban):
|
|
69
|
+
y = self.taban(x)
|
|
70
|
+
if self.taban.bias is not None:
|
|
71
|
+
y = y - self.taban.bias
|
|
72
|
+
return y
|
|
73
|
+
return F.linear(x, self.taban.weight, bias=None)
|
|
74
|
+
|
|
75
|
+
def forward(self, x):
|
|
76
|
+
# forward: Berke T. Adanc
|
|
77
|
+
# 1. Ana ağırlık: x @ W.T (bias eklemeden)
|
|
78
|
+
ana = self._ana(x)
|
|
79
|
+
|
|
80
|
+
# 2. LoRA: önce A, sonra B
|
|
81
|
+
lora_a = F.linear(x, self.A.to(x.dtype), bias=None)
|
|
82
|
+
lora_b = F.linear(lora_a, self.B.to(x.dtype), bias=None)
|
|
83
|
+
|
|
84
|
+
# 3. Ölçek: m / n, ardından x'in dtype'ına dönüştür
|
|
85
|
+
olcek = (self.m / self.n).to(x.dtype)
|
|
86
|
+
|
|
87
|
+
# 4. Ana ağırlık + LoRA toplamını ölçekle
|
|
88
|
+
y = olcek * (ana + self.s * lora_b)
|
|
89
|
+
|
|
90
|
+
# 5. Bias'ı en son ekle (None olabilir)
|
|
91
|
+
if self.taban.bias is not None:
|
|
92
|
+
y = y + self.taban.bias
|
|
93
|
+
|
|
94
|
+
return y
|
|
95
|
+
|
|
96
|
+
def extra_repr(self):
|
|
97
|
+
return f"in={self.taban.in_features}, out={self.taban.out_features}, r={self.r}, alpha={self.alpha}"
|
gbra/model.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Model-level helpers: attach GBRA, refresh norms, merge, save and load."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
import torch.nn as nn
|
|
8
|
+
|
|
9
|
+
from .layer import GBRALinear, base_weight
|
|
10
|
+
|
|
11
|
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
|
12
|
+
CONFIG_NAME = "gbra_config.json"
|
|
13
|
+
WEIGHTS_NAME = "gbra_adapter.safetensors"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def apply_gbra(model: nn.Module, targets=DEFAULT_TARGETS, r: int = 16, alpha: int = 16,
|
|
17
|
+
verbose: bool = True) -> nn.Module:
|
|
18
|
+
"""Freeze the model and wrap every target nn.Linear (incl. 4-bit) with GBRALinear."""
|
|
19
|
+
for p in model.parameters():
|
|
20
|
+
p.requires_grad = False
|
|
21
|
+
count = 0
|
|
22
|
+
for module in list(model.modules()):
|
|
23
|
+
for name, child in list(module.named_children()):
|
|
24
|
+
if name in targets and isinstance(child, nn.Linear) and not isinstance(child, GBRALinear):
|
|
25
|
+
setattr(module, name, GBRALinear(child, r=r, alpha=alpha))
|
|
26
|
+
count += 1
|
|
27
|
+
if count == 0:
|
|
28
|
+
raise ValueError(f"No target layers found. Check the layer names of your model. targets={targets}")
|
|
29
|
+
model._gbra_config = {"r": r, "alpha": alpha, "targets": list(targets)}
|
|
30
|
+
if verbose:
|
|
31
|
+
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
|
|
32
|
+
total = sum(p.numel() for p in model.parameters())
|
|
33
|
+
print(f"GBRA: {count} layers | trainable {trainable:,} / {total:,} ({100 * trainable / total:.2f}%)")
|
|
34
|
+
return model
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@torch.no_grad()
|
|
38
|
+
def refresh_norms(model: nn.Module) -> int:
|
|
39
|
+
"""Refresh the buffered norm of every GBRA layer. Returns the number of layers."""
|
|
40
|
+
n = 0
|
|
41
|
+
for m in model.modules():
|
|
42
|
+
if isinstance(m, GBRALinear):
|
|
43
|
+
m.normu_yenile()
|
|
44
|
+
n += 1
|
|
45
|
+
return n
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def gbra_state_dict(model: nn.Module) -> dict:
|
|
49
|
+
"""Only the GBRA tensors (A, B, m, n) - small enough to share as an adapter."""
|
|
50
|
+
out = {}
|
|
51
|
+
for name, m in model.named_modules():
|
|
52
|
+
if isinstance(m, GBRALinear):
|
|
53
|
+
for key in ("A", "B", "m", "n"):
|
|
54
|
+
out[f"{name}.{key}"] = getattr(m, key).detach().cpu().contiguous()
|
|
55
|
+
return out
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def save_gbra(model: nn.Module, path: str):
|
|
59
|
+
os.makedirs(path, exist_ok=True)
|
|
60
|
+
cfg = getattr(model, "_gbra_config", None)
|
|
61
|
+
if cfg is None:
|
|
62
|
+
raise ValueError("This model has no GBRA layers (call apply_gbra first).")
|
|
63
|
+
with open(os.path.join(path, CONFIG_NAME), "w", encoding="utf-8") as f:
|
|
64
|
+
json.dump({"format": "gbra", "version": 1, **cfg}, f, indent=2)
|
|
65
|
+
from safetensors.torch import save_file
|
|
66
|
+
save_file(gbra_state_dict(model), os.path.join(path, WEIGHTS_NAME))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def load_gbra(model: nn.Module, path: str) -> nn.Module:
|
|
70
|
+
"""Attach GBRA to a fresh base model and load adapter weights saved with save_gbra."""
|
|
71
|
+
with open(os.path.join(path, CONFIG_NAME), encoding="utf-8") as f:
|
|
72
|
+
cfg = json.load(f)
|
|
73
|
+
if not any(isinstance(m, GBRALinear) for m in model.modules()):
|
|
74
|
+
apply_gbra(model, targets=tuple(cfg["targets"]), r=cfg["r"], alpha=cfg["alpha"], verbose=False)
|
|
75
|
+
from safetensors.torch import load_file
|
|
76
|
+
tensors = load_file(os.path.join(path, WEIGHTS_NAME))
|
|
77
|
+
modules = dict(model.named_modules())
|
|
78
|
+
used = set()
|
|
79
|
+
for full, t in tensors.items():
|
|
80
|
+
name, key = full.rsplit(".", 1)
|
|
81
|
+
layer = modules.get(name)
|
|
82
|
+
if not isinstance(layer, GBRALinear):
|
|
83
|
+
raise KeyError(f"Adapter tensor {full} has no matching GBRA layer in this model.")
|
|
84
|
+
getattr(layer, key).data.copy_(t.to(getattr(layer, key).device))
|
|
85
|
+
used.add(name)
|
|
86
|
+
missing = [n for n, m in modules.items() if isinstance(m, GBRALinear) and n not in used]
|
|
87
|
+
if missing:
|
|
88
|
+
raise KeyError(f"No adapter weights for {len(missing)} GBRA layers, e.g. {missing[0]}")
|
|
89
|
+
return model
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@torch.no_grad()
|
|
93
|
+
def merge_gbra(model: nn.Module, dtype=None) -> nn.Module:
|
|
94
|
+
"""Fold every GBRA layer into a plain nn.Linear: W' = (m/n) * (W + s*B@A).
|
|
95
|
+
|
|
96
|
+
The merged model gives the same outputs as the GBRA model (using the current
|
|
97
|
+
buffered norm). 4-bit layers are dequantized, so the result is a dense model
|
|
98
|
+
you can export (e.g. to GGUF).
|
|
99
|
+
"""
|
|
100
|
+
for module in list(model.modules()):
|
|
101
|
+
for name, child in list(module.named_children()):
|
|
102
|
+
if isinstance(child, GBRALinear):
|
|
103
|
+
W = base_weight(child.taban)
|
|
104
|
+
Wm = (child.m / child.n)[:, None] * (W + child.s * child.B.float() @ child.A.float())
|
|
105
|
+
bias = child.taban.bias
|
|
106
|
+
out_dtype = dtype or (bias.dtype if bias is not None else torch.float32)
|
|
107
|
+
lin = nn.Linear(Wm.shape[1], Wm.shape[0], bias=bias is not None,
|
|
108
|
+
device=Wm.device, dtype=out_dtype)
|
|
109
|
+
lin.weight.copy_(Wm.to(out_dtype))
|
|
110
|
+
if bias is not None:
|
|
111
|
+
lin.bias.copy_(bias.detach().to(out_dtype))
|
|
112
|
+
setattr(module, name, lin)
|
|
113
|
+
if hasattr(model, "_gbra_config"):
|
|
114
|
+
del model._gbra_config
|
|
115
|
+
return model
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# Turkish aliases
|
|
119
|
+
gbra_ekle = apply_gbra
|
|
120
|
+
tum_normlari_yenile = refresh_norms
|
|
121
|
+
gbra_kaydet = save_gbra
|
|
122
|
+
gbra_yukle = load_gbra
|
|
123
|
+
gbra_birlestir = merge_gbra
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gbra
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: GBRA (Gember-Berke Rank Adaptation): DoRA-style fine-tuning with a buffered, lazily refreshed weight norm.
|
|
5
|
+
Author: Berke T. Adanc
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: lora,dora,peft,fine-tuning,llm
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: torch>=2.0
|
|
16
|
+
Requires-Dist: safetensors>=0.4
|
|
17
|
+
Provides-Extra: hf
|
|
18
|
+
Requires-Dist: transformers>=4.40; extra == "hf"
|
|
19
|
+
Provides-Extra: 4bit
|
|
20
|
+
Requires-Dist: bitsandbytes>=0.43; extra == "4bit"
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Requires-Dist: pytest; extra == "test"
|
|
23
|
+
Requires-Dist: transformers>=4.40; extra == "test"
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# GBRA: Gember-Berke Rank Adaptation
|
|
27
|
+
|
|
28
|
+
*Pronounced "Gember RA". A GEMBER && Apex Logiç Studio project by Berke T. Adanç.*
|
|
29
|
+
|
|
30
|
+
GBRA is a DoRA-style parameter-efficient fine-tuning method. It keeps DoRA's split of each weight into a learned **magnitude** and a low-rank-updated **direction**, but **buffers the weight norm** instead of recomputing it in every forward pass.
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
DoRA: W' = m · (W + s·BA) / ‖W + s·BA‖ ← norm recomputed every forward (and again during gradient-checkpoint recompute)
|
|
34
|
+
GBRA: W' = m · (W + s·BA) / n ← n is a stored buffer, refreshed every K optimizer steps
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Why it helps:** With gradient accumulation, the weights only change once per optimizer step. Recomputing the norm for every micro-batch repeats the same work many times. GBRA computes it once and reuses it.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install gbra # core
|
|
43
|
+
pip install "gbra[hf]" # + Hugging Face Trainer callback
|
|
44
|
+
pip install "gbra[4bit]" # + 4-bit (bitsandbytes) base models
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from transformers import AutoModelForCausalLM, Trainer
|
|
51
|
+
from gbra import apply_gbra, GBRACallback, save_gbra, merge_gbra
|
|
52
|
+
|
|
53
|
+
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
|
|
54
|
+
model = apply_gbra(model, r=16, alpha=16) # wraps q/k/v/o/gate/up/down projections
|
|
55
|
+
|
|
56
|
+
trainer = Trainer(model=model, ..., callbacks=[GBRACallback(refresh_every=10)])
|
|
57
|
+
trainer.train()
|
|
58
|
+
|
|
59
|
+
save_gbra(model, "my_adapter") # small adapter (A, B, m, n)
|
|
60
|
+
merge_gbra(model) # fold into plain nn.Linear for export
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Turkish aliases are also available: `gbra_ekle`, `tum_normlari_yenile`, `gbra_kaydet`, `gbra_yukle`, `gbra_birlestir`.
|
|
64
|
+
|
|
65
|
+
## Preliminary results
|
|
66
|
+
|
|
67
|
+
Qwen2.5-0.5B-Instruct, 3,000 Turkish chat/tool-use examples, 375 optimizer steps (batch 1 × 8 accumulation), r=16, single Kaggle T4, fp16 autocast. Same data, seed and hyper-parameters for every method. DoRA and LoRA via 🤗 PEFT.
|
|
68
|
+
|
|
69
|
+
| Method | s / step | vs LoRA | Peak memory | Val. loss |
|
|
70
|
+
|---|---|---|---|---|
|
|
71
|
+
| LoRA | **5.77** | 1.00× | 8.11 GB | 1.6376 |
|
|
72
|
+
| DoRA (PEFT) | 9.10 | 1.58× | 8.11 GB | 1.6285 |
|
|
73
|
+
| **GBRA K=10** | 6.47 | 1.12× | 8.11 GB | **1.6249** |
|
|
74
|
+
| GBRA K=100 | 6.47 | 1.12× | 8.11 GB | 1.6253 |
|
|
75
|
+
| GBRA K=never | 6.46 | 1.12× | 8.11 GB | 1.6263 |
|
|
76
|
+
|
|
77
|
+
**In this run, GBRA was ~29% faster than DoRA per step, with equal or slightly lower validation loss.** A shorter 125-step run gave the same speed ratio.
|
|
78
|
+
|
|
79
|
+
### Limitations (please read)
|
|
80
|
+
|
|
81
|
+
- **Single seed, single small model, single dataset.** The loss differences between GBRA variants and DoRA are small (≈0.001–0.004) and may be noise; repeated runs with more seeds are in progress.
|
|
82
|
+
- Much of the speed-up comes from **not recomputing the norm per micro-batch**. With no gradient accumulation and no gradient checkpointing, the gap to DoRA should be smaller.
|
|
83
|
+
- The comparison is against PEFT's DoRA implementation at the time of testing, not against every possible DoRA implementation.
|
|
84
|
+
- 4-bit (bitsandbytes) support is implemented but **has not yet been benchmarked**.
|
|
85
|
+
- Unlike PEFT LoRA, GBRA does not currently use fused kernels such as Unsloth's.
|
|
86
|
+
|
|
87
|
+
## Credits
|
|
88
|
+
|
|
89
|
+
GBRA builds directly on **DoRA** (Liu et al., 2024, *"DoRA: Weight-Decomposed Low-Rank Adaptation"*) and **LoRA** (Hu et al., 2021). Related ideas on learned scaling vectors appear in (IA)³ and VeRA. The contribution here is the buffered / lazily refreshed norm and its evaluation.
|
|
90
|
+
|
|
91
|
+
The core `forward` of `GBRALinear` was written by Berke T. Adanç. Parts of the surrounding library and tests were developed with the help of an AI assistant.
|
|
92
|
+
|
|
93
|
+
## Changelog
|
|
94
|
+
|
|
95
|
+
- **0.1.1**: Python 3.9 compatibility fix in `GBRACallback`; new `refresh_on_end` option (refreshes the norm once more when training ends); README install instructions for PyPI.
|
|
96
|
+
- **0.1.0**: First release.
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Türkçe özet
|
|
105
|
+
|
|
106
|
+
GBRA, DoRA'nın "büyüklük + yön" ayrımını korur, ama ağırlık normunu her adımda yeniden hesaplamak yerine **saklar** ve sadece **K optimizer adımında bir** günceller. Gradient birikimi kullanılan eğitimde ağırlıklar mini-batch'ler arasında değişmediği için DoRA aynı normu tekrar tekrar hesaplar. GBRA bu tekrarı ortadan kaldırır.
|
|
107
|
+
|
|
108
|
+
İlk deneyde (Qwen2.5-0.5B, 375 adım, tek seed) GBRA, DoRA'dan **~%29 daha hızlı** ve doğrulama loss'u aynı ya da biraz daha iyiydi. Sonuçlar tek seed'e dayandığı için **ön sonuç** olarak değerlendirilmelidir.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
gbra/__init__.py,sha256=nzgnXtcnsiZb7N_sUsbdi82vv9OU9uFbKcDAkaGC3GM,926
|
|
2
|
+
gbra/callback.py,sha256=K-pM2KFZidPsx9x-AB-iiRjtHYz5jT_f0U2S3HyA6zY,1238
|
|
3
|
+
gbra/layer.py,sha256=L3rG-DR6d43PC9oE4cSh6wqktjOFVuSO0RrjpJNv7gM,3640
|
|
4
|
+
gbra/model.py,sha256=k3UR3QyrLEZsBqKmWsZdY1hyOrPpEO1XBMPF2npfclI,5126
|
|
5
|
+
gbra-0.1.1.dist-info/licenses/LICENSE,sha256=AJaZTTeRPlE_b0EgsutlchTPFn-JXY8Sdeg06uVmYXM,1101
|
|
6
|
+
gbra-0.1.1.dist-info/METADATA,sha256=4MjnjiIkxrLvDTjM8GyELbCFFGdyef01XJHeoit-dgI,5456
|
|
7
|
+
gbra-0.1.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
8
|
+
gbra-0.1.1.dist-info/top_level.txt,sha256=YS-1qm9iN_uHTzfqaocQiQ21mXfMWPLD-ZYDuCbSFQo,5
|
|
9
|
+
gbra-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Berke T. Adanc (GEMBER && Apex Logic Studio)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gbra
|