A high-throughput and memory-efficient inference and serving engine for LLMs
[Model Runner V2][Bugfix] Fix MRV2 LoRA warmup (#35536)
Signed-off-by: Jee Jee Li <pandaleefree@gmail.com> Signed-off-by: Jee Jee Li <jeejeelee@inferact.ai> Signed-off-by: Woosuk Kwon <woosuk@inferact.ai> Co-authored-by: Nick Hill <nickhill123@gmail.com> Co-authored-by: Woosuk Kwon <woosuk@inferact.ai>
Jee Jee Li committed
Jun 16, 2026 at 04:17 UTC
eacff17c8d574daea685387216b6bb23959ab2b1
5 files changed
+227
-63
tests/lora/test_qwen3_with_multi_loras.py
+14
-4
@@ -6,6 +6,8 @@ This script contains:
6
2. test multi loras request
7
"""
8
9
+import os
10
+
11
import pytest
12
13
from tests.utils import multi_gpu_test
@@ -39,6 +41,18 @@ def format_chatml_messages(
41
]
42
43
44
+@pytest.fixture(autouse=True)
45
+def set_mrv2_env():
46
+ original = os.environ.get("VLLM_USE_V2_MODEL_RUNNER", "0")
47
+ os.environ["VLLM_USE_V2_MODEL_RUNNER"] = "1"
48
+ yield
49
+
50
+ if original is None:
51
+ os.environ.pop("VLLM_USE_V2_MODEL_RUNNER", None)
52
+ else:
53
+ os.environ["VLLM_USE_V2_MODEL_RUNNER"] = original
54
+
55
+
56
def make_add_lora_request(name: str, path: str):
57
global INCREASE_LORA_ID, LORA_NAME_ID_MAP
58
@@ -61,7 +75,6 @@ def test_multi_loras_with_tp_sync():
75
max_lora_rank=LORA_RANK,
76
max_model_len=512,
77
gpu_memory_utilization=0.5,
64
- enforce_eager=True,
78
tensor_parallel_size=2, # ensure tp >= 2
79
max_cpu_loras=4, # ensure max_cpu_loras >= 2
80
)
@@ -167,7 +180,6 @@ def test_multiple_lora_requests():
180
max_lora_rank=LORA_RANK,
181
max_model_len=512,
182
gpu_memory_utilization=0.5,
170
- enforce_eager=True,
183
)
184
PROMPTS = ["Hello, my name is"] * 2
185
LORA_NAME = "Alice"
@@ -203,7 +215,6 @@ def test_load_inplace_offline_reload(
215
max_lora_rank=LORA_RANK,
216
max_model_len=512,
217
gpu_memory_utilization=0.5,
206
- enforce_eager=True,
218
)
219
adapter_id = 1
220
messages = format_chatml_messages(
@@ -254,7 +265,6 @@ def test_load_inplace_false_no_reload(
265
max_lora_rank=LORA_RANK,
266
max_model_len=512,
267
gpu_memory_utilization=0.5,
257
- enforce_eager=True,
268
)
269
adapter_id = 2
270
messages = format_chatml_messages(
vllm/v1/worker/gpu/cudagraph_utils.py
+92
-20
@@ -3,6 +3,7 @@
3
from collections import defaultdict
4
from collections.abc import Callable
5
from dataclasses import dataclass
6
+from itertools import product
7
from typing import Any, NamedTuple, Protocol
8
9
import torch
@@ -56,6 +57,7 @@ class BatchExecutionDescriptor:
57
num_tokens: int
58
num_reqs: int | None # None means no request padding is needed (PIECEWISE graphs)
59
uniform_token_count: int | None = None
60
+ num_active_loras: int = 0
61
62
63
class CreateForwardFn(Protocol):
@@ -75,6 +77,7 @@ def _is_compatible(
77
num_reqs: int,
78
num_tokens: int,
79
uniform_token_count: int | None,
80
+ num_active_loras: int,
81
) -> bool:
82
# desc.uniform_token_count=None (PIECEWISE) can handle any uniform_token_count
83
# desc.num_reqs=None means no request padding needed (PIECEWISE)
@@ -85,6 +88,7 @@ def _is_compatible(
88
)
89
and (desc.num_reqs is None or desc.num_reqs >= num_reqs)
90
and desc.num_tokens >= num_tokens
91
+ and desc.num_active_loras == num_active_loras
92
)
93
94
@@ -111,6 +115,7 @@ class CudaGraphManager:
115
device: torch.device,
116
cudagraph_mode: CUDAGraphMode,
117
decode_query_len: int,
118
+ lora_capture_cases: list[int] | None = None,
119
):
120
self.vllm_config = vllm_config
121
self.device = device
@@ -124,12 +129,17 @@ class CudaGraphManager:
129
self.tp_size = vllm_config.parallel_config.tensor_parallel_size
130
self.is_first_pp_rank = get_pp_group().is_first_rank
131
self.is_last_pp_rank = get_pp_group().is_last_rank
132
+ self.lora_capture_cases = lora_capture_cases or [0]
133
+ # Precompute actual num_active_loras -> captured case mapping so that
134
+ # dispatch() is a plain dict lookup instead of a per-call bisect.
135
+ self._lora_dispatch_map, self._max_lora_case = self._build_lora_dispatch_map()
136
137
self.graphs: dict[BatchExecutionDescriptor, torch.cuda.CUDAGraph] = {}
138
self.pool = current_platform.get_global_graph_pool() if cudagraph_mode else None
139
140
self._graphs_captured = False
132
- self._candidates: list[list[BatchExecutionDescriptor]] = []
141
+
142
+ self._candidates: dict[tuple[int, int], list[BatchExecutionDescriptor]] = {}
143
self._capture_descs: dict[CUDAGraphMode, list[BatchExecutionDescriptor]] = {}
144
# adjust the cudagraph sizes to be a multiple of the uniform decode query length
145
self.compilation_config.adjust_cudagraph_sizes_for_spec_decode(
@@ -144,6 +154,32 @@ class CudaGraphManager:
154
)
155
self.breakable_cg_runner: BreakableCUDAGraphWrapper | None = None
156
157
+ def _build_lora_dispatch_map(self) -> tuple[dict[int, int], int]:
158
+ """Precompute actual num_active_loras -> effective captured case.
159
+
160
+ Mirrors the num_tokens candidate expansion in ``_init_candidates``:
161
+ every possible active-LoRA count is mapped ahead of time to the
162
+ smallest captured case that can serve it, so ``dispatch`` is a plain
163
+ dict lookup instead of a per-call bisect.
164
+ """
165
+ captured_with_lora = sorted(c for c in self.lora_capture_cases if c > 0)
166
+ if not captured_with_lora:
167
+ return {}, 0
168
+ dispatch_map: dict[int, int] = {}
169
+ case_idx = 0
170
+ for n in range(1, captured_with_lora[-1] + 1):
171
+ while captured_with_lora[case_idx] < n:
172
+ case_idx += 1
173
+ dispatch_map[n] = captured_with_lora[case_idx]
174
+ return dispatch_map, captured_with_lora[-1]
175
+
176
+ def _resolve_effective_loras(self, num_active_loras: int) -> int:
177
+ """Map an actual active-LoRA count to its captured graph case."""
178
+ if num_active_loras <= 0 or not self._lora_dispatch_map:
179
+ return num_active_loras
180
+ # Counts above the largest captured case clamp to it.
181
+ return self._lora_dispatch_map.get(num_active_loras, self._max_lora_case)
182
+
183
def _init_candidates(self) -> None:
184
"""Build priority-ordered candidate lists for each token count."""
185
capture_sizes = self.compilation_config.cudagraph_capture_sizes
@@ -156,10 +192,14 @@ class CudaGraphManager:
192
mixed_mode = self.cudagraph_mode.mixed_mode()
193
separate_decode_routine = self.cudagraph_mode.separate_routine()
194
159
- descs_by_token_count = defaultdict(list)
195
+ descs_by_token_lora: dict[tuple[int, int], list[BatchExecutionDescriptor]] = (
196
+ defaultdict(list)
197
+ )
198
descs_by_mode = defaultdict(list)
199
162
- for num_tokens in capture_sizes:
200
+ for num_tokens, num_active_loras in product(
201
+ capture_sizes, self.lora_capture_cases
202
+ ):
203
# Capture uniform decode specfifc graphs if required
204
# (i.e. separate decode routine)
205
if (
@@ -172,9 +212,10 @@ class CudaGraphManager:
212
num_tokens=num_tokens,
213
num_reqs=num_tokens // self.decode_query_len,
214
uniform_token_count=self.decode_query_len,
215
+ num_active_loras=num_active_loras,
216
)
217
descs_by_mode[decode_mode].append(desc)
177
- descs_by_token_count[num_tokens].append(desc)
218
+ descs_by_token_lora[(num_tokens, num_active_loras)].append(desc)
219
220
if mixed_mode:
221
# for PIECEWISE graphs there is no limit on requests when replaying
@@ -189,21 +230,25 @@ class CudaGraphManager:
230
cg_mode=mixed_mode,
231
num_tokens=num_tokens,
232
num_reqs=num_reqs,
233
+ num_active_loras=num_active_loras,
234
)
235
descs_by_mode[mixed_mode].append(desc)
194
- descs_by_token_count[num_tokens].append(desc)
236
+ descs_by_token_lora[(num_tokens, num_active_loras)].append(desc)
237
196
- if not descs_by_token_count:
238
+ if not descs_by_token_lora:
239
return
240
199
- sorted_padded = sorted(descs_by_token_count.keys())
200
- self._candidates = [[] for _ in range(sorted_padded[-1] + 1)]
201
-
241
+ all_token_counts = sorted({k[0] for k in descs_by_token_lora})
242
current_range_start = 0
203
- for cg_size in sorted_padded:
204
- for i in range(current_range_start, cg_size + 1):
205
- self._candidates[i] = descs_by_token_count[cg_size]
206
- current_range_start = cg_size + 1
243
+ for token_cg_size in all_token_counts:
244
+ for i in range(current_range_start, token_cg_size + 1):
245
+ for num_active_loras in self.lora_capture_cases:
246
+ staging_key = (token_cg_size, num_active_loras)
247
+ if staging_key in descs_by_token_lora:
248
+ self._candidates[(i, num_active_loras)] = descs_by_token_lora[
249
+ staging_key
250
+ ]
251
+ current_range_start = token_cg_size + 1
252
253
for mode, descs in descs_by_mode.items():
254
descs.sort(key=lambda d: d.num_tokens, reverse=True)
@@ -289,14 +334,27 @@ class CudaGraphManager:
334
num_reqs: int,
335
num_tokens: int,
336
uniform_token_count: int | None,
337
+ num_active_loras: int,
338
) -> BatchExecutionDescriptor:
339
"""Find matching cudagraph descriptor from priority-ordered candidates."""
294
- if self._graphs_captured and 0 < num_tokens < len(self._candidates):
295
- for desc in self._candidates[num_tokens]:
296
- if _is_compatible(desc, num_reqs, num_tokens, uniform_token_count):
340
+
341
+ effective_loras = self._resolve_effective_loras(num_active_loras)
342
+ key = (num_tokens, effective_loras)
343
+ if self._graphs_captured and num_tokens > 0 and key in self._candidates:
344
+ for desc in self._candidates[key]:
345
+ if _is_compatible(
346
+ desc,
347
+ num_reqs,
348
+ num_tokens,
349
+ uniform_token_count,
350
+ effective_loras,
351
+ ):
352
return desc
353
return BatchExecutionDescriptor(
299
- cg_mode=CUDAGraphMode.NONE, num_tokens=num_tokens, num_reqs=num_reqs
354
+ cg_mode=CUDAGraphMode.NONE,
355
+ num_tokens=num_tokens,
356
+ num_reqs=num_reqs,
357
+ num_active_loras=effective_loras,
358
)
359
360
def run_fullgraph(self, desc: BatchExecutionDescriptor):
@@ -337,9 +395,15 @@ class ModelCudaGraphManager(CudaGraphManager):
395
device: torch.device,
396
cudagraph_mode: CUDAGraphMode,
397
decode_query_len: int,
398
+ lora_capture_cases: list[int] | None = None,
399
):
341
- super().__init__(vllm_config, device, cudagraph_mode, decode_query_len)
342
- # Used for FULL CUDA graphs. PW CUDA graphs do not use these.
400
+ super().__init__(
401
+ vllm_config,
402
+ device,
403
+ cudagraph_mode,
404
+ decode_query_len,
405
+ lora_capture_cases=lora_capture_cases,
406
+ )
407
self.hidden_states: torch.Tensor | None = None
408
self.aux_hidden_states: list[torch.Tensor] = []
409
self.use_aux_hidden_state_outputs = False
@@ -356,6 +420,7 @@ class ModelCudaGraphManager(CudaGraphManager):
420
kv_cache_config: KVCacheConfig,
421
has_lora: bool = False,
422
use_aux_hidden_state_outputs: bool = False,
423
+ lora_capture_hook: Callable[[int, int, int], None] | None = None,
424
progress_bar_desc: str = "Capturing CUDA graphs",
425
) -> dict[BatchExecutionDescriptor, AttentionStatePair]:
426
"""Capture CUDA graphs for model forward pass."""
@@ -372,6 +437,11 @@ class ModelCudaGraphManager(CudaGraphManager):
437
]:
438
num_tokens = desc.num_tokens
439
num_reqs = desc.num_reqs or min(num_tokens, self.max_num_reqs)
440
+
441
+ # Set LoRA state before capture so kernels see correct adapters.
442
+ if lora_capture_hook is not None:
443
+ lora_capture_hook(desc.num_active_loras, num_reqs, num_tokens)
444
+
445
num_tokens_across_dp = (
446
torch.full((self.dp_size,), num_tokens, dtype=torch.int32, device="cpu")
447
if self.dp_size > 1
@@ -406,7 +476,9 @@ class ModelCudaGraphManager(CudaGraphManager):
476
if cg_mode == CUDAGraphMode.PIECEWISE:
477
assert attn_metadata is None
478
batch_descriptor = BatchDescriptor(
409
- num_tokens=num_tokens, has_lora=has_lora
479
+ num_tokens=num_tokens,
480
+ has_lora=has_lora,
481
+ num_active_loras=desc.num_active_loras,
482
)
483
with set_forward_context(
484
attn_metadata,
vllm/v1/worker/gpu/dp_utils.py
+15
-3
@@ -21,6 +21,7 @@ def sync_cudagraph_and_dp_padding(
21
uniform_token_count: int | None,
22
dp_size: int,
23
dp_rank: int,
24
+ num_active_loras: int = 0,
25
) -> tuple[BatchExecutionDescriptor, torch.Tensor | None]:
26
"""
27
Coordinates the batch descriptor and DP padding across all ranks.
@@ -53,6 +54,7 @@ def sync_cudagraph_and_dp_padding(
54
cg_mode=CUDAGraphMode.NONE,
55
num_tokens=num_tokens,
56
num_reqs=num_reqs,
57
+ num_active_loras=desired_batch_desc.num_active_loras,
58
), num_tokens_across_dp
59
60
assert cudagraph_manager is not None, (
@@ -68,9 +70,13 @@ def sync_cudagraph_and_dp_padding(
70
synced_uniform_token_count = None
71
72
# Dispatch for the final synced values, use num_reqs instead of synced_num_reqs
71
- # so we don't perform request padding for PIECEWISE graphs
73
+ # so we don't perform request padding for PIECEWISE graphs.
74
+ # num_active_loras is per-rank and doesn't need cross-rank agreement.
75
synced_desc = cudagraph_manager.dispatch(
73
- num_reqs, synced_num_tokens, synced_uniform_token_count
76
+ num_reqs,
77
+ synced_num_tokens,
78
+ synced_uniform_token_count,
79
+ num_active_loras=num_active_loras,
80
)
81
82
# Update num_tokens_across_dp to reflect padded size.
@@ -87,12 +93,14 @@ def dispatch_cg_and_sync_dp(
93
dp_size: int,
94
dp_rank: int,
95
need_eager: bool = False,
96
+ num_active_loras: int = 0,
97
) -> tuple[BatchExecutionDescriptor, torch.Tensor | None]:
98
if need_eager:
99
batch_desc = BatchExecutionDescriptor(
100
cg_mode=CUDAGraphMode.NONE,
101
num_tokens=num_tokens,
102
num_reqs=num_reqs,
103
+ num_active_loras=num_active_loras,
104
)
105
else:
106
assert cudagraph_manager is not None, (
@@ -100,7 +108,10 @@ def dispatch_cg_and_sync_dp(
108
"where need_eager must be True"
109
)
110
batch_desc = cudagraph_manager.dispatch(
103
- num_reqs, num_tokens, uniform_token_count
111
+ num_reqs,
112
+ num_tokens,
113
+ uniform_token_count,
114
+ num_active_loras=num_active_loras,
115
)
116
117
if dp_size == 1:
@@ -114,4 +125,5 @@ def dispatch_cg_and_sync_dp(
125
uniform_token_count,
126
dp_size,
127
dp_rank,
128
+ num_active_loras=num_active_loras,
129
)
vllm/v1/worker/gpu/lora_utils.py
+66
-1
@@ -1,12 +1,74 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""LoRA utilities for the Model Runner V2 and cudagraph."""
4
+
5
+from collections.abc import Callable
6
+from typing import TYPE_CHECKING, Any
7
+
8
import numpy as np
9
10
from vllm.lora.request import LoRARequest
11
+from vllm.lora.utils import get_captured_lora_counts
12
+
13
+if TYPE_CHECKING:
14
+ from vllm.config.compilation import CompilationConfig
15
+ from vllm.config.lora import LoRAConfig
16
17
NO_LORA_ID = 0
18
19
20
+def get_lora_capture_cases(
21
+ lora_config: "LoRAConfig | None",
22
+ compilation_config: "CompilationConfig",
23
+) -> list[int]:
24
+ """
25
+ Return num_active_loras values for cudagraph capture.
26
+
27
+ When cudagraph_specialize_lora=True: powers of 2 up to max_loras, plus
28
+ max_loras+1. When False: [0, max_loras+1]. When LoRA disabled: [0].
29
+ """
30
+ if lora_config is None:
31
+ return [0]
32
+ if compilation_config.cudagraph_specialize_lora:
33
+ specialize = getattr(lora_config, "specialize_active_lora", False)
34
+ captured = get_captured_lora_counts(lora_config.max_loras, specialize)
35
+ return [0] + [c for c in captured if c > 0]
36
+ return [0, lora_config.max_loras + 1]
37
+
38
+
39
+def get_num_active_loras_for_dispatch(
40
+ lora_config: "LoRAConfig | None",
41
+ lora_state: "LoraState",
42
+ req_ids: list[str],
43
+ dummy_run: bool,
44
+) -> int:
45
+ """Compute num_active_loras for cudagraph dispatch."""
46
+ if lora_config and not dummy_run:
47
+ return len(lora_state.get_activate_loras(req_ids))
48
+ if dummy_run and lora_config:
49
+ return lora_config.max_loras + 1
50
+ return 0
51
+
52
+
53
+def create_lora_capture_hook(
54
+ lora_config: "LoRAConfig | None",
55
+ runner: Any,
56
+) -> Callable[[int, int, int], None] | None:
57
+ """Create a hook to set up LoRA state before each cudagraph capture."""
58
+ if lora_config is None:
59
+ return None
60
+
61
+ def hook(num_active_loras: int, num_reqs: int, num_tokens: int) -> None:
62
+ num_scheduled = np.full(num_reqs, num_tokens // num_reqs, dtype=np.int32)
63
+ num_scheduled[-1] += num_tokens % num_reqs
64
+ with runner.maybe_select_dummy_loras(
65
+ lora_config, num_scheduled, num_active_loras=num_active_loras
66
+ ):
67
+ pass
68
+
69
+ return hook
70
+
71
+
72
class LoraState:
73
def __init__(self, max_num_reqs: int):
74
self.lora_ids = np.zeros(max_num_reqs, dtype=np.int32)
@@ -35,10 +97,13 @@ class LoraState:
97
lora_ids = self.lora_ids[idx_mapping]
98
prompt_lora_mapping = tuple(lora_ids)
99
token_lora_mapping = tuple(lora_ids.repeat(num_scheduled_tokens))
100
+ active_lora_requests: set[LoRARequest] = self.get_activate_loras(req_ids)
101
+ return prompt_lora_mapping, token_lora_mapping, active_lora_requests
102
103
+ def get_activate_loras(self, req_ids: list[str]) -> set[LoRARequest]:
104
active_lora_requests: set[LoRARequest] = set()
105
for req_id in req_ids:
106
lora_request = self.lora_requests.get(req_id)
107
if lora_request is not None:
108
active_lora_requests.add(lora_request)
44
- return prompt_lora_mapping, token_lora_mapping, active_lora_requests
109
+ return active_lora_requests
vllm/v1/worker/gpu/model_runner.py
+40
-35
@@ -37,7 +37,6 @@ from vllm.distributed.parallel_state import (
37
)
38
from vllm.forward_context import BatchDescriptor, set_forward_context
39
from vllm.logger import init_logger
40
-from vllm.lora.layers import LoRAMapping
40
from vllm.model_executor.layers.mamba.ops.ssu_dispatch import (
41
initialize_mamba_ssu_backend,
42
)
@@ -88,7 +87,12 @@ from vllm.v1.worker.gpu.kv_connector import (
87
KVConnector,
88
get_kv_connector,
89
)
91
-from vllm.v1.worker.gpu.lora_utils import LoraState
90
+from vllm.v1.worker.gpu.lora_utils import (
91
+ LoraState,
92
+ create_lora_capture_hook,
93
+ get_lora_capture_cases,
94
+ get_num_active_loras_for_dispatch,
95
+)
96
from vllm.v1.worker.gpu.mm.encoder_cache import EncoderCache
97
from vllm.v1.worker.gpu.mm.lora import set_active_mm_loras
98
from vllm.v1.worker.gpu.model_states import init_model_state
@@ -234,8 +238,15 @@ class GPUModelRunner(LoRAModelRunnerMixin):
238
self.prompt_logprobs_worker: PromptLogprobsWorker | None = None
239
self.structured_outputs_worker: StructuredOutputsWorker | None = None
240
self.cudagraph_manager: ModelCudaGraphManager | None = None
241
+
242
# LoRA-related workers.
243
self.lora_state = LoraState(max_num_reqs=self.max_num_reqs)
244
+ self.lora_capture_cases = [0]
245
+ if self.lora_config:
246
+ self.lora_capture_cases = get_lora_capture_cases(
247
+ self.lora_config, self.compilation_config
248
+ )
249
+
250
# KV Connector if configured.
251
self.kv_connector: KVConnector = NO_OP_KV_CONNECTOR
252
@@ -458,6 +469,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
469
self.device,
470
cudagraph_mode,
471
decode_query_len=self.decode_query_len,
472
+ lora_capture_cases=self.lora_capture_cases,
473
)
474
if self.speculator is not None:
475
self.speculator.init_cudagraph_manager(cudagraph_mode)
@@ -540,14 +552,22 @@ class GPUModelRunner(LoRAModelRunnerMixin):
552
assert self.intermediate_tensors is not None
553
intermediate_tensors = self.intermediate_tensors[:num_tokens]
554
543
- # Execute the model.
544
- self.execute_model(
545
- dummy_scheduler_output,
546
- intermediate_tensors=intermediate_tensors,
547
- dummy_run=True,
548
- skip_attn_for_dummy_run=skip_attn,
549
- is_profile=is_profile,
550
- )
555
+ max_loras = self.lora_config.max_loras if self.lora_config is not None else 0
556
+ with self.maybe_dummy_run_with_lora(
557
+ self.lora_config,
558
+ num_scheduled_tokens=np.array(num_tokens_per_request, dtype=np.int32),
559
+ num_sampled_tokens=None,
560
+ remove_lora=True,
561
+ num_active_loras=max_loras,
562
+ ):
563
+ # Execute the model.
564
+ self.execute_model(
565
+ dummy_scheduler_output,
566
+ intermediate_tensors=intermediate_tensors,
567
+ dummy_run=True,
568
+ skip_attn_for_dummy_run=skip_attn,
569
+ is_profile=is_profile,
570
+ )
571
self.kv_connector.set_disabled(False)
572
573
# Non-last PP ranks don't produce output for sampling.
@@ -694,6 +714,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
714
self.kv_cache_config,
715
has_lora=self.lora_config is not None,
716
use_aux_hidden_state_outputs=self.use_aux_hidden_state_outputs,
717
+ lora_capture_hook=create_lora_capture_hook(self.lora_config, self),
718
)
719
if self.speculator is not None:
720
self.speculator.capture(attn_states)
@@ -1105,6 +1126,13 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1126
max_query_len = max(scheduler_output.num_scheduled_tokens.values())
1127
uniform_tok_count = get_uniform_token_count(num_reqs, num_toks, max_query_len)
1128
1129
+ num_active_loras = 0
1130
+ if self.lora_config:
1131
+ req_ids = list(scheduler_output.num_scheduled_tokens.keys())
1132
+ num_active_loras = get_num_active_loras_for_dispatch(
1133
+ self.lora_config, self.lora_state, req_ids, dummy_run
1134
+ )
1135
+
1136
skip_compiled = False
1137
if self.is_encoder_decoder and scheduler_output.scheduled_encoder_inputs:
1138
# Encoder-decoder models such as Whisper should run eager/non-compiled
@@ -1120,6 +1148,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1148
self.dp_size,
1149
self.dp_rank,
1150
need_eager=is_profile or skip_compiled,
1151
+ num_active_loras=num_active_loras,
1152
)
1153
1154
if batch_desc.num_tokens == 0:
@@ -1157,31 +1186,6 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1186
)
1187
block_tables = None
1188
slot_mappings = None
1160
- if self.lora_config:
1161
- # program a no-LoRA mapping here so kernels early-exit instead of
1162
- # reading uninitialized metadata during dummy runs.
1163
- # FIXME: Replace this with LoRA warmup:
1164
- # https://github.com/vllm-project/vllm/pull/35536
1165
- assert hasattr(self, "lora_manager")
1166
- adapter_manager = self.lora_manager._adapter_manager
1167
- adapter_manager.set_adapter_mapping(
1168
- LoRAMapping(
1169
- index_mapping=(0,) * input_batch.num_tokens_after_padding,
1170
- prompt_mapping=(0,) * input_batch.num_reqs,
1171
- is_prefill=True,
1172
- )
1173
- )
1174
- seen_wrappers: set[int] = set()
1175
- for punica_wrapper in adapter_manager.punica_wrapper_mapping.values():
1176
- if id(punica_wrapper) in seen_wrappers:
1177
- continue
1178
- seen_wrappers.add(id(punica_wrapper))
1179
- for kernel_meta in (
1180
- punica_wrapper.token_mapping_meta, # type: ignore[attr-defined]
1181
- punica_wrapper.prompt_mapping_meta, # type: ignore[attr-defined]
1182
- ):
1183
- kernel_meta.no_lora_flag_cpu[0] = False
1184
- kernel_meta.num_active_loras_cpu[0] = 1
1189
1190
attn_metadata = None
1191
slot_mappings_by_layer = None
@@ -1258,6 +1262,7 @@ class GPUModelRunner(LoRAModelRunnerMixin):
1262
batch_descriptor = BatchDescriptor(
1263
num_tokens=input_batch.num_tokens_after_padding,
1264
has_lora=self.lora_config is not None,
1265
+ num_active_loras=batch_desc.num_active_loras,
1266
)
1267
1268
with set_forward_context(