diff --git a/vllm/lora/model_manager.py b/vllm/lora/model_manager.py index 7611d2d71a03..6cf846cae947 100644 --- a/vllm/lora/model_manager.py +++ b/vllm/lora/model_manager.py @@ -249,6 +249,7 @@ def activate_adapter( "Activating LoRA. int id: %d, slot index: %d", lora_model.id, index ) self.lora_index_to_id[index] = lora_model.id + num_applied = 0 for module_name, module in self.modules.items(): module_lora = self._get_lora_layer_weights(lora_model, module_name) if not module_lora: @@ -260,6 +261,16 @@ def activate_adapter( module_lora.lora_a, module_lora.lora_b, ) + num_applied += 1 + + if num_applied == 0 and self.modules: + logger.warning( + "LoRA adapter %d was activated but none of its weights " + "matched any LoRA-eligible module. The adapter will have " + "no effect. This usually means the model class is missing " + "an hf_to_vllm_mapper attribute.", + lora_model.id, + ) return True diff --git a/vllm/lora/worker_manager.py b/vllm/lora/worker_manager.py index 2db747e2ceab..160baf9db4db 100644 --- a/vllm/lora/worker_manager.py +++ b/vllm/lora/worker_manager.py @@ -130,6 +130,24 @@ def _load_adapter(self, lora_request: LoRARequest) -> LoRAModel: skip_prefixes=lora_skip_prefixes, ) + # Check that at least some loaded LoRA modules will match + # the model's module names. When hf_to_vllm_mapper is + # missing, LoRA weights may load successfully but with + # wrong module paths, causing them to be silently ignored. + model_module_names = {name for name, _ in model.named_modules()} + matched = any( + module_name in model_module_names for module_name in lora.loras + ) + if lora.loras and not matched: + logger.warning( + "None of the LoRA modules in adapter '%s' matched " + "any module in %s. The adapter weights will have no " + "effect. This is usually caused by a missing " + "hf_to_vllm_mapper on the model class.", + lora_request.lora_name, + model.__class__.__name__, + ) + except FileNotFoundError as e: # FileNotFoundError should be raised if both # - No adapter found to download from huggingface (or in diff --git a/vllm/model_executor/models/pixtral.py b/vllm/model_executor/models/pixtral.py index 0cfa8b6a3a84..bef16f68f769 100644 --- a/vllm/model_executor/models/pixtral.py +++ b/vllm/model_executor/models/pixtral.py @@ -40,6 +40,7 @@ ) from vllm.model_executor.layers.quantization import QuantizationConfig from vllm.model_executor.model_loader.weight_utils import default_weight_loader +from vllm.model_executor.models.utils import WeightsMapper from vllm.multimodal import MULTIMODAL_REGISTRY, MultiModalKwargsItems from vllm.multimodal.inputs import ( MultiModalDataDict, @@ -372,6 +373,15 @@ def _cached_apply_hf_processor( class PixtralForConditionalGeneration( nn.Module, SupportsLoRA, SupportsMultiModal, SupportsPP ): + hf_to_vllm_mapper = WeightsMapper( + orig_to_new_prefix={ + "model.language_model.": "language_model.model.", + "model.vision_tower.": "vision_tower.", + "model.multi_modal_projector.": "multi_modal_projector.", + "lm_head.": "language_model.lm_head.", + } + ) + @classmethod def get_placeholder_str(cls, modality: str, i: int) -> str | None: if modality.startswith("image"):