main
py 116 lines 3.74 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import json
5 import math
6 import shutil
7
8 import pytest
9
10 from vllm.config.lora import LoRAConfig
11 from vllm.lora.peft_helper import PEFTHelper
12
13 ERROR_CASES = [
14 (
15 "test_rank",
16 {"r": 1024},
17 "is greater than max_lora_rank",
18 ),
19 ("test_dora", {"use_dora": True}, "does not yet support DoRA"),
20 (
21 "test_modules_to_save",
22 {"modules_to_save": ["lm_head"]},
23 "Unsupported modules_to_save",
24 ),
25 ("test_rank_zero", {"r": 0}, "must be a positive integer"),
26 ("test_rank_negative", {"r": -8}, "must be a positive integer"),
27 ]
28
29
30 def test_peft_helper_pass(llama32_lora_files, tmp_path):
31 peft_helper = PEFTHelper.from_local_dir(
32 llama32_lora_files, max_position_embeddings=4096
33 )
34 lora_config = LoRAConfig(max_lora_rank=16, max_cpu_loras=3, max_loras=2)
35 peft_helper.validate_legal(lora_config)
36 assert peft_helper.r == 8
37 assert peft_helper.lora_alpha == 32
38 target_modules = sorted(peft_helper.target_modules)
39
40 assert target_modules == [
41 "down_proj",
42 "embed_tokens",
43 "gate_proj",
44 "k_proj",
45 "lm_head",
46 "o_proj",
47 "q_proj",
48 "up_proj",
49 "v_proj",
50 ]
51 assert peft_helper.vllm_max_position_embeddings == 4096
52
53 # test RSLoRA
54 rslora_config = dict(use_rslora=True)
55 test_dir = tmp_path / "test_rslora"
56 shutil.copytree(llama32_lora_files, test_dir)
57
58 # Load and modify configuration
59 config_path = test_dir / "adapter_config.json"
60 with open(config_path) as f:
61 adapter_config = json.load(f)
62 # Apply configuration changes
63 adapter_config.update(rslora_config)
64
65 # Save modified configuration
66 with open(config_path, "w") as f:
67 json.dump(adapter_config, f)
68
69 peft_helper = PEFTHelper.from_local_dir(test_dir, max_position_embeddings=4096)
70 peft_helper.validate_legal(lora_config)
71 scaling = peft_helper.lora_alpha / math.sqrt(peft_helper.r)
72 assert abs(peft_helper.vllm_lora_scaling_factor - scaling) < 1e-3
73
74
75 @pytest.mark.parametrize("test_name,config_change,expected_error", ERROR_CASES)
76 def test_peft_helper_error(
77 llama32_lora_files,
78 tmp_path,
79 test_name: str,
80 config_change: dict,
81 expected_error: str,
82 ):
83 test_dir = tmp_path / test_name
84 shutil.copytree(llama32_lora_files, test_dir)
85
86 # Load and modify configuration
87 config_path = test_dir / "adapter_config.json"
88 with open(config_path) as f:
89 adapter_config = json.load(f)
90 # Apply configuration changes
91 adapter_config.update(config_change)
92
93 # Save modified configuration
94 with open(config_path, "w") as f:
95 json.dump(adapter_config, f)
96 lora_config = LoRAConfig(max_lora_rank=16, max_cpu_loras=3, max_loras=2)
97 # Test loading the adapter
98 with pytest.raises(ValueError, match=expected_error):
99 PEFTHelper.from_local_dir(
100 test_dir, max_position_embeddings=4096
101 ).validate_legal(lora_config)
102
103
104 @pytest.mark.parametrize("bad_rank", [0, -1, -8])
105 def test_peft_helper_invalid_rank_direct(bad_rank: int):
106 """Regression test: constructing a PEFTHelper with a non-positive rank
107 must raise a clear ValueError instead of crashing with an unrelated
108 ZeroDivisionError (r=0) or silently succeeding with a sign-flipped
109 scaling factor that validate_legal() never catches (r<0, since its only
110 rank check is the upper bound against max_lora_rank).
111
112 Network-free: constructs PEFTHelper directly rather than going through
113 from_local_dir(), which needs an on-disk adapter_config.json.
114 """
115 with pytest.raises(ValueError, match="must be a positive integer"):
116 PEFTHelper(r=bad_rank, lora_alpha=16, target_modules=["q_proj"])