| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | from abc import ABC, abstractmethod |
| 5 | from collections.abc import Set |
| 6 | from dataclasses import dataclass, field |
| 7 | |
| 8 | from vllm.logger import init_logger |
| 9 | from vllm.lora.request import LoRARequest |
| 10 | |
| 11 | logger = init_logger(__name__) |
| 12 | |
| 13 | |
| 14 | class LoRAResolver(ABC): |
| 15 | """Base class for LoRA adapter resolvers. |
| 16 | |
| 17 | This class defines the interface for resolving and fetching LoRA adapters. |
| 18 | Implementations of this class should handle the logic for locating and |
| 19 | downloading LoRA adapters from various sources (e.g. S3, cloud storage, |
| 20 | etc.). |
| 21 | """ |
| 22 | |
| 23 | @abstractmethod |
| 24 | async def resolve_lora( |
| 25 | self, base_model_name: str, lora_name: str |
| 26 | ) -> LoRARequest | None: |
| 27 | """Abstract method to resolve and fetch a LoRA model adapter. |
| 28 | |
| 29 | Implements logic to locate and download LoRA adapter based on the name. |
| 30 | Implementations might fetch from a blob storage or other sources. |
| 31 | |
| 32 | Args: |
| 33 | base_model_name: The name/identifier of the base model to resolve. |
| 34 | lora_name: The name/identifier of the LoRA model to resolve. |
| 35 | |
| 36 | Returns: |
| 37 | Optional[LoRARequest]: The resolved LoRA model information, or None |
| 38 | if the LoRA model cannot be found. |
| 39 | |
| 40 | """ |
| 41 | pass |
| 42 | |
| 43 | |
| 44 | @dataclass |
| 45 | class _LoRAResolverRegistry: |
| 46 | resolvers: dict[str, LoRAResolver] = field(default_factory=dict) |
| 47 | |
| 48 | def get_supported_resolvers(self) -> Set[str]: |
| 49 | """Get all registered resolver names.""" |
| 50 | return self.resolvers.keys() |
| 51 | |
| 52 | def register_resolver( |
| 53 | self, |
| 54 | resolver_name: str, |
| 55 | resolver: LoRAResolver, |
| 56 | ) -> None: |
| 57 | """Register a LoRA resolver. |
| 58 | |
| 59 | Args: |
| 60 | resolver_name: Name to register the resolver under. |
| 61 | resolver: The LoRA resolver instance to register. |
| 62 | |
| 63 | """ |
| 64 | if resolver_name in self.resolvers: |
| 65 | logger.warning( |
| 66 | "LoRA resolver %s is already registered, and will be " |
| 67 | "overwritten by the new resolver instance %s.", |
| 68 | resolver_name, |
| 69 | resolver, |
| 70 | ) |
| 71 | |
| 72 | self.resolvers[resolver_name] = resolver |
| 73 | |
| 74 | def get_resolver(self, resolver_name: str) -> LoRAResolver: |
| 75 | """Get a registered resolver instance by name. |
| 76 | |
| 77 | Args: |
| 78 | resolver_name: Name of the resolver to get. |
| 79 | |
| 80 | Returns: |
| 81 | The resolver instance. |
| 82 | |
| 83 | Raises: |
| 84 | KeyError: If the resolver is not found in the registry. |
| 85 | |
| 86 | """ |
| 87 | if resolver_name not in self.resolvers: |
| 88 | raise KeyError( |
| 89 | f"LoRA resolver '{resolver_name}' not found. " |
| 90 | f"Available resolvers: {list(self.resolvers.keys())}" |
| 91 | ) |
| 92 | return self.resolvers[resolver_name] |
| 93 | |
| 94 | |
| 95 | LoRAResolverRegistry = _LoRAResolverRegistry() |