From f27ee91eab7048f89103fbc8c50e0f9c90a39375 Mon Sep 17 00:00:00 2001 From: Nick Hill Date: Tue, 29 Sep 2026 11:27:14 -0700 Subject: [PATCH] [Bugfix][Frontend] Reject LoRA adapters named after a served model A LoRA adapter loaded (dynamically or via --lora-modules) under a served base model's name was registered in lora_requests, which _maybe_get_adapters consults before the base models, so the adapter silently captured every request for that model and /v1/models listed a duplicate id. Reject such names with a 400 in _check_load_lora_adapter_request, including load_inplace, mirroring the Rust frontend's LoadLoraError::BaseModelName check. Static LoRA modules go through the same path, so init_static_loras now fails at startup. Signed-off-by: Nick Hill Co-Authored-By: Claude Opus 5.5 --- .../serve/lora/test_serving_models.py | 16 ++++++++++++++++ vllm/entrypoints/openai/models/serving.py | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/entrypoints/serve/lora/test_serving_models.py b/tests/entrypoints/serve/lora/test_serving_models.py index 755f875333cf..1d6176a43464 100644 --- a/tests/entrypoints/serve/lora/test_serving_models.py +++ b/tests/entrypoints/serve/lora/test_serving_models.py @@ -99,6 +99,22 @@ async def test_load_lora_adapter_duplicate(): assert len(serving_models.lora_requests) == 1 +@pytest.mark.asyncio +@pytest.mark.parametrize("load_inplace", [False, True]) +async def test_load_lora_adapter_base_model_name(load_inplace: bool): + """A LoRA named after a served model would shadow it for all requests.""" + serving_models = await _async_serving_models_init() + request = LoadLoRAAdapterRequest( + lora_name=MODEL_NAME, lora_path="/path/to/adapter", load_inplace=load_inplace + ) + response = await serving_models.load_lora_adapter(request) + assert isinstance(response, ErrorResponse) + assert response.error.type == "InvalidUserInput" + assert response.error.code == HTTPStatus.BAD_REQUEST + assert len(serving_models.lora_requests) == 0 + serving_models.engine_client.add_lora.assert_not_called() + + @pytest.mark.asyncio async def test_unload_lora_adapter_success(): serving_models = await _async_serving_models_init() diff --git a/vllm/entrypoints/openai/models/serving.py b/vllm/entrypoints/openai/models/serving.py index 1e13311dce2a..c139effa79f5 100644 --- a/vllm/entrypoints/openai/models/serving.py +++ b/vllm/entrypoints/openai/models/serving.py @@ -245,6 +245,14 @@ async def _check_load_lora_adapter_request( status_code=HTTPStatus.BAD_REQUEST, ) + if self.is_base_model(request.lora_name): + return create_error_response( + message=f"The lora adapter '{request.lora_name}' conflicts with a " + "served base model.", + err_type="InvalidUserInput", + status_code=HTTPStatus.BAD_REQUEST, + ) + # If not loading inplace # Check if the lora adapter with the given name already exists if not request.load_inplace and request.lora_name in self.lora_requests: