mirror of https://github.com/razor-ai/soup.git
220 lines
7.0 KiB
Python
220 lines
7.0 KiB
Python
"""Tests for new PEFT methods — VeRA + OLoRA (Part D of v0.25.0)."""
|
|
|
|
import pytest
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# LoraConfig schema fields
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestLoraConfigFields:
|
|
def test_use_vera_default_false(self):
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
cfg = LoraConfig()
|
|
assert cfg.use_vera is False
|
|
|
|
def test_use_olora_default_false(self):
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
cfg = LoraConfig()
|
|
assert cfg.use_olora is False
|
|
|
|
def test_use_vera_enabled(self):
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
cfg = LoraConfig(use_vera=True)
|
|
assert cfg.use_vera is True
|
|
|
|
def test_use_olora_enabled(self):
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
cfg = LoraConfig(use_olora=True)
|
|
assert cfg.use_olora is True
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mutual exclusion
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestPeftMutualExclusion:
|
|
def test_vera_and_olora_rejected(self):
|
|
from pydantic import ValidationError
|
|
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
with pytest.raises(ValidationError):
|
|
LoraConfig(use_vera=True, use_olora=True)
|
|
|
|
def test_vera_and_dora_rejected(self):
|
|
from pydantic import ValidationError
|
|
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
with pytest.raises(ValidationError):
|
|
LoraConfig(use_vera=True, use_dora=True)
|
|
|
|
def test_olora_and_dora_rejected(self):
|
|
from pydantic import ValidationError
|
|
|
|
from soup_cli.config.schema import LoraConfig
|
|
|
|
with pytest.raises(ValidationError):
|
|
LoraConfig(use_olora=True, use_dora=True)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Peft builder helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestPeftBuilder:
|
|
def test_standard_lora_returns_lora_config(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
schema_cfg = SchemaLoraConfig()
|
|
result = build_peft_config(
|
|
schema_cfg,
|
|
target_modules=["q_proj", "v_proj"],
|
|
task_type="CAUSAL_LM",
|
|
)
|
|
# Expect dict with `peft_cls` key + init kwargs
|
|
assert result["peft_cls"] == "LoraConfig"
|
|
|
|
def test_olora_adds_init_weights(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
schema_cfg = SchemaLoraConfig(use_olora=True)
|
|
result = build_peft_config(
|
|
schema_cfg,
|
|
target_modules=["q_proj", "v_proj"],
|
|
task_type="CAUSAL_LM",
|
|
)
|
|
assert result["peft_cls"] == "LoraConfig"
|
|
assert result["init_kwargs"].get("init_lora_weights") == "olora"
|
|
|
|
def test_vera_returns_vera_config(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
schema_cfg = SchemaLoraConfig(use_vera=True)
|
|
result = build_peft_config(
|
|
schema_cfg,
|
|
target_modules=["q_proj", "v_proj"],
|
|
task_type="CAUSAL_LM",
|
|
)
|
|
assert result["peft_cls"] == "VeraConfig"
|
|
|
|
def test_dora_preserved(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
schema_cfg = SchemaLoraConfig(use_dora=True)
|
|
result = build_peft_config(
|
|
schema_cfg,
|
|
target_modules=["q_proj"],
|
|
task_type="CAUSAL_LM",
|
|
)
|
|
assert result["peft_cls"] == "LoraConfig"
|
|
assert result["init_kwargs"].get("use_dora") is True
|
|
|
|
def test_target_modules_propagated_lora(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
modules = ["q_proj", "k_proj", "v_proj", "o_proj"]
|
|
result = build_peft_config(
|
|
SchemaLoraConfig(),
|
|
target_modules=modules,
|
|
task_type="CAUSAL_LM",
|
|
)
|
|
assert result["init_kwargs"]["target_modules"] == modules
|
|
|
|
def test_target_modules_propagated_vera(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
modules = ["q_proj", "v_proj"]
|
|
result = build_peft_config(
|
|
SchemaLoraConfig(use_vera=True),
|
|
target_modules=modules,
|
|
task_type="CAUSAL_LM",
|
|
)
|
|
assert result["init_kwargs"]["target_modules"] == modules
|
|
|
|
def test_task_type_propagated(self):
|
|
from soup_cli.config.schema import LoraConfig as SchemaLoraConfig
|
|
from soup_cli.utils.peft_builder import build_peft_config
|
|
|
|
result = build_peft_config(
|
|
SchemaLoraConfig(),
|
|
target_modules=["q_proj"],
|
|
task_type="SEQ_CLS",
|
|
)
|
|
assert result["init_kwargs"]["task_type"] == "SEQ_CLS"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sweep integration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestPeftSweep:
|
|
def test_sweep_accepts_use_vera(self, tmp_path, monkeypatch):
|
|
from soup_cli.commands.sweep import _parse_sweep_params
|
|
|
|
params = _parse_sweep_params(["lora.use_vera=true,false"])
|
|
assert "lora.use_vera" in params
|
|
|
|
def test_sweep_accepts_use_olora(self):
|
|
from soup_cli.commands.sweep import _parse_sweep_params
|
|
|
|
params = _parse_sweep_params(["lora.use_olora=true,false"])
|
|
assert "lora.use_olora" in params
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# End-to-end config loads
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestPeftYamlConfig:
|
|
def test_yaml_with_vera(self):
|
|
from soup_cli.config.loader import load_config_from_string
|
|
|
|
yaml_str = """
|
|
base: meta-llama/Llama-3.1-8B-Instruct
|
|
task: sft
|
|
data:
|
|
train: ./data/train.jsonl
|
|
format: auto
|
|
training:
|
|
epochs: 1
|
|
lora:
|
|
use_vera: true
|
|
output: ./output
|
|
"""
|
|
cfg = load_config_from_string(yaml_str)
|
|
assert cfg.training.lora.use_vera is True
|
|
assert cfg.training.lora.use_olora is False
|
|
|
|
def test_yaml_with_olora(self):
|
|
from soup_cli.config.loader import load_config_from_string
|
|
|
|
yaml_str = """
|
|
base: meta-llama/Llama-3.1-8B-Instruct
|
|
task: sft
|
|
data:
|
|
train: ./data/train.jsonl
|
|
format: auto
|
|
training:
|
|
epochs: 1
|
|
lora:
|
|
use_olora: true
|
|
output: ./output
|
|
"""
|
|
cfg = load_config_from_string(yaml_str)
|
|
assert cfg.training.lora.use_olora is True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|