main
py 99 lines 3.52 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import logging
4
5 from fastapi import APIRouter, Depends, FastAPI, Request
6 from fastapi.responses import JSONResponse, Response
7
8 from vllm import envs
9 from vllm.entrypoints.openai.models.api_router import models
10 from vllm.entrypoints.openai.models.serving import OpenAIServingModels
11 from vllm.entrypoints.serve.engine.protocol import ErrorResponse
12 from vllm.entrypoints.serve.lora.protocol import (
13 LoadLoRAAdapterRequest,
14 UnloadLoRAAdapterRequest,
15 )
16 from vllm.entrypoints.serve.utils.api_utils import validate_json_request
17 from vllm.logger import init_logger
18
19 logger = init_logger(__name__)
20 router = APIRouter()
21
22
23 def attach_router(app: FastAPI):
24 """Attach the LoRA adapter load/unload endpoints to the API server.
25
26 Does nothing when dynamic LoRA updating is disabled. Handler levels are
27 snapshotted and restored because importing
28 model_hosting_container_standards may reconfigure root logging.
29 """
30 if not envs.VLLM_ALLOW_RUNTIME_LORA_UPDATING:
31 """If LoRA dynamic loading & unloading is not enabled, do nothing."""
32 return
33 logger.warning(
34 "LoRA dynamic loading & unloading is enabled in the API server. "
35 "This should ONLY be used for local development!"
36 )
37
38 snapshot = [
39 (h, h.level)
40 for lg in (logging.getLogger(), logging.getLogger("vllm"))
41 for h in lg.handlers
42 ]
43
44 try:
45 _attach_router(app)
46 finally:
47 for handler, level in snapshot:
48 handler.setLevel(level)
49
50
51 def _attach_router(app: FastAPI):
52 """Register the LoRA adapter load/unload routes on the app."""
53 import model_hosting_container_standards.sagemaker as sagemaker_standards
54
55 @sagemaker_standards.register_load_adapter_handler(
56 request_shape={
57 "lora_name": "body.name",
58 "lora_path": "body.src",
59 "load_inplace": "body.load_inplace || `false`",
60 "is_3d_lora_weight": "body.is_3d_lora_weight || `false`",
61 },
62 )
63 @router.post("/v1/load_lora_adapter", dependencies=[Depends(validate_json_request)])
64 async def load_lora_adapter(request: LoadLoRAAdapterRequest, raw_request: Request):
65 """Handle POST /v1/load_lora_adapter: load a LoRA adapter into the
66 serving engine."""
67 handler: OpenAIServingModels = models(raw_request)
68 response = await handler.load_lora_adapter(request)
69 if isinstance(response, ErrorResponse):
70 return JSONResponse(
71 content=response.model_dump(), status_code=response.error.code
72 )
73
74 return Response(status_code=200, content=response)
75
76 @sagemaker_standards.register_unload_adapter_handler(
77 request_shape={
78 "lora_name": "path_params.adapter_name",
79 }
80 )
81 @router.post(
82 "/v1/unload_lora_adapter", dependencies=[Depends(validate_json_request)]
83 )
84 async def unload_lora_adapter(
85 request: UnloadLoRAAdapterRequest, raw_request: Request
86 ):
87 """Handle POST /v1/unload_lora_adapter: unload a LoRA adapter from
88 the serving engine."""
89 handler: OpenAIServingModels = models(raw_request)
90 response = await handler.unload_lora_adapter(request)
91 if isinstance(response, ErrorResponse):
92 return JSONResponse(
93 content=response.model_dump(), status_code=response.error.code
94 )
95
96 return Response(status_code=200, content=response)
97
98 # register the router
99 app.include_router(router)