A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 13, 13:15 UTC
[Metrics] Refactor LoRA state tracking (#26801)
Signed-off-by: Mark McLoughlin <markmc@redhat.com>
Mark McLoughlin committed
Nov 10, 2025 at 08:34 UTC
6f7de33bed412869bec4631add885e5ff88c22cf
7 files changed
+268
-106
tests/v1/engine/test_output_processor.py
+173
-2
@@ -15,12 +15,19 @@ from tests.v1.engine.utils import (
15
)
16
from vllm import PoolingParams
17
from vllm.logprobs import PromptLogprobs, SampleLogprobs
18
+from vllm.lora.request import LoRARequest
19
from vllm.outputs import CompletionOutput, RequestOutput
20
from vllm.sampling_params import RequestOutputKind, SamplingParams
21
from vllm.transformers_utils.tokenizer import AnyTokenizer
21
-from vllm.v1.engine import EngineCoreRequest
22
+from vllm.v1.engine import (
23
+ EngineCoreEvent,
24
+ EngineCoreEventType,
25
+ EngineCoreOutputs,
26
+ EngineCoreRequest,
27
+ FinishReason,
28
+)
29
from vllm.v1.engine.output_processor import OutputProcessor, RequestOutputCollector
23
-from vllm.v1.metrics.stats import IterationStats
30
+from vllm.v1.metrics.stats import IterationStats, SchedulerStats
31
32
33
def _ref_convert_id_to_token(
@@ -895,6 +902,170 @@ def test_iteration_stats(dummy_test_vectors):
902
assert iteration_stats.num_generation_tokens == num_active
903
904
905
+@pytest.mark.parametrize("log_stats", [True, False])
906
+def test_lora_request_tracking(log_stats: bool, dummy_test_vectors):
907
+ """Test LoRA request lifecycle tracking through waiting -> running -> finished."""
908
+ output_processor = OutputProcessor(
909
+ dummy_test_vectors.tokenizer, log_stats=log_stats
910
+ )
911
+ engine_core = MockEngineCore(dummy_test_vectors.generation_tokens)
912
+ engine_core_timestamp = time.monotonic()
913
+
914
+ # Create LoRA requests
915
+ lora1 = LoRARequest(lora_name="lora-1", lora_int_id=1, lora_path="/path/to/lora1")
916
+ lora2 = LoRARequest(lora_name="lora-2", lora_int_id=2, lora_path="/path/to/lora2")
917
+
918
+ # Create requests with different LoRA adapters:
919
+ # - request-0: lora-1
920
+ # - request-1: lora-2
921
+ # - request-2: None (no LoRA)
922
+ lora_assignments = [lora1, lora2, None]
923
+ requests = [
924
+ EngineCoreRequest(
925
+ request_id=f"request-{idx}",
926
+ prompt_token_ids=prompt_tokens,
927
+ mm_features=None,
928
+ eos_token_id=None,
929
+ arrival_time=0,
930
+ lora_request=lora_assignments[idx],
931
+ cache_salt=None,
932
+ data_parallel_rank=None,
933
+ sampling_params=SamplingParams(),
934
+ pooling_params=None,
935
+ )
936
+ for idx, prompt_tokens in enumerate(dummy_test_vectors.prompt_tokens)
937
+ ]
938
+
939
+ # Add all requests to the OutputProcessor
940
+ for request in requests:
941
+ output_processor.add_request(request, None)
942
+
943
+ # First iteration: process outputs with QUEUED events
944
+ outputs = EngineCoreOutputs(
945
+ outputs=engine_core.get_outputs(), scheduler_stats=SchedulerStats()
946
+ )
947
+ for output in outputs.outputs:
948
+ output.events = [
949
+ EngineCoreEvent.new_event(EngineCoreEventType.QUEUED, engine_core_timestamp)
950
+ ]
951
+
952
+ iteration_stats = IterationStats() if log_stats else None
953
+ output_processor.process_outputs(
954
+ outputs.outputs, engine_core_timestamp, iteration_stats
955
+ )
956
+ output_processor.update_scheduler_stats(outputs.scheduler_stats)
957
+
958
+ if log_stats:
959
+ # Verify waiting counts
960
+ assert outputs.scheduler_stats.waiting_lora_adapters.get("lora-1") == 1
961
+ assert outputs.scheduler_stats.waiting_lora_adapters.get("lora-2") == 1
962
+ assert outputs.scheduler_stats.running_lora_adapters.get("lora-1") == 0
963
+ assert outputs.scheduler_stats.running_lora_adapters.get("lora-2") == 0
964
+ # Verify internal state
965
+ assert len(output_processor.lora_states.requests) == 2
966
+ assert "lora-1" in output_processor.lora_states.requests
967
+ assert "lora-2" in output_processor.lora_states.requests
968
+ else:
969
+ # When log_stats=False, no tracking should occur
970
+ assert iteration_stats is None
971
+ assert len(output_processor.lora_states.requests) == 0
972
+
973
+ # Second iteration: process outputs with SCHEDULED events
974
+ outputs = EngineCoreOutputs(
975
+ outputs=engine_core.get_outputs(), scheduler_stats=SchedulerStats()
976
+ )
977
+ for output in outputs.outputs:
978
+ output.events = [
979
+ EngineCoreEvent.new_event(
980
+ EngineCoreEventType.SCHEDULED, engine_core_timestamp
981
+ )
982
+ ]
983
+
984
+ iteration_stats = IterationStats() if log_stats else None
985
+ output_processor.process_outputs(
986
+ outputs.outputs, engine_core_timestamp, iteration_stats
987
+ )
988
+ output_processor.update_scheduler_stats(outputs.scheduler_stats)
989
+
990
+ if log_stats:
991
+ # Verify running counts
992
+ assert outputs.scheduler_stats.waiting_lora_adapters.get("lora-1") == 0
993
+ assert outputs.scheduler_stats.waiting_lora_adapters.get("lora-2") == 0
994
+ assert outputs.scheduler_stats.running_lora_adapters.get("lora-1") == 1
995
+ assert outputs.scheduler_stats.running_lora_adapters.get("lora-2") == 1
996
+ else:
997
+ assert iteration_stats is None
998
+ assert len(output_processor.lora_states.requests) == 0
999
+
1000
+ # Third iteration: finish request-0 (lora-1)
1001
+ outputs = EngineCoreOutputs(
1002
+ outputs=engine_core.get_outputs(), scheduler_stats=SchedulerStats()
1003
+ )
1004
+ # Find and mark request-0 as finished (it uses lora-1)
1005
+ for output in outputs.outputs:
1006
+ if output.request_id == "request-0":
1007
+ output.finish_reason = FinishReason.LENGTH
1008
+ break
1009
+
1010
+ iteration_stats = IterationStats() if log_stats else None
1011
+ output_processor.process_outputs(
1012
+ outputs.outputs, engine_core_timestamp, iteration_stats
1013
+ )
1014
+ output_processor.update_scheduler_stats(outputs.scheduler_stats)
1015
+
1016
+ if log_stats:
1017
+ # lora-1 should be removed since no requests remain
1018
+ assert "lora-1" not in output_processor.lora_states.requests
1019
+ # lora-2 should still be running
1020
+ assert outputs.scheduler_stats.running_lora_adapters.get("lora-2") == 1
1021
+ assert len(output_processor.lora_states.requests) == 1
1022
+ else:
1023
+ assert len(output_processor.lora_states.requests) == 0
1024
+
1025
+ # Fourth iteration: finish request-1 (lora-2)
1026
+ outputs = EngineCoreOutputs(
1027
+ outputs=engine_core.get_outputs(), scheduler_stats=SchedulerStats()
1028
+ )
1029
+ # Find and mark request-1 as finished (it uses lora-2)
1030
+ for output in outputs.outputs:
1031
+ if output.request_id == "request-1":
1032
+ output.finish_reason = FinishReason.LENGTH
1033
+ break
1034
+
1035
+ iteration_stats = IterationStats() if log_stats else None
1036
+ output_processor.process_outputs(
1037
+ outputs.outputs, engine_core_timestamp, iteration_stats
1038
+ )
1039
+ output_processor.update_scheduler_stats(outputs.scheduler_stats)
1040
+
1041
+ if log_stats:
1042
+ # lora-2 should be removed since no requests remain
1043
+ assert "lora-2" not in output_processor.lora_states.requests
1044
+ assert len(outputs.scheduler_stats.running_lora_adapters) == 0
1045
+ assert len(output_processor.lora_states.requests) == 0
1046
+ else:
1047
+ assert len(output_processor.lora_states.requests) == 0
1048
+
1049
+ # Finish the last request (no LoRA)
1050
+ outputs = EngineCoreOutputs(
1051
+ outputs=engine_core.get_outputs(), scheduler_stats=SchedulerStats()
1052
+ )
1053
+ # Find and mark request-2 as finished (it has no LoRA)
1054
+ for output in outputs.outputs:
1055
+ if output.request_id == "request-2":
1056
+ output.finish_reason = FinishReason.LENGTH
1057
+ break
1058
+
1059
+ iteration_stats = IterationStats() if log_stats else None
1060
+ output_processor.process_outputs(
1061
+ outputs.outputs, engine_core_timestamp, iteration_stats
1062
+ )
1063
+ output_processor.update_scheduler_stats(outputs.scheduler_stats)
1064
+
1065
+ # Verify all requests are finished
1066
+ assert output_processor.get_num_unfinished_requests() == 0
1067
+
1068
+
1069
@pytest.mark.asyncio
1070
async def test_request_output_collector():
1071
NUM_REQS = 3
tests/v1/metrics/test_stats.py
+1
-17
@@ -5,20 +5,4 @@ from vllm.v1.metrics.stats import IterationStats
5
6
def test_iteration_stats_repr():
7
iteration_stats = IterationStats()
8
- iteration_stats.iteration_timestamp = 0
9
- expected_repr = (
10
- "IterationStats("
11
- "iteration_timestamp=0, "
12
- "num_generation_tokens=0, "
13
- "num_prompt_tokens=0, "
14
- "num_preempted_reqs=0, "
15
- "finished_requests=[], "
16
- "max_num_generation_tokens_iter=[], "
17
- "n_params_iter=[], "
18
- "time_to_first_tokens_iter=[], "
19
- "inter_token_latencies_iter=[], "
20
- "waiting_lora_adapters={}, "
21
- "running_lora_adapters={}, "
22
- "num_corrupted_reqs=0)"
23
- )
24
- assert repr(iteration_stats) == expected_repr
8
+ assert repr(iteration_stats).startswith("IterationStats(")
vllm/v1/engine/async_llm.py
+2
@@ -508,6 +508,8 @@ class AsyncLLM(EngineClient):
508
processed_outputs.reqs_to_abort
509
)
510
511
+ output_processor.update_scheduler_stats(outputs.scheduler_stats)
512
+
513
# 4) Logging.
514
# TODO(rob): make into a coroutine and launch it in
515
# background thread once Prometheus overhead is non-trivial.
vllm/v1/engine/llm_engine.py
+1
@@ -289,6 +289,7 @@ class LLMEngine:
289
engine_core_timestamp=outputs.timestamp,
290
iteration_stats=iteration_stats,
291
)
292
+ self.output_processor.update_scheduler_stats(outputs.scheduler_stats)
293
294
# 3) Abort any reqs that finished due to stop strings.
295
self.engine_core.abort_requests(processed_outputs.reqs_to_abort)
vllm/v1/engine/output_processor.py
+14
-9
@@ -22,7 +22,12 @@ from vllm.v1.engine import EngineCoreOutput, EngineCoreRequest, FinishReason
22
from vllm.v1.engine.detokenizer import IncrementalDetokenizer
23
from vllm.v1.engine.logprobs import LogprobsProcessor
24
from vllm.v1.engine.parallel_sampling import ParentRequest
25
-from vllm.v1.metrics.stats import IterationStats, LoRARequestStates, RequestStateStats
25
+from vllm.v1.metrics.stats import (
26
+ IterationStats,
27
+ LoRARequestStates,
28
+ RequestStateStats,
29
+ SchedulerStats,
30
+)
31
32
33
class RequestOutputCollector:
@@ -310,7 +315,7 @@ class OutputProcessor:
315
self.tokenizer = tokenizer
316
self.request_states: dict[str, RequestState] = {}
317
self.parent_requests: dict[str, ParentRequest] = {}
313
- self.lora_states = LoRARequestStates()
318
+ self.lora_states = LoRARequestStates(log_stats)
319
self.tracer: Tracer | None = None
320
321
def get_num_unfinished_requests(self):
@@ -334,7 +339,7 @@ class OutputProcessor:
339
for request_id in request_ids:
340
req_state = self.request_states.pop(request_id, None)
341
if req_state is not None:
337
- self.lora_states.abort_request(req_state)
342
+ self.lora_states.request_finished(request_id, req_state.lora_name)
343
request_ids_to_abort.append(request_id)
344
# Produce final abort output.
345
if req_state.queue is not None and (
@@ -382,7 +387,6 @@ class OutputProcessor:
387
log_stats=self.log_stats,
388
)
389
self.request_states[request_id] = req_state
385
- self.lora_states.add_request(req_state)
390
if parent_req:
391
self.parent_requests[parent_req.request_id] = parent_req
392
@@ -484,13 +488,15 @@ class OutputProcessor:
488
)
489
if self.tracer:
490
self.do_tracing(engine_core_output, req_state, iteration_stats)
487
- self.lora_states.update_iteration_stats(iteration_stats)
491
492
return OutputProcessorOutput(
493
request_outputs=request_outputs,
494
reqs_to_abort=reqs_to_abort,
495
)
496
497
+ def update_scheduler_stats(self, scheduler_stats: SchedulerStats | None):
498
+ self.lora_states.update_scheduler_stats(scheduler_stats)
499
+
500
def do_tracing(
501
self,
502
engine_core_output: EngineCoreOutput,
@@ -564,8 +570,6 @@ class OutputProcessor:
570
if iteration_stats is None:
571
return
572
567
- lora_stats = self.lora_states.get_stats(req_state)
568
-
573
assert engine_core_timestamp is not None
574
assert req_state.stats is not None
575
iteration_stats.update_from_output(
@@ -574,7 +578,8 @@ class OutputProcessor:
578
req_state.is_prefilling,
579
req_state.prompt_len,
580
req_state.stats,
577
- lora_stats,
581
+ self.lora_states,
582
+ req_state.lora_name,
583
)
584
585
def _update_stats_from_finished(
@@ -596,7 +601,7 @@ class OutputProcessor:
601
max_tokens_param=req_state.max_tokens_param,
602
req_stats=req_state.stats,
603
)
599
- self.lora_states.finish_request(req_state)
604
+ self.lora_states.request_finished(req_state.request_id, req_state.lora_name)
605
606
ParentRequest.observe_finished_request(
607
req_state.parent_req, iteration_stats, req_state.stats.num_generation_tokens
vllm/v1/metrics/loggers.py
+14
-14
@@ -989,6 +989,20 @@ class PrometheusStatLogger(AggregateStatLoggerBase):
989
scheduler_stats.kv_connector_stats, engine_idx
990
)
991
992
+ if self.gauge_lora_info is not None:
993
+ running_lora_adapters = ",".join(
994
+ scheduler_stats.running_lora_adapters.keys()
995
+ )
996
+ waiting_lora_adapters = ",".join(
997
+ scheduler_stats.waiting_lora_adapters.keys()
998
+ )
999
+ lora_info_labels = {
1000
+ self.labelname_running_lora_adapters: running_lora_adapters,
1001
+ self.labelname_waiting_lora_adapters: waiting_lora_adapters,
1002
+ self.labelname_max_lora: self.max_lora,
1003
+ }
1004
+ self.gauge_lora_info.labels(**lora_info_labels).set_to_current_time()
1005
+
1006
if mm_cache_stats is not None:
1007
self.counter_mm_cache_queries[engine_idx].inc(mm_cache_stats.queries)
1008
self.counter_mm_cache_hits[engine_idx].inc(mm_cache_stats.hits)
@@ -1055,20 +1069,6 @@ class PrometheusStatLogger(AggregateStatLoggerBase):
1069
finished_request.max_tokens_param
1070
)
1071
1058
- if self.gauge_lora_info is not None:
1059
- running_lora_adapters = ",".join(
1060
- iteration_stats.running_lora_adapters.keys()
1061
- )
1062
- waiting_lora_adapters = ",".join(
1063
- iteration_stats.waiting_lora_adapters.keys()
1064
- )
1065
- lora_info_labels = {
1066
- self.labelname_running_lora_adapters: running_lora_adapters,
1067
- self.labelname_waiting_lora_adapters: waiting_lora_adapters,
1068
- self.labelname_max_lora: self.max_lora,
1069
- }
1070
- self.gauge_lora_info.labels(**lora_info_labels).set_to_current_time()
1071
-
1072
def record_sleep_state(self, sleep: int = 0, level: int = 0):
1073
awake = 1
1074
discard_all = 0
vllm/v1/metrics/stats.py
+63
-64
@@ -2,7 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import time
5
-from collections import deque
5
+from collections import defaultdict, deque
6
from dataclasses import dataclass, field
7
from typing import TYPE_CHECKING, Any
8
@@ -11,7 +11,6 @@ from vllm.v1.spec_decode.metrics import SpecDecodingStats
11
12
if TYPE_CHECKING:
13
from vllm.v1.engine import EngineCoreEvent, EngineCoreOutput, FinishReason
14
- from vllm.v1.engine.output_processor import RequestState
14
15
16
@dataclass
@@ -170,11 +169,8 @@ class SchedulerStats:
169
spec_decoding_stats: SpecDecodingStats | None = None
170
kv_connector_stats: dict[str, Any] | None = None
171
173
-
174
-@dataclass
175
-class LoRAStats:
176
- waiting_requests: set[str] = field(default_factory=set)
177
- running_requests: set[str] = field(default_factory=set)
172
+ waiting_lora_adapters: dict[str, int] = field(default_factory=dict)
173
+ running_lora_adapters: dict[str, int] = field(default_factory=dict)
174
175
176
@dataclass
@@ -229,8 +225,6 @@ class IterationStats:
225
self.n_params_iter: list[int] = []
226
self.time_to_first_tokens_iter: list[float] = []
227
self.inter_token_latencies_iter: list[float] = []
232
- self.waiting_lora_adapters: dict[str, int] = {}
233
- self.running_lora_adapters: dict[str, int] = {}
228
self.num_corrupted_reqs: int = 0
229
230
def __repr__(self) -> str:
@@ -248,7 +242,8 @@ class IterationStats:
242
is_prefilling: bool,
243
prompt_len: int,
244
req_stats: RequestStateStats,
251
- lora_stats: LoRAStats | None,
245
+ lora_states: "LoRARequestStates",
246
+ lora_name: str | None,
247
):
248
num_new_generation_tokens = len(output.new_token_ids)
249
@@ -274,7 +269,12 @@ class IterationStats:
269
# Process request-level engine core events
270
if output.events is not None:
271
self.update_from_events(
277
- output.request_id, output.events, is_prefilling, req_stats, lora_stats
272
+ output.request_id,
273
+ output.events,
274
+ is_prefilling,
275
+ req_stats,
276
+ lora_states,
277
+ lora_name,
278
)
279
280
# Process the batch-level "new tokens" engine core event
@@ -292,7 +292,8 @@ class IterationStats:
292
events: list["EngineCoreEvent"],
293
is_prefilling: bool,
294
req_stats: RequestStateStats,
295
- lora_stats: LoRAStats | None,
295
+ lora_states: "LoRARequestStates",
296
+ lora_name: str | None,
297
):
298
# Avoid circular dependency
299
from vllm.v1.engine import EngineCoreEventType
@@ -300,15 +301,14 @@ class IterationStats:
301
for event in events:
302
if event.type == EngineCoreEventType.QUEUED:
303
req_stats.queued_ts = event.timestamp
303
- if lora_stats is not None:
304
- lora_stats.waiting_requests.add(req_id)
304
+ lora_states.request_waiting(req_id, lora_name)
305
elif event.type == EngineCoreEventType.SCHEDULED:
306
if req_stats.scheduled_ts == 0.0: # ignore preemptions
307
req_stats.scheduled_ts = event.timestamp
308
- LoRARequestStates.scheduled_request(lora_stats, req_id)
308
+ lora_states.request_running(req_id, lora_name)
309
elif event.type == EngineCoreEventType.PREEMPTED:
310
self.num_preempted_reqs += 1
311
- LoRARequestStates.preempted_request(lora_stats, req_id)
311
+ lora_states.request_waiting(req_id, lora_name)
312
313
def update_from_finished_request(
314
self,
@@ -361,61 +361,60 @@ class IterationStats:
361
self.num_corrupted_reqs += 1
362
363
364
-class LoRARequestStates:
365
- """Per-LoRA request state stats."""
364
+class LoRAStats:
365
+ """Tracks waiting and running request IDs for a single LoRA."""
366
367
def __init__(self):
368
- self.lora_name_to_stats: dict[str, LoRAStats] = {}
368
+ self.waiting: set[str] = set()
369
+ self.running: set[str] = set()
370
370
- def get_stats(self, req_state: "RequestState") -> LoRAStats | None:
371
- if req_state.lora_name is None:
372
- return None
373
- if req_state.lora_name not in self.lora_name_to_stats:
374
- self.lora_name_to_stats[req_state.lora_name] = LoRAStats()
375
- return self.lora_name_to_stats[req_state.lora_name]
371
+ def update(self, req_id: str, waiting: bool, running: bool):
372
+ assert not (waiting and running)
373
+ if waiting:
374
+ self.waiting.add(req_id)
375
+ else:
376
+ self.waiting.discard(req_id)
377
377
- def add_request(self, req_state: "RequestState"):
378
- if (lora_stats := self.get_stats(req_state)) is not None:
379
- lora_stats.waiting_requests.add(req_state.request_id)
378
+ if running:
379
+ self.running.add(req_id)
380
+ else:
381
+ self.running.discard(req_id)
382
381
- def finish_request(self, req_state: "RequestState"):
382
- if req_state.lora_name is None:
383
- return
384
- lora_stats = self.lora_name_to_stats[req_state.lora_name]
385
- lora_stats.running_requests.remove(req_state.request_id)
383
+ @property
384
+ def empty(self) -> bool:
385
+ return not (self.waiting or self.running)
386
387
- def abort_request(self, req_state: "RequestState"):
388
- if req_state.lora_name is None:
389
- return
390
- lora_stats = self.lora_name_to_stats[req_state.lora_name]
391
- lora_stats.waiting_requests.discard(req_state.request_id)
392
- lora_stats.running_requests.discard(req_state.request_id)
393
-
394
- # Break the pattern for this lifecycle methods so we can
395
- # call this from IterationStats.update_from_events()
396
- @staticmethod
397
- def scheduled_request(lora_stats: LoRAStats | None, request_id: str):
398
- if lora_stats is None:
399
- return
400
- lora_stats.waiting_requests.remove(request_id)
401
- lora_stats.running_requests.add(request_id)
387
403
- @staticmethod
404
- def preempted_request(lora_stats: LoRAStats | None, request_id: str):
405
- if lora_stats is None:
388
+class LoRARequestStates:
389
+ """A per-LoRA count of running and waiting requests."""
390
+
391
+ def __init__(self, log_stats: bool = False):
392
+ self.log_stats = log_stats
393
+ self.requests: defaultdict[str, LoRAStats] = defaultdict(LoRAStats)
394
+
395
+ def _request_update(
396
+ self, req_id: str, lora_name: str | None, waiting: bool, running: bool
397
+ ):
398
+ if not self.log_stats or lora_name is None:
399
return
407
- lora_stats.running_requests.remove(request_id)
408
- lora_stats.waiting_requests.add(request_id)
400
410
- def update_iteration_stats(self, iteration_stats: IterationStats | None):
411
- if iteration_stats is None:
401
+ lora_stats = self.requests[lora_name]
402
+ lora_stats.update(req_id, waiting, running)
403
+ if lora_stats.empty:
404
+ del self.requests[lora_name]
405
+
406
+ def request_waiting(self, req_id: str, lora_name: str | None):
407
+ self._request_update(req_id, lora_name, waiting=True, running=False)
408
+
409
+ def request_running(self, req_id: str, lora_name: str | None):
410
+ self._request_update(req_id, lora_name, waiting=False, running=True)
411
+
412
+ def request_finished(self, req_id: str, lora_name: str | None):
413
+ self._request_update(req_id, lora_name, waiting=False, running=False)
414
+
415
+ def update_scheduler_stats(self, scheduler_stats: SchedulerStats | None):
416
+ if not self.log_stats or scheduler_stats is None:
417
return
413
- for lora_name, stats in self.lora_name_to_stats.items():
414
- if stats.waiting_requests:
415
- iteration_stats.waiting_lora_adapters[lora_name] = len(
416
- stats.waiting_requests
417
- )
418
- if stats.running_requests:
419
- iteration_stats.running_lora_adapters[lora_name] = len(
420
- stats.running_requests
421
- )
418
+ for lora_name, stats in self.requests.items():
419
+ scheduler_stats.waiting_lora_adapters[lora_name] = len(stats.waiting)
420
+ scheduler_stats.running_lora_adapters[lora_name] = len(stats.running)