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 14, 05:30 UTC
[Misc] Have AsyncLLM `custom_stat_loggers` extend default logger list (#20952)
Signed-off-by: Seiji Eicher <seiji@anyscale.com> Signed-off-by: Seiji Eicher <58963096+eicherseiji@users.noreply.github.com> Co-authored-by: Nick Hill <nhill@redhat.com>
Seiji Eicher committed
Sep 4, 2025 at 14:25 UTC
60b755cbcb1154e519572410f60d0d258eecbce4
4 files changed
+99
-9
tests/v1/engine/test_async_llm.py
+3
-2
@@ -393,7 +393,7 @@ class MockLoggingStatLogger(LoggingStatLogger):
393
async def test_customize_loggers(monkeypatch):
394
"""Test that we can customize the loggers.
395
If a customized logger is provided at the init, it should
396
- be used directly.
396
+ be added to the default loggers.
397
"""
398
399
with monkeypatch.context() as m, ExitStack() as after:
@@ -410,7 +410,8 @@ async def test_customize_loggers(monkeypatch):
410
411
stat_loggers = engine.logger_manager.per_engine_logger_dict
412
assert len(stat_loggers) == 1
413
- assert len(stat_loggers[0]) == 1
413
+ assert len(
414
+ stat_loggers[0]) == 2 # LoggingStatLogger + MockLoggingStatLogger
415
stat_loggers[0][0].log.assert_called_once()
416
417
tests/v1/metrics/test_engine_logger_apis.py
new
+83
@@ -0,0 +1,83 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import copy
4
+
5
+import pytest
6
+
7
+from vllm.v1.engine.async_llm import AsyncEngineArgs, AsyncLLM
8
+from vllm.v1.metrics.ray_wrappers import RayPrometheusStatLogger
9
+
10
+
11
+class DummyStatLogger:
12
+ """
13
+ A dummy stat logger for testing purposes.
14
+ Implements the minimal interface expected by StatLoggerManager.
15
+ """
16
+
17
+ def __init__(self, vllm_config, engine_idx):
18
+ self.vllm_config = vllm_config
19
+ self.engine_idx = engine_idx
20
+ self.recorded = []
21
+ self.logged = False
22
+ self.engine_initialized = False
23
+
24
+ def record(self, scheduler_stats, iteration_stats, engine_idx):
25
+ self.recorded.append((scheduler_stats, iteration_stats, engine_idx))
26
+
27
+ def log(self):
28
+ self.logged = True
29
+
30
+ def log_engine_initialized(self):
31
+ self.engine_initialized = True
32
+
33
+
34
+@pytest.fixture
35
+def log_stats_enabled_engine_args():
36
+ """
37
+ Shared fixture providing common AsyncEngineArgs configuration
38
+ used across multiple tests.
39
+ """
40
+ return AsyncEngineArgs(
41
+ model="distilbert/distilgpt2",
42
+ dtype="half",
43
+ disable_log_stats=False,
44
+ enforce_eager=True,
45
+ )
46
+
47
+
48
+@pytest.mark.asyncio
49
+async def test_async_llm_replace_default_loggers(
50
+ log_stats_enabled_engine_args):
51
+ """
52
+ RayPrometheusStatLogger should replace the default PrometheusStatLogger
53
+ """
54
+
55
+ engine = AsyncLLM.from_engine_args(log_stats_enabled_engine_args,
56
+ stat_loggers=[RayPrometheusStatLogger])
57
+ assert isinstance(engine.logger_manager.prometheus_logger,
58
+ RayPrometheusStatLogger)
59
+ engine.shutdown()
60
+
61
+
62
+@pytest.mark.asyncio
63
+async def test_async_llm_add_to_default_loggers(log_stats_enabled_engine_args):
64
+ """
65
+ It's still possible to use custom stat loggers exclusively by passing
66
+ disable_log_stats=True in addition to a list of custom stat loggers.
67
+ """
68
+ # Create engine_args with disable_log_stats=True for this test
69
+ disabled_log_engine_args = copy.deepcopy(log_stats_enabled_engine_args)
70
+ disabled_log_engine_args.disable_log_stats = True
71
+
72
+ # Disable default loggers; pass custom stat logger to the constructor
73
+ engine = AsyncLLM.from_engine_args(disabled_log_engine_args,
74
+ stat_loggers=[DummyStatLogger])
75
+
76
+ assert len(engine.logger_manager.per_engine_logger_dict[0]) == 1
77
+ assert isinstance(engine.logger_manager.per_engine_logger_dict[0][0],
78
+ DummyStatLogger)
79
+
80
+ # log_stats is still True, since custom stat loggers are used
81
+ assert engine.log_stats
82
+
83
+ engine.shutdown()
vllm/v1/engine/async_llm.py
+7
-1
@@ -98,7 +98,12 @@ class AsyncLLM(EngineClient):
98
self.model_config = vllm_config.model_config
99
self.vllm_config = vllm_config
100
self.log_requests = log_requests
101
- self.log_stats = log_stats
101
+
102
+ self.log_stats = log_stats or (stat_loggers is not None)
103
+ if not log_stats and stat_loggers is not None:
104
+ logger.info(
105
+ "AsyncLLM created with log_stats=False and non-empty custom "
106
+ "logger list; enabling logging without default stat loggers")
107
108
if self.model_config.skip_tokenizer_init:
109
self.tokenizer = None
@@ -137,6 +142,7 @@ class AsyncLLM(EngineClient):
142
vllm_config=vllm_config,
143
engine_idxs=self.engine_core.engine_ranks_managed,
144
custom_stat_loggers=stat_loggers,
145
+ enable_default_loggers=log_stats,
146
)
147
self.logger_manager.log_engine_initialized()
148
vllm/v1/metrics/loggers.py
+6
-6
@@ -651,16 +651,16 @@ class StatLoggerManager:
651
vllm_config: VllmConfig,
652
engine_idxs: Optional[list[int]] = None,
653
custom_stat_loggers: Optional[list[StatLoggerFactory]] = None,
654
+ enable_default_loggers: bool = True,
655
):
656
self.engine_idxs = engine_idxs if engine_idxs else [0]
657
657
- factories: list[StatLoggerFactory]
658
+ factories: list[StatLoggerFactory] = []
659
if custom_stat_loggers is not None:
659
- factories = custom_stat_loggers
660
- else:
661
- factories = []
662
- if logger.isEnabledFor(logging.INFO):
663
- factories.append(LoggingStatLogger)
660
+ factories.extend(custom_stat_loggers)
661
+
662
+ if enable_default_loggers and logger.isEnabledFor(logging.INFO):
663
+ factories.append(LoggingStatLogger)
664
665
# engine_idx: StatLogger
666
self.per_engine_logger_dict: dict[int, list[StatLoggerBase]] = {}