forked from MakazhanAlpamys/Soup
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_pissa_init.py
More file actions
159 lines (124 loc) · 6.05 KB
/
Copy pathtest_pissa_init.py
File metadata and controls
159 lines (124 loc) · 6.05 KB
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
"""Tests for v0.39.0 Part A — PiSSA init + init_strategy field on LoraConfig."""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from soup_cli.config.schema import LoraConfig
from soup_cli.utils.peft_builder import build_peft_config
class TestInitStrategyField:
def test_default_is_random(self):
cfg = LoraConfig()
assert cfg.init_strategy == "random"
def test_accepts_pissa(self):
cfg = LoraConfig(init_strategy="pissa")
assert cfg.init_strategy == "pissa"
def test_accepts_olora(self):
cfg = LoraConfig(init_strategy="olora")
assert cfg.init_strategy == "olora"
def test_rejects_unknown(self):
# loftq added in v0.41.0 — use a truly unknown sentinel.
with pytest.raises(ValidationError):
LoraConfig(init_strategy="bogus_strategy")
def test_rejects_non_string(self):
with pytest.raises(ValidationError):
LoraConfig(init_strategy=42)
class TestBackcompatNoMutation:
def test_backcompat_does_not_mutate_input_dict(self):
"""v0.39.0 security fix — _backcompat_align_olora must copy."""
original = {"use_olora": True}
LoraConfig(**original)
assert "init_strategy" not in original, (
"Validator mutated caller's dict in-place"
)
class TestInitStrategyOloraBackcompat:
def test_use_olora_true_alone_still_works(self):
cfg = LoraConfig(use_olora=True)
assert cfg.use_olora is True
assert cfg.init_strategy == "olora" # auto-aligned for back-compat
def test_init_strategy_olora_alone_works(self):
cfg = LoraConfig(init_strategy="olora")
assert cfg.init_strategy == "olora"
# use_olora may be True or False; effect is via init_strategy
def test_both_set_consistent_ok(self):
cfg = LoraConfig(use_olora=True, init_strategy="olora")
assert cfg.init_strategy == "olora"
def test_use_olora_true_with_pissa_rejected(self):
with pytest.raises(ValidationError, match="init_strategy"):
LoraConfig(use_olora=True, init_strategy="pissa")
def test_use_olora_true_with_random_rejected(self):
# explicit conflict — user said both random and olora; loud-fail
with pytest.raises(ValidationError, match="init_strategy"):
LoraConfig(use_olora=True, init_strategy="random")
class TestInitStrategyMutualExclusion:
def test_pissa_with_dora_rejected(self):
# PiSSA + DoRA isn't supported (PEFT init_lora_weights conflicts with use_dora init)
with pytest.raises(ValidationError, match="init_strategy"):
LoraConfig(use_dora=True, init_strategy="pissa")
def test_pissa_with_vera_rejected(self):
# VeRA doesn't have init_lora_weights — PiSSA meaningless
with pytest.raises(ValidationError, match="init_strategy"):
LoraConfig(use_vera=True, init_strategy="pissa")
def test_pissa_with_rslora_ok(self):
# rsLoRA only changes scaling factor — orthogonal to init
cfg = LoraConfig(use_rslora=True, init_strategy="pissa")
assert cfg.init_strategy == "pissa"
assert cfg.use_rslora is True
class TestPeftBuilderInitStrategy:
def test_random_does_not_set_init_lora_weights(self):
cfg = LoraConfig(init_strategy="random")
spec = build_peft_config(cfg, target_modules="auto", task_type="CAUSAL_LM")
assert spec["peft_cls"] == "LoraConfig"
assert "init_lora_weights" not in spec["init_kwargs"]
def test_pissa_sets_init_lora_weights(self):
cfg = LoraConfig(init_strategy="pissa")
spec = build_peft_config(cfg, target_modules="auto", task_type="CAUSAL_LM")
assert spec["init_kwargs"]["init_lora_weights"] == "pissa"
def test_olora_via_init_strategy(self):
cfg = LoraConfig(init_strategy="olora")
spec = build_peft_config(cfg, target_modules="auto", task_type="CAUSAL_LM")
assert spec["init_kwargs"]["init_lora_weights"] == "olora"
def test_olora_via_use_olora_legacy(self):
cfg = LoraConfig(use_olora=True)
spec = build_peft_config(cfg, target_modules="auto", task_type="CAUSAL_LM")
assert spec["init_kwargs"]["init_lora_weights"] == "olora"
def test_vera_ignores_init_strategy_random(self):
cfg = LoraConfig(use_vera=True, init_strategy="random")
spec = build_peft_config(cfg, target_modules="auto", task_type="CAUSAL_LM")
assert spec["peft_cls"] == "VeraConfig"
assert "init_lora_weights" not in spec["init_kwargs"]
class TestInstantiatePeftConfig:
def test_instantiate_lora_config(self):
from soup_cli.utils.peft_builder import instantiate_peft_config
cfg = LoraConfig(init_strategy="pissa")
spec = build_peft_config(cfg, target_modules=["q_proj", "v_proj"], task_type="CAUSAL_LM")
result = instantiate_peft_config(spec)
# Verify it really is a peft.LoraConfig with the right kwargs
import peft
assert isinstance(result, peft.LoraConfig)
assert result.r == 64
assert result.init_lora_weights == "pissa"
def test_instantiate_with_rank_pattern(self):
from soup_cli.utils.peft_builder import instantiate_peft_config
cfg = LoraConfig(rank_pattern={"q_proj": 8})
spec = build_peft_config(cfg, target_modules=["q_proj"], task_type="CAUSAL_LM")
result = instantiate_peft_config(spec)
assert result.rank_pattern == {"q_proj": 8}
class TestInitStrategyYamlRoundtripV0401Regression:
"""v0.40.1 Part B — QA found bogus init_strategy via YAML occasionally
bypassed validation; assert it always errors at full-config load."""
def test_bogus_init_strategy_via_full_yaml_rejected(self):
from soup_cli.config.loader import load_config_from_string
yaml_text = """
base: HuggingFaceTB/SmolLM2-135M-Instruct
task: sft
data:
train: data.jsonl
training:
epochs: 1
lr: 0.0002
output: ./out
lora:
r: 8
init_strategy: bogus
"""
with pytest.raises((ValidationError, ValueError), match="init_strategy"):
load_config_from_string(yaml_text)