[LoRA] Avoid false target matches for unsupported module types (#52313)
Signed-off-by: linitra24 <Joy25810@foxmail.com> Co-authored-by: Jee Jee Li <pandaleefree@gmail.com>
linitra24 committed
Aug 20, 2026 at 00:48 UTC
9b5f3454f2be4c2c0f17c84a78af25a1d550d446
3 files changed
+39
-51
tests/lora/test_lora_utils.py
+1
-31
@@ -1,37 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-
5
-from vllm.lora.utils import is_in_target_modules, is_supported_lora_module
6
-
7
-
8
-class TestIsSupportedLoraModule:
9
- """Tests for is_supported_lora_module (model-definition check)."""
10
-
11
- def test_suffix_match(self):
12
- assert is_supported_lora_module(
13
- "model.layers.0.self_attn.o_proj", ["o_proj", "q_proj"]
14
- )
15
-
16
- def test_no_match(self):
17
- assert not is_supported_lora_module(
18
- "model.layers.0.self_attn.o_proj", ["q_proj", "k_proj"]
19
- )
20
-
21
- def test_exact_match(self):
22
- assert is_supported_lora_module("o_proj", ["o_proj"])
23
-
24
- def test_regex_suffix_matching(self):
25
- """Regex anchors to end — partial suffix should not match."""
26
- assert not is_supported_lora_module("model.layers.0.self_attn.o_proj", ["proj"])
27
-
28
- def test_empty_supported_modules(self):
29
- assert not is_supported_lora_module("model.layers.0.self_attn.o_proj", [])
30
-
31
- def test_multiple_supported_modules(self):
32
- supported = ["q_proj", "k_proj", "v_proj", "o_proj"]
33
- assert is_supported_lora_module("model.layers.0.self_attn.v_proj", supported)
34
- assert not is_supported_lora_module("model.layers.0.mlp.gate_proj", supported)
4
+from vllm.lora.utils import is_in_target_modules
5
6
7
class TestIsInTargetModules:
vllm/lora/model_manager.py
+18
-4
@@ -418,7 +418,16 @@ class LoRAModelManager:
418
if isinstance(module, PPMissingLayer):
419
continue
420
421
- if not self._match_target_modules(module_name):
421
+ target_modules = self.lora_config.target_modules
422
+ is_configured_target = target_modules is not None and is_in_target_modules(
423
+ module_name,
424
+ target_modules,
425
+ self.packed_modules_mapping,
426
+ )
427
+ if (
428
+ not self._match_target_modules(module_name, module)
429
+ and not is_configured_target
430
+ ):
431
continue
432
433
punica_wrapper = self._get_punica_wrapper(module_name)
@@ -565,7 +574,7 @@ class LoRAModelManager:
574
model = LoRAModel(lora_id, rank, {})
575
for module_name, module in self.model.named_modules():
576
if (
568
- not self._match_target_modules(module_name)
577
+ not self._match_target_modules(module_name, module)
578
or not isinstance(module, BaseLayerWithLoRA)
579
or self._get_punica_wrapper(module_name) is None
580
):
@@ -701,7 +710,7 @@ class LoRAModelManager:
710
)
711
return adjusted_rank
712
704
- def _match_target_modules(self, module_name: str) -> bool:
713
+ def _match_target_modules(self, module_name: str, module: nn.Module) -> bool:
714
"""Check if a module should have LoRA applied.
715
716
This method first checks if the module is in vLLM's supported LoRA
@@ -711,11 +720,16 @@ class LoRAModelManager:
720
Args:
721
module_name: Full dot-separated module name (e.g.,
722
"model.layers.0.self_attn.o_proj")
723
+ module: Runtime module associated with ``module_name``.
724
725
Returns:
726
True if LoRA should be applied to this module, False otherwise.
727
"""
718
- if not is_supported_lora_module(module_name, self.supported_lora_modules):
728
+ if not is_supported_lora_module(
729
+ module_name,
730
+ module,
731
+ self.supported_lora_modules,
732
+ ):
733
return False
734
return is_in_target_modules(
735
module_name,
vllm/lora/utils.py
+20
-16
@@ -4,7 +4,6 @@
4
import os
5
from typing import TYPE_CHECKING
6
7
-import regex as re
7
from huggingface_hub.utils import HfHubHTTPError, HFValidationError
8
from torch import nn
9
from transformers import PretrainedConfig
@@ -33,8 +32,10 @@ from vllm.lora.layers import (
32
RowParallelLinearWithShardedLoRA,
33
VocabParallelEmbeddingWithLoRA,
34
)
35
+from vllm.model_executor.custom_op import maybe_get_oot_by_class
36
from vllm.model_executor.layers.fused_moe import MoERunner
37
from vllm.model_executor.layers.linear import LinearBase
38
+from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
39
from vllm.model_executor.utils import get_moe_expert_mapping, get_packed_modules_mapping
40
from vllm.transformers_utils.repo_utils import hf_api
41
@@ -230,11 +231,7 @@ def get_supported_lora_modules(model: nn.Module) -> list[str]:
231
for name in embedding_modules:
232
supported_lora_modules.add(name)
233
233
- # get all the linear subfixes.
234
- if isinstance(module, (LinearBase,)):
235
- supported_lora_modules.add(name.split(".")[-1])
236
-
237
- if isinstance(module, (MoERunner,)):
234
+ if isinstance(module, (LinearBase, MoERunner)):
235
supported_lora_modules.add(name.split(".")[-1])
236
237
return list(supported_lora_modules)
@@ -242,29 +239,36 @@ def get_supported_lora_modules(model: nn.Module) -> list[str]:
239
240
def is_supported_lora_module(
241
module_name: str,
242
+ module: nn.Module,
243
supported_lora_modules: list[str],
244
) -> bool:
245
"""Check if a module is in the model's supported LoRA modules.
246
249
- Uses regex suffix matching against the model-defined supported modules
250
- list (e.g., matching "model.layers.0.self_attn.o_proj" against
251
- "o_proj").
247
+ The module name must match a model-supported suffix, and the runtime
248
+ module must belong to a module family handled by LoRA.
249
250
Args:
251
module_name: Full dot-separated module name.
252
+ module: Runtime module associated with ``module_name``.
253
supported_lora_modules: List of module suffixes supported by the
254
model.
255
256
Returns:
257
True if the module is supported, False otherwise.
258
"""
261
- return any(
262
- re.match(
263
- r".*\.{target_module}$".format(target_module=target_module),
264
- module_name,
265
- )
266
- or target_module == module_name
267
- for target_module in supported_lora_modules
259
+ module_suffix = module_name.rsplit(".", 1)[-1]
260
+ if module_suffix not in supported_lora_modules:
261
+ return False
262
+
263
+ return isinstance(
264
+ module,
265
+ (
266
+ LinearBase,
267
+ MoERunner,
268
+ VocabParallelEmbedding,
269
+ maybe_get_oot_by_class(VocabParallelEmbedding),
270
+ BaseLayerWithLoRA,
271
+ ),
272
)
273
274