Skip to content

Commit ba75bb1

Browse files
BenjaminBossanBenjaminBossan
andauthored
FIX: More VeRA tests, fix tests, more checks (#1900)
* FIX More VeRA tests, fix tests, more checks - Fixes incorrect config for VeRA in a test - Add VeRA to multi-adapter tests - Add more checks on the VeRA A/B shapes The latter becomes necessary when we add more than one VeRA adapter. The shapes for VeRA A and B are only determined once, when the first VeRA adapter is created. After that, they are fixed. However, users may add a second VeRA adapter. As long as that adapter targets the same layers and has the same rank, we're good. But if it targets other, bigger layers, or if it has increased rank, the shapes of VeRA A and/or VeRA B will be too small, resulting in an error during the forward pass. To prevent this, we already check the shapes during initialization of the new adapter and raise an error right away. * Revier feedback: wording, better error message * Reviewer feedback: Clarify tests --------- Co-authored-by: BenjaminBossan <b.bossan@gmail.com>
1 parent 6472061 commit ba75bb1

File tree

3 files changed

+86
-2
lines changed

3 files changed

+86
-2
lines changed

src/peft/tuners/vera/layer.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,28 @@ def update_layer(
100100
# we can take any of the existing adapter's parameters, as they should all be identical
101101
vera_A_param = list(self.vera_A.values())[0]
102102
vera_B_param = list(self.vera_B.values())[0]
103+
104+
error_tmpl = (
105+
"{} has a size of {} but {} or greater is required; this probably happened because an additional VeRA "
106+
"adapter was added after the first one with incompatible shapes."
107+
)
108+
# check input size
109+
if vera_A_param.shape[1] < self.in_features:
110+
raise ValueError(error_tmpl.format("vera_A", vera_A_param.shape[1], self.in_features))
111+
# check output size
112+
if vera_B_param.shape[0] < self.out_features:
113+
raise ValueError(error_tmpl.format("vera_B", vera_B_param.shape[0], self.out_features))
114+
# check r
115+
error_tmpl = (
116+
"{} has a size of {} but {} or greater is required; this probably happened because an additional VeRA "
117+
"adapter with a lower rank was added after the first one; loading the adapters "
118+
"in reverse order may solve this."
119+
)
120+
if vera_A_param.shape[0] < self.r[adapter_name]:
121+
raise ValueError(error_tmpl.format("vera_A", vera_A_param.shape[0], self.r[adapter_name]))
122+
if vera_B_param.shape[1] < self.r[adapter_name]:
123+
raise ValueError(error_tmpl.format("vera_B", vera_B_param.shape[1], self.r[adapter_name]))
124+
103125
self.vera_A[adapter_name] = vera_A_param
104126
self.vera_B[adapter_name] = vera_B_param
105127

tests/test_custom_models.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,13 @@
391391
),
392392
]
393393

394+
# For this test matrix, each tuple consists of:
395+
# - test name
396+
# - tuner method
397+
# - config_cls
398+
# - 1st config kwargs
399+
# - 2nd config kwargs
400+
# The model used for this test is `MLP`, which uses linear layers `lin0` and `lin1`
394401
MULTIPLE_ACTIVE_ADAPTERS_TEST_CASES = [
395402
(
396403
"LoRA Same",
@@ -464,6 +471,16 @@
464471
{"n_frequency": 10, "target_modules": ["lin0"]},
465472
{"n_frequency": 10, "target_modules": ["lin1"]},
466473
),
474+
# Note: Currently, we cannot target lin0 and lin1 with different adapters when using VeRA. The reason is that the
475+
# first adapter being created will result in a vera_A or vera_B shape that is too small for the next adapter
476+
# (remember that VeRA shares these parameters across all layers), which results in an error.
477+
(
478+
"VeRA Same",
479+
"vera",
480+
VeraConfig,
481+
{"target_modules": ["lin0"], "init_weights": False},
482+
{"target_modules": ["lin0"], "init_weights": False},
483+
),
467484
(
468485
"HRA Same",
469486
"hra",
@@ -479,6 +496,7 @@
479496
{"target_modules": ["lin1"], "init_weights": False},
480497
),
481498
]
499+
482500
PREFIXES = {
483501
IA3Config: "ia3_",
484502
LoraConfig: "lora_",

tests/test_initialization.py

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,21 +1098,65 @@ def test_use_prompt_tuning_init_text_raises(self):
10981098
with pytest.raises(ValueError, match="When prompt_tuning_init='TEXT', prompt_tuning_init_text can't be None"):
10991099
PromptTuningConfig(prompt_tuning_init="TEXT", tokenizer_name_or_path="t5-base")
11001100

1101+
1102+
class TestVeraInitialization:
1103+
torch_device = infer_device()
1104+
1105+
def get_model(self):
1106+
class MLP(nn.Module):
1107+
def __init__(self, bias=True):
1108+
super().__init__()
1109+
self.lin0 = nn.Linear(10, 20, bias=bias)
1110+
self.lin1 = nn.Linear(20, 2, bias=bias)
1111+
1112+
def forward(self, X):
1113+
X = self.lin0(X)
1114+
X = self.lin1(X)
1115+
return X
1116+
1117+
return MLP().to(self.torch_device)
1118+
11011119
def test_vera_mixing_save_projection_raises(self):
11021120
# it is unclear what the right thing to do would be if some adapters save the projection weights and some don't
11031121
# so we better raise an error
11041122

1105-
config0 = VeraConfig(target_modules="linear", init_weights=False, save_projection=True)
1123+
config0 = VeraConfig(target_modules=["lin0"], init_weights=False, save_projection=True)
11061124
model = self.get_model()
11071125
model = get_peft_model(model, config0)
1108-
config1 = VeraConfig(target_modules="linear", init_weights=False, save_projection=False)
1126+
config1 = VeraConfig(target_modules=["lin0"], init_weights=False, save_projection=False)
11091127
msg = re.escape(
11101128
"VeRA projection weights must be saved for all adapters or none, but got multiple different values: "
11111129
"[False, True]"
11121130
)
11131131
with pytest.raises(ValueError, match=msg):
11141132
model.add_adapter("other", config1)
11151133

1134+
def test_vera_add_second_adapter_with_incompatible_input_shape(self):
1135+
config0 = VeraConfig(target_modules=["lin0"], r=8)
1136+
config1 = VeraConfig(target_modules=["lin1"])
1137+
1138+
base_model = self.get_model()
1139+
lin0_in_feat = base_model.lin0.in_features
1140+
lin1_in_feat = base_model.lin1.in_features
1141+
model = get_peft_model(base_model, config0)
1142+
# not full message but enough to identify the error
1143+
msg = f"vera_A has a size of {lin0_in_feat} but {lin1_in_feat} or greater is required"
1144+
with pytest.raises(ValueError, match=msg):
1145+
model.add_adapter("other", config1)
1146+
1147+
def test_vera_add_second_adapter_with_higher_rank(self):
1148+
rank0 = 123
1149+
rank1 = 456
1150+
config0 = VeraConfig(target_modules=["lin0"], r=rank0)
1151+
# second adapter has higher rank
1152+
config1 = VeraConfig(target_modules=["lin0"], r=rank1)
1153+
1154+
model = get_peft_model(self.get_model(), config0)
1155+
# not full message but enough to identify the error
1156+
msg = f"vera_A has a size of {rank0} but {rank1} or greater is required"
1157+
with pytest.raises(ValueError, match=msg):
1158+
model.add_adapter("other", config1)
1159+
11161160

11171161
class TestNoInfiniteRecursionDeepspeed:
11181162
# see #1892 for details

0 commit comments

Comments
 (0)