# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import asyncio import multiprocessing import multiprocessing.forkserver as forkserver import os import signal import socket import tempfile from argparse import Namespace from collections.abc import AsyncIterator from contextlib import asynccontextmanager from typing import Any import vllm.envs as envs from vllm.engine.arg_utils import AsyncEngineArgs from vllm.engine.protocol import EngineClient from vllm.logger import init_logger from vllm.reasoning import ReasoningParserManager from vllm.tool_parsers import ToolParserManager from vllm.usage.usage_lib import UsageContext from vllm.utils.system_utils import decorate_logs from ..app import build_app from ..launcher import serve_http, setup_server from ..utils.server_utils import get_uvicorn_log_config from .app_state import init_app_state prometheus_multiproc_dir: tempfile.TemporaryDirectory # Cannot use __name__ (https://github.com/vllm-project/vllm/pull/4765) logger = init_logger("vllm.entrypoints.launchers.api_server.entry") @asynccontextmanager async def build_async_engine_client( args: Namespace, *, usage_context: UsageContext = UsageContext.OPENAI_API_SERVER, client_config: dict[str, Any] | None = None, ) -> AsyncIterator[EngineClient]: if os.getenv("VLLM_WORKER_MULTIPROC_METHOD") == "forkserver": # The executor is expected to be mp. # Pre-import heavy modules in the forkserver process logger.debug("Setup forkserver with pre-imports") multiprocessing.set_start_method("forkserver") multiprocessing.set_forkserver_preload(["vllm.v1.engine.async_llm"]) forkserver.ensure_running() logger.debug("Forkserver setup complete!") # Context manager to handle engine_client lifecycle # Ensures everything is shutdown and cleaned up on error/exit engine_args = AsyncEngineArgs.from_cli_args(args) if client_config: engine_args._api_process_count = client_config.get("client_count", 1) engine_args._api_process_rank = client_config.get("client_index", 0) async with build_async_engine_client_from_engine_args( engine_args, usage_context=usage_context, client_config=client_config, ) as engine: yield engine @asynccontextmanager async def build_async_engine_client_from_engine_args( engine_args: AsyncEngineArgs, *, usage_context: UsageContext = UsageContext.OPENAI_API_SERVER, client_config: dict[str, Any] | None = None, ) -> AsyncIterator[EngineClient]: """ Create EngineClient, either: - in-process using the AsyncLLMEngine Directly - multiprocess using AsyncLLMEngine RPC Returns the Client or None if the creation failed. """ # Create the EngineConfig (determines if we can use V1). vllm_config = engine_args.create_engine_config(usage_context=usage_context) from vllm.v1.engine.async_llm import AsyncLLM async_llm: AsyncLLM | None = None # Don't mutate the input client_config client_config = dict(client_config) if client_config else {} client_count = client_config.pop("client_count", 1) client_index = client_config.pop("client_index", 0) try: async_llm = AsyncLLM.from_vllm_config( vllm_config=vllm_config, usage_context=usage_context, enable_log_requests=engine_args.enable_log_requests, aggregate_engine_logging=engine_args.aggregate_engine_logging, disable_log_stats=engine_args.disable_log_stats, client_addresses=client_config, client_count=client_count, client_index=client_index, ) # Don't keep the dummy data in memory assert async_llm is not None await async_llm.reset_mm_cache() yield async_llm finally: if async_llm: async_llm.shutdown(timeout=vllm_config.shutdown_timeout) async def build_and_serve( engine_client: EngineClient, listen_address: str, sock: socket.socket, args: Namespace, **uvicorn_kwargs, ) -> asyncio.Task: """Build FastAPI app, initialize state, and start serving. Returns the shutdown task for the caller to await. """ # Get uvicorn log config (from file or with endpoint filter) log_config = get_uvicorn_log_config(args) if log_config is not None: uvicorn_kwargs["log_config"] = log_config supported_tasks = await engine_client.get_supported_tasks() model_config = engine_client.model_config logger.info("Supported tasks: %s", supported_tasks) app = build_app(args, supported_tasks, model_config) await init_app_state(engine_client, app.state, args, supported_tasks) logger.info("Starting vLLM server on %s", listen_address) return await serve_http( app, sock=sock, enable_ssl_refresh=args.enable_ssl_refresh, host=args.host, port=args.port, log_level=args.uvicorn_log_level, # NOTE: When the 'disable_uvicorn_access_log' value is True, # no access log will be output. access_log=not args.disable_uvicorn_access_log, timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE, ssl_keyfile=args.ssl_keyfile, ssl_certfile=args.ssl_certfile, ssl_ca_certs=args.ssl_ca_certs, ssl_cert_reqs=args.ssl_cert_reqs, ssl_ciphers=args.ssl_ciphers, h11_max_incomplete_event_size=args.h11_max_incomplete_event_size, h11_max_header_count=args.h11_max_header_count, **uvicorn_kwargs, ) async def run_server(args, **uvicorn_kwargs) -> None: """Run a single-worker API server.""" decorate_logs("APIServer", skip_if_decorated=True) # Interrupt initialization if SIGTERM arrives before uvicorn installs its # own signal handlers. Once uvicorn is running it replaces this. def _interrupt_init(*_) -> None: raise KeyboardInterrupt("terminated") signal.signal(signal.SIGTERM, _interrupt_init) listen_address, sock = setup_server(args, reuse_port=False) await run_server_worker(listen_address, sock, args, **uvicorn_kwargs) async def run_server_worker( listen_address, sock, args, client_config=None, **uvicorn_kwargs ) -> None: """Run a single API server worker.""" if args.tool_parser_plugin and len(args.tool_parser_plugin) > 3: ToolParserManager.import_tool_parser(args.tool_parser_plugin) if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3: ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin) async with build_async_engine_client( args, client_config=client_config, ) as engine_client: shutdown_task = await build_and_serve( engine_client, listen_address, sock, args, **uvicorn_kwargs ) # NB: Await server shutdown only after the backend context is exited try: await shutdown_task finally: sock.close() def main(): import uvloop from vllm.entrypoints.serve.utils.api_utils import cli_env_setup from vllm.utils.argparse_utils import FlexibleArgumentParser from ..cli_args import ( make_arg_parser, validate_parsed_serve_args, ) # NOTE(simon): # This section should be in sync with vllm/entrypoints/cli/main.py for CLI # entrypoints. cli_env_setup() parser = FlexibleArgumentParser( description="vLLM OpenAI-Compatible RESTful API server." ) parser = make_arg_parser(parser) args = parser.parse_args() validate_parsed_serve_args(args) uvloop.run(run_server(args)) if __name__ == "__main__": main()