diff --git a/tests/lora/test_lora_utils.py b/tests/lora/test_lora_utils.py index 603ec92974..771e5b39b3 100644 --- a/tests/lora/test_lora_utils.py +++ b/tests/lora/test_lora_utils.py @@ -1,37 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project - -from vllm.lora.utils import is_in_target_modules, is_supported_lora_module - - -class TestIsSupportedLoraModule: - """Tests for is_supported_lora_module (model-definition check).""" - - def test_suffix_match(self): - assert is_supported_lora_module( - "model.layers.0.self_attn.o_proj", ["o_proj", "q_proj"] - ) - - def test_no_match(self): - assert not is_supported_lora_module( - "model.layers.0.self_attn.o_proj", ["q_proj", "k_proj"] - ) - - def test_exact_match(self): - assert is_supported_lora_module("o_proj", ["o_proj"]) - - def test_regex_suffix_matching(self): - """Regex anchors to end — partial suffix should not match.""" - assert not is_supported_lora_module("model.layers.0.self_attn.o_proj", ["proj"]) - - def test_empty_supported_modules(self): - assert not is_supported_lora_module("model.layers.0.self_attn.o_proj", []) - - def test_multiple_supported_modules(self): - supported = ["q_proj", "k_proj", "v_proj", "o_proj"] - assert is_supported_lora_module("model.layers.0.self_attn.v_proj", supported) - assert not is_supported_lora_module("model.layers.0.mlp.gate_proj", supported) +from vllm.lora.utils import is_in_target_modules class TestIsInTargetModules: diff --git a/vllm/lora/model_manager.py b/vllm/lora/model_manager.py index 5db3cd4faa..48e31a5c03 100644 --- a/vllm/lora/model_manager.py +++ b/vllm/lora/model_manager.py @@ -418,7 +418,16 @@ class LoRAModelManager: if isinstance(module, PPMissingLayer): continue - if not self._match_target_modules(module_name): + target_modules = self.lora_config.target_modules + is_configured_target = target_modules is not None and is_in_target_modules( + module_name, + target_modules, + self.packed_modules_mapping, + ) + if ( + not self._match_target_modules(module_name, module) + and not is_configured_target + ): continue punica_wrapper = self._get_punica_wrapper(module_name) @@ -565,7 +574,7 @@ class LoRAModelManager: model = LoRAModel(lora_id, rank, {}) for module_name, module in self.model.named_modules(): if ( - not self._match_target_modules(module_name) + not self._match_target_modules(module_name, module) or not isinstance(module, BaseLayerWithLoRA) or self._get_punica_wrapper(module_name) is None ): @@ -701,7 +710,7 @@ class LoRAModelManager: ) return adjusted_rank - def _match_target_modules(self, module_name: str) -> bool: + def _match_target_modules(self, module_name: str, module: nn.Module) -> bool: """Check if a module should have LoRA applied. This method first checks if the module is in vLLM's supported LoRA @@ -711,11 +720,16 @@ class LoRAModelManager: Args: module_name: Full dot-separated module name (e.g., "model.layers.0.self_attn.o_proj") + module: Runtime module associated with ``module_name``. Returns: True if LoRA should be applied to this module, False otherwise. """ - if not is_supported_lora_module(module_name, self.supported_lora_modules): + if not is_supported_lora_module( + module_name, + module, + self.supported_lora_modules, + ): return False return is_in_target_modules( module_name, diff --git a/vllm/lora/utils.py b/vllm/lora/utils.py index c5d5765bd0..f2a9dc902f 100644 --- a/vllm/lora/utils.py +++ b/vllm/lora/utils.py @@ -4,7 +4,6 @@ import os from typing import TYPE_CHECKING -import regex as re from huggingface_hub.utils import HfHubHTTPError, HFValidationError from torch import nn from transformers import PretrainedConfig @@ -33,8 +32,10 @@ from vllm.lora.layers import ( RowParallelLinearWithShardedLoRA, VocabParallelEmbeddingWithLoRA, ) +from vllm.model_executor.custom_op import maybe_get_oot_by_class from vllm.model_executor.layers.fused_moe import MoERunner from vllm.model_executor.layers.linear import LinearBase +from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding from vllm.model_executor.utils import get_moe_expert_mapping, get_packed_modules_mapping from vllm.transformers_utils.repo_utils import hf_api @@ -230,11 +231,7 @@ def get_supported_lora_modules(model: nn.Module) -> list[str]: for name in embedding_modules: supported_lora_modules.add(name) - # get all the linear subfixes. - if isinstance(module, (LinearBase,)): - supported_lora_modules.add(name.split(".")[-1]) - - if isinstance(module, (MoERunner,)): + if isinstance(module, (LinearBase, MoERunner)): supported_lora_modules.add(name.split(".")[-1]) return list(supported_lora_modules) @@ -242,29 +239,36 @@ def get_supported_lora_modules(model: nn.Module) -> list[str]: def is_supported_lora_module( module_name: str, + module: nn.Module, supported_lora_modules: list[str], ) -> bool: """Check if a module is in the model's supported LoRA modules. - Uses regex suffix matching against the model-defined supported modules - list (e.g., matching "model.layers.0.self_attn.o_proj" against - "o_proj"). + The module name must match a model-supported suffix, and the runtime + module must belong to a module family handled by LoRA. Args: module_name: Full dot-separated module name. + module: Runtime module associated with ``module_name``. supported_lora_modules: List of module suffixes supported by the model. Returns: True if the module is supported, False otherwise. """ - return any( - re.match( - r".*\.{target_module}$".format(target_module=target_module), - module_name, - ) - or target_module == module_name - for target_module in supported_lora_modules + module_suffix = module_name.rsplit(".", 1)[-1] + if module_suffix not in supported_lora_modules: + return False + + return isinstance( + module, + ( + LinearBase, + MoERunner, + VocabParallelEmbedding, + maybe_get_oot_by_class(VocabParallelEmbedding), + BaseLayerWithLoRA, + ), )