Skip to content

Commit 4119531

Browse files
committed
Allow v1 async llm engine to use custom logger
Signed-off-by: Zijing Liu <liuzijing2014@gmail.com>
1 parent 53be4a8 commit 4119531

File tree

4 files changed

+92
-11
lines changed

4 files changed

+92
-11
lines changed

tests/v1/engine/test_async_llm.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import asyncio
44
from contextlib import ExitStack
55
from typing import Optional
6+
from unittest.mock import MagicMock
67

78
import pytest
89

@@ -13,11 +14,16 @@
1314
from vllm.platforms import current_platform
1415
from vllm.sampling_params import RequestOutputKind
1516
from vllm.v1.engine.async_llm import AsyncLLM
17+
from vllm.v1.metrics.loggers import (PROMETHEUS_LOGGING_LOGGER_NAME,
18+
STANDARD_LOGGING_LOGGER_NAME,
19+
LoggingStatLogger, StatLoggerBase)
1620

1721
if not current_platform.is_cuda():
1822
pytest.skip(reason="V1 currently only supported on CUDA.",
1923
allow_module_level=True)
2024

25+
TEST_LOGGER_NAME = "test_logger"
26+
2127
TEXT_ENGINE_ARGS = AsyncEngineArgs(model="meta-llama/Llama-3.2-1B-Instruct",
2228
enforce_eager=True,
2329
disable_log_requests=True)
@@ -220,3 +226,47 @@ async def test_finished_flag(monkeypatch, n: int,
220226
# Assert only the last output has the finished flag set
221227
assert all(not out.finished for out in outputs[:-1])
222228
assert outputs[-1].finished
229+
230+
231+
def get_customized_logger_mock() -> StatLoggerBase:
232+
logger = LoggingStatLogger()
233+
logger.log = MagicMock()
234+
return logger
235+
236+
237+
@pytest.mark.parametrize(
238+
"loggers",
239+
[{
240+
TEST_LOGGER_NAME: get_customized_logger_mock()
241+
}, None],
242+
)
243+
@pytest.mark.asyncio
244+
async def test_customize_loggers(
245+
monkeypatch,
246+
loggers: Optional[dict[str, StatLoggerBase]],
247+
):
248+
"""Test that we can customize the loggers.
249+
Test case #1: Not customized logger is provided at the init, default loggers
250+
would be initialized. Thus, we should be able to remove those and add a
251+
customized one later.
252+
Test case #2: If a customized logger is provided at the init, it should
253+
be used directly.
254+
"""
255+
256+
with monkeypatch.context() as m, ExitStack() as after:
257+
m.setenv("VLLM_USE_V1", "1")
258+
259+
engine = AsyncLLM.from_engine_args(
260+
TEXT_ENGINE_ARGS,
261+
stat_loggers=loggers,
262+
)
263+
after.callback(engine.shutdown)
264+
265+
if loggers is None:
266+
engine.remove_logger(PROMETHEUS_LOGGING_LOGGER_NAME)
267+
engine.remove_logger(STANDARD_LOGGING_LOGGER_NAME)
268+
engine.add_logger(TEST_LOGGER_NAME, get_customized_logger_mock())
269+
270+
await engine.do_log_stats()
271+
for logger in engine.stat_loggers.values():
272+
logger.log.assert_called_once()

vllm/third_party/pynvml.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@
3030
# THE POSSIBILITY OF SUCH DAMAGE.
3131
#####
3232

33+
import os
34+
import string
35+
import sys
36+
import threading
3337
##
3438
# Python bindings for the NVML library
3539
##
3640
from ctypes import *
3741
from ctypes.util import find_library
3842
from functools import wraps
39-
import sys
40-
import os
41-
import threading
42-
import string
4343

4444
## C Type mappings ##
4545
## Enums

vllm/v1/engine/async_llm.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
from vllm.v1.engine.parallel_sampling import ParentRequest
3030
from vllm.v1.engine.processor import Processor
3131
from vllm.v1.executor.abstract import Executor
32-
from vllm.v1.metrics.loggers import (LoggingStatLogger, PrometheusStatLogger,
32+
from vllm.v1.metrics.loggers import (PROMETHEUS_LOGGING_LOGGER_NAME,
33+
STANDARD_LOGGING_LOGGER_NAME,
34+
LoggingStatLogger, PrometheusStatLogger,
3335
StatLoggerBase)
3436
from vllm.v1.metrics.stats import IterationStats, SchedulerStats
3537

@@ -48,6 +50,7 @@ def __init__(
4850
use_cached_outputs: bool = False,
4951
log_requests: bool = True,
5052
start_engine_loop: bool = True,
53+
stat_loggers: Optional[dict[str, StatLoggerBase]] = None,
5154
) -> None:
5255

5356
assert start_engine_loop
@@ -56,11 +59,16 @@ def __init__(
5659

5760
self.log_requests = log_requests
5861
self.log_stats = log_stats
59-
self.stat_loggers: list[StatLoggerBase] = []
62+
self.stat_loggers: dict[str, StatLoggerBase] = dict()
6063
if self.log_stats:
61-
if logger.isEnabledFor(logging.INFO):
62-
self.stat_loggers.append(LoggingStatLogger())
63-
self.stat_loggers.append(PrometheusStatLogger(vllm_config))
64+
if stat_loggers is not None:
65+
self.stat_loggers = stat_loggers
66+
else:
67+
if logger.isEnabledFor(logging.INFO):
68+
self.stat_loggers[STANDARD_LOGGING_LOGGER_NAME] = (
69+
LoggingStatLogger())
70+
self.stat_loggers[PROMETHEUS_LOGGING_LOGGER_NAME] = (
71+
PrometheusStatLogger(vllm_config))
6472

6573
# Tokenizer (+ ensure liveness if running in another process).
6674
self.tokenizer = init_tokenizer_from_configs(
@@ -99,6 +107,7 @@ def from_engine_args(
99107
engine_config: Optional[VllmConfig] = None,
100108
start_engine_loop: bool = True,
101109
usage_context: UsageContext = UsageContext.ENGINE_CONTEXT,
110+
stat_loggers: Optional[dict[str, StatLoggerBase]] = None,
102111
) -> "AsyncLLM":
103112
"""Create an AsyncLLM from the EngineArgs."""
104113

@@ -118,6 +127,7 @@ def from_engine_args(
118127
log_stats=not engine_args.disable_log_stats,
119128
start_engine_loop=start_engine_loop,
120129
usage_context=usage_context,
130+
stat_loggers=stat_loggers,
121131
)
122132

123133
def shutdown(self):
@@ -313,7 +323,7 @@ def _record_stats(
313323
return
314324

315325
assert scheduler_stats is not None
316-
for stat_logger in self.stat_loggers:
326+
for stat_logger in self.stat_loggers.values():
317327
stat_logger.record(scheduler_stats=scheduler_stats,
318328
iteration_stats=iteration_stats)
319329

@@ -351,9 +361,27 @@ async def do_log_stats(
351361
scheduler_outputs=None,
352362
model_output=None,
353363
) -> None:
354-
for stat_logger in self.stat_loggers:
364+
for stat_logger in self.stat_loggers.values():
355365
stat_logger.log()
356366

367+
def add_logger(self, logger_name: str, logger: StatLoggerBase) -> None:
368+
if not self.log_stats:
369+
raise RuntimeError(
370+
"Stat logging is disabled. Set `disable_log_stats=False` "
371+
"argument to enable.")
372+
if logger_name in self.stat_loggers:
373+
raise KeyError(f"Logger with name {logger_name} already exists.")
374+
self.stat_loggers[logger_name] = logger
375+
376+
def remove_logger(self, logger_name: str) -> None:
377+
if not self.log_stats:
378+
raise RuntimeError(
379+
"Stat logging is disabled. Set `disable_log_stats=False` "
380+
"argument to enable.")
381+
if logger_name not in self.stat_loggers:
382+
raise KeyError(f"Logger with name {logger_name} does not exist.")
383+
del self.stat_loggers[logger_name]
384+
357385
async def check_health(self) -> None:
358386
logger.debug("Called check_health.")
359387

vllm/v1/metrics/loggers.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
_LOCAL_LOGGING_INTERVAL_SEC = 5.0
1919

20+
STANDARD_LOGGING_LOGGER_NAME = "logging"
21+
PROMETHEUS_LOGGING_LOGGER_NAME = "prometheus"
22+
2023

2124
class StatLoggerBase(ABC):
2225

0 commit comments

Comments
 (0)