| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | |
| 4 | import os |
| 5 | import random |
| 6 | import tempfile |
| 7 | from unittest.mock import patch |
| 8 | |
| 9 | from vllm.config import ( |
| 10 | CacheConfig, |
| 11 | DeviceConfig, |
| 12 | ModelConfig, |
| 13 | ParallelConfig, |
| 14 | SchedulerConfig, |
| 15 | VllmConfig, |
| 16 | set_current_vllm_config, |
| 17 | ) |
| 18 | from vllm.config.load import LoadConfig |
| 19 | from vllm.config.lora import LoRAConfig |
| 20 | from vllm.lora.model_manager import LoRAMapping |
| 21 | from vllm.lora.request import LoRARequest |
| 22 | from vllm.platforms import current_platform |
| 23 | |
| 24 | if current_platform.is_xpu(): |
| 25 | from vllm.v1.worker.xpu_worker import XPUWorker as Worker |
| 26 | else: |
| 27 | from vllm.v1.worker.gpu_worker import Worker |
| 28 | |
| 29 | MODEL_PATH = "Qwen/Qwen3-0.6B" |
| 30 | NUM_LORAS = 16 |
| 31 | |
| 32 | DEVICE_TYPE = current_platform.device_type |
| 33 | |
| 34 | |
| 35 | @patch.dict(os.environ, {"RANK": "0"}) |
| 36 | def test_worker_apply_lora(qwen3_lora_files): |
| 37 | def set_active_loras(worker: Worker, lora_requests: list[LoRARequest]): |
| 38 | lora_mapping = LoRAMapping([], []) |
| 39 | |
| 40 | worker.model_runner.lora_manager.set_active_adapters( |
| 41 | lora_requests, lora_mapping |
| 42 | ) |
| 43 | |
| 44 | model_config = ModelConfig( |
| 45 | MODEL_PATH, |
| 46 | seed=0, |
| 47 | dtype="float16", |
| 48 | max_model_len=127, |
| 49 | enforce_eager=True, |
| 50 | ) |
| 51 | |
| 52 | vllm_config = VllmConfig( |
| 53 | model_config=model_config, |
| 54 | load_config=LoadConfig( |
| 55 | download_dir=None, |
| 56 | load_format="dummy", |
| 57 | ), |
| 58 | parallel_config=ParallelConfig( |
| 59 | pipeline_parallel_size=1, |
| 60 | tensor_parallel_size=1, |
| 61 | data_parallel_size=1, |
| 62 | ), |
| 63 | scheduler_config=SchedulerConfig( |
| 64 | max_model_len=model_config.max_model_len, |
| 65 | is_encoder_decoder=model_config.is_encoder_decoder, |
| 66 | runner_type="generate", |
| 67 | max_num_batched_tokens=32, |
| 68 | max_num_seqs=32, |
| 69 | ), |
| 70 | device_config=DeviceConfig(DEVICE_TYPE), |
| 71 | cache_config=CacheConfig( |
| 72 | block_size=16, |
| 73 | cache_dtype="auto", |
| 74 | ), |
| 75 | lora_config=LoRAConfig( |
| 76 | max_lora_rank=8, max_cpu_loras=NUM_LORAS, max_loras=NUM_LORAS |
| 77 | ), |
| 78 | ) |
| 79 | worker = Worker( |
| 80 | vllm_config=vllm_config, |
| 81 | local_rank=0, |
| 82 | rank=0, |
| 83 | distributed_init_method=f"file://{tempfile.mkstemp()[1]}", |
| 84 | ) |
| 85 | |
| 86 | with set_current_vllm_config(vllm_config): |
| 87 | worker.init_device() |
| 88 | worker.load_model() |
| 89 | |
| 90 | set_active_loras(worker, []) |
| 91 | assert worker.list_loras() == set() |
| 92 | |
| 93 | lora_requests = [ |
| 94 | LoRARequest(str(i + 1), i + 1, qwen3_lora_files) for i in range(NUM_LORAS) |
| 95 | ] |
| 96 | |
| 97 | set_active_loras(worker, lora_requests) |
| 98 | assert worker.list_loras() == { |
| 99 | lora_request.lora_int_id for lora_request in lora_requests |
| 100 | } |
| 101 | |
| 102 | for i in range(NUM_LORAS): |
| 103 | random.seed(i) |
| 104 | iter_lora_requests = random.choices( |
| 105 | lora_requests, k=random.randint(1, NUM_LORAS) |
| 106 | ) |
| 107 | random.shuffle(iter_lora_requests) |
| 108 | iter_lora_requests = iter_lora_requests[: -random.randint(0, NUM_LORAS)] |
| 109 | set_active_loras(worker, lora_requests) |
| 110 | assert worker.list_loras().issuperset( |
| 111 | {lora_request.lora_int_id for lora_request in iter_lora_requests} |
| 112 | ) |