[Bugfix][LoRA] Reject PEFT adapter features vLLM does not implement (#60225)

Signed-off-by: suraj <surajsharan85@gmail.com>
Co-authored-by: linitra24 <renshuang.zhou@daocloud.io>
This commit is contained in:
Suraj Sharan
2026-10-09 16:39:58 +04:00
committed by GitHub
co-authored by linitra24
parent c17fadcfdc
commit 1ee69dde9c
5 changed files with 83 additions and 1 deletions
@@ -23,6 +23,7 @@ BADREQUEST_CASES = [
"is greater than max_lora_rank",
),
("test_dora", {"use_dora": True}, "does not yet support DoRA"),
("test_pissa", {"init_lora_weights": "pissa"}, "init_lora_weights='pissa'"),
(
"test_modules_to_save",
{"modules_to_save": ["lm_head"]},
+39
View File
@@ -24,6 +24,25 @@ ERROR_CASES = [
),
("test_rank_zero", {"r": 0}, "must be a positive integer"),
("test_rank_negative", {"r": -8}, "must be a positive integer"),
("test_lora_bias", {"lora_bias": True}, "does not support LoRA bias"),
("test_pissa", {"init_lora_weights": "pissa"}, "init_lora_weights='pissa'"),
(
"test_pissa_niter",
{"init_lora_weights": "pissa_niter_4"},
"init_lora_weights='pissa_niter_4'",
),
("test_olora", {"init_lora_weights": "olora"}, "init_lora_weights='olora'"),
("test_corda", {"init_lora_weights": "corda"}, "init_lora_weights='corda'"),
("test_loftq", {"init_lora_weights": "loftq"}, "init_lora_weights='loftq'"),
(
"test_unknown_init",
{"init_lora_weights": "future_init"},
"init_lora_weights='future_init'",
),
("test_alora", {"alora_invocation_tokens": [1, 2]}, "Activated LoRA"),
("test_layer_replication", {"layer_replication": [[0, 2]]}, "layer_replication"),
("test_bdlora", {"use_bdlora": {"nblocks": 2}}, "BD-LoRA"),
("test_qalora", {"use_qalora": True}, "QALoRA"),
]
@@ -114,3 +133,23 @@ def test_peft_helper_invalid_rank_direct(bad_rank: int):
"""
with pytest.raises(ValueError, match="must be a positive integer"):
PEFTHelper(r=bad_rank, lora_alpha=16, target_modules=["q_proj"])
@pytest.mark.parametrize(
"init_lora_weights",
[True, False, "gaussian", "eva", "orthogonal", "mica", "lora_ga"],
)
def test_peft_helper_init_lora_weights_supported(init_lora_weights):
"""A saved adapter with these inits loads in PEFT as a plain LoRA."""
lora_config = LoRAConfig(max_lora_rank=16, max_cpu_loras=3, max_loras=2)
PEFTHelper.from_dict(
{
"r": 8,
"lora_alpha": 16,
"target_modules": ["q_proj"],
"init_lora_weights": init_lora_weights,
"alora_invocation_tokens": None,
"layer_replication": None,
"use_bdlora": None,
}
).validate_legal(lora_config)
+2
View File
@@ -106,6 +106,8 @@ def test_parse_fine_tuned_lora_name_invalid():
fixture = {
"base_model.weight",
"base_model.model.weight",
"base_model.model.model.layers.9.mlp.down_proj.lora_A.bias",
"base_model.model.model.layers.9.mlp.down_proj.lora_B.bias",
}
for name in fixture:
with pytest.raises(ValueError, match="unsupported LoRA weight"):
+40
View File
@@ -33,6 +33,14 @@ class PEFTHelper:
use_rslora: bool = field(default=False)
# True to use Weight-Decomposed Low-Rank Adaptation (DoRA, see: https://arxiv.org/abs/2402.09353)
use_dora: bool = field(default=False)
# PEFT features that change the adapter's semantics and that vLLM does not
# implement. They are read only to reject such adapters.
lora_bias: bool = field(default=False)
init_lora_weights: bool | str = field(default=True)
alora_invocation_tokens: list[int] | None = field(default=None)
layer_replication: list[list[int]] | None = field(default=None)
use_bdlora: dict | None = field(default=None)
use_qalora: bool = field(default=False)
# Extra vllm field, start with 'vllm_' to avoid conflict
vllm_lora_scaling_factor: float = field(default=1.0)
vllm_max_position_embeddings: int | None = field(default=False)
@@ -52,8 +60,40 @@ class PEFTHelper:
)
if self.use_dora:
error_msg.append("vLLM does not yet support DoRA.")
if self.lora_bias:
error_msg.append("vLLM does not support LoRA bias (lora_bias).")
if not self._init_is_supported():
error_msg.append(
f"vLLM does not support init_lora_weights={self.init_lora_weights!r}."
" Inits such as PiSSA, OLoRA, CorDA and LoftQ modify the base model"
" weights when PEFT loads the adapter. Convert it to a regular LoRA"
" adapter with PEFT's path_initial_model_for_weight_conversion, or"
" set init_lora_weights to true if the served base model already"
" contains the modified weights."
)
if self.alora_invocation_tokens:
error_msg.append("vLLM does not support Activated LoRA (aLoRA).")
if self.layer_replication:
error_msg.append("vLLM does not support layer_replication.")
if self.use_bdlora:
error_msg.append("vLLM does not support block-diagonal LoRA (BD-LoRA).")
if self.use_qalora:
error_msg.append("vLLM does not support QALoRA.")
return error_msg
def _init_is_supported(self) -> bool:
# These inits only set the initial adapter weights, so a saved adapter
# loads in PEFT as a plain LoRA. lora_ga modifies the base weights only
# during training, when its gradients are attached.
init = self.init_lora_weights
return not isinstance(init, str) or init.lower() in (
"gaussian",
"eva",
"orthogonal",
"mica",
"lora_ga",
)
def __post_init__(self):
if self.r <= 0:
raise ValueError(f"LoRA rank `r` must be a positive integer, got {self.r}.")
+1 -1
View File
@@ -205,7 +205,7 @@ def parse_fine_tuned_lora_name(
parts = name.split(".")
if (parts[-1] == "weight" or parts[-1] == "bias") and len(parts) >= 2:
if parts[-2] in ["lora_A", "lora_B"]:
if parts[-2] in ["lora_A", "lora_B"] and parts[-1] == "weight":
new_name = ".".join(parts[start_index:-2])
return new_name, parts[-2] == "lora_A"
# For modules_to_save in classification.