[Frontend] Move api_server.py out openai folder (#52131)
Signed-off-by: wang.yuqi <yuqi.wang@daocloud.io>
wang.yuqi committed
Aug 19, 2026 at 17:43 UTC
eac636a7fa476983cdae34b45a984e9852aad375
43 files changed
+887
-775
.buildkite/test-amd.yaml
+6
-4
@@ -990,10 +990,12 @@ steps:
990
- vllm/entrypoints
991
- tests/entrypoints/unit_tests
992
- tests/entrypoints/weight_transfer
993
+ - tests/entrypoints/launchers
994
- vllm/platforms/rocm.py
995
commands:
996
- pytest -v -s entrypoints/unit_tests
997
- pytest -v -s entrypoints/weight_transfer
998
+ - pytest -v -s entrypoints/launchers
999
1000
- label: Entrypoints Integration (LLM) # TBD
1001
timeout_in_minutes: 180
@@ -2320,9 +2322,9 @@ steps:
2322
- tests/benchmarks/test_serve_cli.py
2323
- tests/entrypoints/openai/chat_completion/test_chat_completion.py
2324
- tests/entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py
2323
- - tests/entrypoints/openai/completion/test_shutdown.py
2325
+ - tests/entrypoints/launchers/test_shutdown.py
2326
- tests/entrypoints/openai/test_return_token_ids.py
2325
- - tests/entrypoints/openai/test_uds.py
2327
+ - tests/entrypoints/serve/instrumentator/test_uds.py
2328
- tests/v1/sample/test_logprobs_e2e.py
2329
- vllm/platforms/rocm.py
2330
commands:
@@ -2331,9 +2333,9 @@ steps:
2333
- pytest -v -s benchmarks/test_serve_cli.py -k "not insecure and not (test_bench_serve and not test_bench_serve_chat)"
2334
- pytest -v -s entrypoints/openai/chat_completion/test_chat_completion.py -k "not test_invalid_json_schema and not test_invalid_regex and not test_kv_transfer_prompt_token_ids_round_trip and not test_kv_transfer_prompt_token_ids_streaming"
2335
- pytest -v -s entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py -k "not multiple"
2334
- - pytest -v -s entrypoints/openai/completion/test_shutdown.py -k "not engine_failure and not test_abort_timeout_exits_quickly"
2336
+ - pytest -v -s entrypoints/launchers/test_shutdown.py -k "not engine_failure and not test_abort_timeout_exits_quickly"
2337
- pytest -v -s entrypoints/openai/test_return_token_ids.py -k "not test_comparison"
2336
- - pytest -v -s entrypoints/openai/test_uds.py
2338
+ - pytest -v -s entrypoints/serve/instrumentator/test_uds.py
2339
- pytest -v -s v1/sample/test_logprobs_e2e.py -k "test_prompt_logprobs_e2e_server"
2340
2341
- label: Rust Frontend Serve Admin Coverage # TBD
.buildkite/test_areas/entrypoints.yaml
+2
@@ -11,9 +11,11 @@ steps:
11
- vllm/entrypoints
12
- tests/entrypoints/unit_tests
13
- tests/entrypoints/weight_transfer
14
+ - tests/entrypoints/launchers
15
commands:
16
- pytest -v -s entrypoints/unit_tests
17
- pytest -v -s entrypoints/weight_transfer
18
+ - pytest -v -s entrypoints/launchers
19
mirror:
20
amd:
21
label: ":amd: (MI355) Entrypoints Unit"
.buildkite/test_areas/rust_frontend.yaml
+4
-4
@@ -18,9 +18,9 @@ steps:
18
- tests/entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py
19
20
# - tests/entrypoints/openai/completion/test_prompt_validation.py
21
- - tests/entrypoints/openai/completion/test_shutdown.py
21
+ - tests/entrypoints/launchers/test_shutdown.py
22
- tests/entrypoints/openai/test_return_token_ids.py
23
- - tests/entrypoints/openai/test_uds.py
23
+ - tests/entrypoints/serve/instrumentator/test_uds.py
24
- tests/v1/sample/test_logprobs_e2e.py
25
commands:
26
- export VLLM_USE_RUST_FRONTEND=1
@@ -30,11 +30,11 @@ steps:
30
- pytest -v -s entrypoints/openai/chat_completion/test_chat_logit_bias_validation.py -k "not multiple"
31
32
# - pytest -v -s entrypoints/openai/completion/test_prompt_validation.py -k "not prompt_embeds"
33
- - pytest -v -s entrypoints/openai/completion/test_shutdown.py -k "not engine_failure and not test_abort_timeout_exits_quickly"
33
+ - pytest -v -s entrypoints/launchers/test_shutdown.py -k "not engine_failure and not test_abort_timeout_exits_quickly"
34
# test_comparison streams differently: Rust emits a separate first (prompt_token_ids) chunk and
35
# finish chunk without logprobs, while the test reads `logprobs.tokens` on every chunk.
36
- pytest -v -s entrypoints/openai/test_return_token_ids.py -k "not test_comparison"
37
- - pytest -v -s entrypoints/openai/test_uds.py
37
+ - pytest -v -s entrypoints/serve/instrumentator/test_uds.py
38
- pytest -v -s v1/sample/test_logprobs_e2e.py -k "test_prompt_logprobs_e2e_server"
39
40
- label: ":nvidia: (H200) Rust Frontend Serve/Admin Coverage"
docs/deployment/integrations/kthena.md
+1
-1
@@ -117,7 +117,7 @@ Commands:
117
- >
118
bash /vllm-workspace/examples/ray_serving/multi-node-serving.sh
119
leader --ray_cluster_size=2; python3 -m
120
- vllm.entrypoints.openai.api_server --port 8080 --model
120
+ vllm.entrypoints.launchers.api_server.entry --port 8080 --model
121
meta-llama/Llama-3.1-405B-Instruct --tensor-parallel-size 8
122
--pipeline-parallel-size 2
123
```
docs/design/arch_overview.md
+1
-15
@@ -62,20 +62,6 @@ vllm serve <model>
62
63
The code for the `vllm` CLI can be found in [vllm/entrypoints/cli/main.py](../../vllm/entrypoints/cli/main.py).
64
65
-Sometimes you may see the API server entrypoint used directly instead of via the
66
-`vllm` CLI command. For example:
67
-
68
-```bash
69
-python -m vllm.entrypoints.openai.api_server --model <model>
70
-```
71
-
72
-!!! warning
73
-
74
- `python -m vllm.entrypoints.openai.api_server` is deprecated
75
- and may become unsupported in a future release.
76
-
77
-That code can be found in [vllm/entrypoints/openai/api_server.py](../../vllm/entrypoints/openai/api_server.py).
78
-
65
More details on the API server can be found in the [Online Serving](../serving/online_serving/README.md) document.
66
67
## V1 Process Architecture
@@ -88,7 +74,7 @@ The API server process handles HTTP requests (e.g., the OpenAI-compatible API),
74
75
By default, there is **1 API server process**, but when data parallelism is used, the API server count automatically scales to match the data parallel size. This can also be manually configured with the `--api-server-count` flag. Each API server connects to **all** engine cores via ZMQ in a many-to-many topology, enabling any API server to route requests to any engine core. Each API server process uses multiple CPU threads for media loading (controlled by `VLLM_MEDIA_LOADING_THREAD_COUNT`, default 8).
76
91
-The code can be found in [vllm/entrypoints/openai/api_server.py](../../vllm/entrypoints/openai/api_server.py) and [vllm/v1/utils.py](../../vllm/v1/utils.py).
77
+The code can be found in [vllm/entrypoints/launchers/api_server](../../vllm/entrypoints/launchers/api_server) and [vllm/v1/utils.py](../../vllm/v1/utils.py).
78
79
### Engine Core Process
80
docs/design/endpoint_plugins.md
+1
-1
@@ -1,6 +1,6 @@
1
# Endpoint Plugins
2
3
-Endpoint plugins let out-of-tree packages add HTTP routes to the OpenAI compatible API server without editing `vllm/entrypoints/openai/api_server.py`. Their scope is
3
+Endpoint plugins let out-of-tree packages add HTTP routes to the OpenAI compatible API server. Their scope is
4
the **HTTP surface only** registering routes and optionally per app state used by those routes. A plugin reaches the engine the same way an in-tree serving handler does, through the `EngineClient` it is handed at startup (e.g. `engine_client.collective_rpc(...)`). No new engine access path is introduced.
5
6
!!! warning "Security"
examples/applications/api_server/server.py
+2
-3
@@ -4,8 +4,7 @@
4
NOTE: This API server is used only for demonstrating usage of AsyncEngine
5
and simple performance benchmarks. It is not intended for production use.
6
For production use, we recommend using our OpenAI compatible server.
7
-We are also not going to accept PRs modifying this file, please
8
-change `vllm/entrypoints/openai/api_server.py` instead.
7
+We are also not going to accept PRs modifying this file.
8
"""
9
10
import asyncio
@@ -21,7 +20,7 @@ from fastapi.responses import JSONResponse, Response, StreamingResponse
20
import vllm.envs as envs
21
from vllm.engine.arg_utils import AsyncEngineArgs
22
from vllm.engine.async_llm_engine import AsyncLLMEngine
24
-from vllm.entrypoints.launcher import serve_http
23
+from vllm.entrypoints.launchers.launcher import serve_http
24
from vllm.entrypoints.serve.utils.api_utils import with_cancellation
25
from vllm.logger import init_logger
26
from vllm.sampling_params import SamplingParams
tests/entrypoints/launchers/__init__.py
tests/entrypoints/launchers/api_server/__init__.py
tests/entrypoints/launchers/api_server/_api_server_spawn_workers.py
renamed
tests/entrypoints/launchers/api_server/test_api_server_process_manager.py
renamed
+1
-1
@@ -10,7 +10,7 @@ from unittest.mock import patch
10
import pytest
11
import zmq
12
13
-from tests.entrypoints.unit_tests._api_server_spawn_workers import (
13
+from tests.entrypoints.launchers.api_server._api_server_spawn_workers import (
14
exit_before_report_worker,
15
)
16
from vllm.utils.network_utils import make_zmq_socket, split_zmq_path
tests/entrypoints/launchers/api_server/test_multi_api_servers.py
renamed
tests/entrypoints/launchers/test_launch_cli.py
renamed
tests/entrypoints/launchers/test_shutdown.py
renamed
+1
-1
@@ -147,7 +147,7 @@ async def test_shutdown_on_engine_failure(tmp_path: Path):
147
# dtype, max-len etc set so that this can run in CI
148
sys.executable,
149
"-m",
150
- "vllm.entrypoints.openai.api_server",
150
+ "vllm.entrypoints.launchers.api_server.entry",
151
"--model",
152
MODEL_NAME,
153
"--dtype",
tests/entrypoints/launchers/test_ssl_cert_refresher.py
renamed
+1
-1
@@ -7,7 +7,7 @@ from ssl import SSLContext
7
8
import pytest
9
10
-from vllm.entrypoints.serve.utils.ssl import SSLCertRefresher
10
+from vllm.entrypoints.launchers.utils.ssl import SSLCertRefresher
11
12
13
class MockSSLContext(SSLContext):
tests/entrypoints/serve/exception_handling/test_http_status_metrics.py
+1
-1
@@ -14,7 +14,7 @@ import pytest
14
from fastapi import HTTPException
15
from prometheus_client import CollectorRegistry
16
17
-from vllm.entrypoints.openai.api_server import build_app
17
+from vllm.entrypoints.launchers.api_server.entry import build_app
18
from vllm.exceptions import (
19
VLLMNotFoundError,
20
VLLMServerError,
tests/entrypoints/serve/instrumentator/test_uds.py
renamed
+1
-2
@@ -6,11 +6,10 @@ from tempfile import TemporaryDirectory
6
import httpx
7
import pytest
8
9
+from tests.utils import RemoteOpenAIServer
10
from vllm import envs
11
from vllm.version import __version__ as VLLM_VERSION
12
12
-from ...utils import RemoteOpenAIServer
13
-
13
MODEL_NAME = "Qwen/Qwen3-0.6B"
14
15
tests/lora/test_add_lora.py
+1
-1
@@ -6,7 +6,7 @@ import time
6
import pytest
7
8
from vllm.engine.arg_utils import AsyncEngineArgs
9
-from vllm.entrypoints.openai.api_server import (
9
+from vllm.entrypoints.launchers.api_server.entry import (
10
build_async_engine_client_from_engine_args,
11
)
12
from vllm.inputs import TextPrompt
tests/lora/test_lora_functions.py
+1
-1
@@ -7,7 +7,7 @@ Script to test add_lora, remove_lora, pin_lora, list_loras functions.
7
import pytest
8
9
from vllm.engine.arg_utils import AsyncEngineArgs, EngineArgs
10
-from vllm.entrypoints.openai.api_server import (
10
+from vllm.entrypoints.launchers.api_server.entry import (
11
build_async_engine_client_from_engine_args,
12
)
13
from vllm.lora.request import LoRARequest
tests/plugins_tests/test_endpoint_plugins.py
+10
-10
@@ -17,14 +17,14 @@ import pytest
17
from fastapi import FastAPI
18
from vllm_add_dummy_endpoint_plugin import DummyAdminEndpointPlugin
19
20
-from vllm.entrypoints.openai.api_server import (
21
- _attach_endpoint_plugins,
22
- _init_endpoint_plugins_state,
23
- build_app,
24
-)
20
+from vllm.entrypoints.launchers.app import build_app
21
from vllm.entrypoints.openai.cli_args import make_arg_parser
22
from vllm.plugins import load_endpoint_plugins
27
-from vllm.plugins.endpoint_plugins.interface import EndpointPlugin
23
+from vllm.plugins.endpoint_plugins.interface import (
24
+ EndpointPlugin,
25
+ attach_endpoint_plugins,
26
+ init_endpoint_plugins_state,
27
+)
28
from vllm.utils.argparse_utils import FlexibleArgumentParser
29
30
@@ -157,7 +157,7 @@ def test_attach_is_noop_when_nothing_discovered(monkeypatch: pytest.MonkeyPatch)
157
monkeypatch.delenv("VLLM_PLUGINS", raising=False)
158
159
app = FastAPI()
160
- _attach_endpoint_plugins(app, ("generate",))
160
+ attach_endpoint_plugins(app, ("generate",))
161
162
assert app.state.endpoint_plugins == []
163
@@ -172,7 +172,7 @@ async def test_init_state_is_noop_without_phase_a(monkeypatch: pytest.MonkeyPatc
172
monkeypatch.setenv("VLLM_PLUGINS", "dummy_admin_endpoint_plugin")
173
174
state = State()
175
- await _init_endpoint_plugins_state(_FakeEngineClient(), state, _build_args())
175
+ await init_endpoint_plugins_state(_FakeEngineClient(), state, _build_args())
176
177
assert not hasattr(state, "dummy_engine_client")
178
@@ -196,7 +196,7 @@ async def test_render_server_attaches_endpoint_plugins_with_no_engine_client(
196
for route in app.routes
197
)
198
199
- await _init_endpoint_plugins_state(None, app.state, args)
199
+ await init_endpoint_plugins_state(None, app.state, args)
200
201
assert app.state.dummy_engine_client is None
202
@@ -223,7 +223,7 @@ async def test_endpoint_plugin_end_to_end(monkeypatch: pytest.MonkeyPatch):
223
)
224
225
fake_engine_client = _FakeEngineClient(rpc_result=["cfg-a", "cfg-b"])
226
- await _init_endpoint_plugins_state(fake_engine_client, app.state, args)
226
+ await init_endpoint_plugins_state(fake_engine_client, app.state, args)
227
228
assert app.state.dummy_engine_client is fake_engine_client
229
vllm/benchmarks/throughput.py
+1
-1
@@ -305,7 +305,7 @@ async def run_vllm_async(
305
disable_detokenize: bool = False,
306
warmup_requests: list[SampleRequest] | None = None,
307
) -> float:
308
- from vllm.entrypoints.openai.api_server import (
308
+ from vllm.entrypoints.launchers.api_server.entry import (
309
build_async_engine_client_from_engine_args,
310
)
311
vllm/entrypoints/cli/launch.py
+1
-43
@@ -3,18 +3,11 @@
3
4
import argparse
5
import inspect
6
-import signal
6
7
import uvloop
8
10
-from vllm import envs
11
-from vllm.config import VllmConfig
12
-from vllm.engine.arg_utils import AsyncEngineArgs
9
from vllm.entrypoints.cli.types import CLISubcommand
14
-from vllm.entrypoints.openai.api_server import (
15
- build_and_serve_renderer,
16
- setup_server,
17
-)
10
+from vllm.entrypoints.launchers.render.entry import run_launch_fastapi
11
from vllm.entrypoints.openai.cli_args import (
12
make_arg_parser,
13
validate_parsed_serve_args,
@@ -120,38 +113,3 @@ class LaunchSubcommand(CLISubcommand):
113
114
def cmd_init() -> list[CLISubcommand]:
115
return [LaunchSubcommand()]
123
-
124
-
125
-async def run_launch_fastapi(args: argparse.Namespace) -> None:
126
- """Run the online serving layer with FastAPI (no GPU inference)."""
127
-
128
- # Interrupt initialization if SIGTERM arrives before uvicorn installs
129
- # its own signal handlers. Once uvicorn is running it replaces this.
130
- def _interrupt_init(*_) -> None:
131
- raise KeyboardInterrupt("terminated")
132
-
133
- signal.signal(signal.SIGTERM, _interrupt_init)
134
-
135
- # 1. Socket binding
136
- listen_address, sock = setup_server(args, reuse_port=False)
137
-
138
- # 2. Build and serve the API server
139
- engine_args = AsyncEngineArgs.from_cli_args(args)
140
- model_config = engine_args.create_model_config()
141
-
142
- # Render servers preprocess data only — no inference, no quantized kernels.
143
- # Clear quantization so VllmConfig skips quant dtype/capability validation.
144
- model_config.quantization = None
145
-
146
- # Render servers never allocate KV cache; suppress the spurious CPU KV
147
- # cache space warning from CpuPlatform.check_and_update_config.
148
- envs.VLLM_CPU_KVCACHE_SPACE = 0
149
-
150
- vllm_config = VllmConfig(model_config=model_config)
151
- shutdown_task = await build_and_serve_renderer(
152
- vllm_config, listen_address, sock, args
153
- )
154
- try:
155
- await shutdown_task
156
- finally:
157
- sock.close()
vllm/entrypoints/cli/serve.py
+2
-4
@@ -10,11 +10,9 @@ import uvloop
10
import vllm
11
import vllm.envs as envs
12
from vllm.entrypoints.cli.types import CLISubcommand
13
-from vllm.entrypoints.openai.api_server import run_server, setup_server
13
+from vllm.entrypoints.launchers.api_server.entry import run_server, setup_server
14
from vllm.entrypoints.openai.cli_args import make_arg_parser, validate_parsed_serve_args
15
-from vllm.entrypoints.openai.dp_supervisor import (
16
- run_dp_supervisor,
17
-)
15
+from vllm.entrypoints.openai.dp_supervisor import run_dp_supervisor
16
from vllm.entrypoints.serve.utils.api_utils import VLLM_SUBCMD_PARSER_EPILOG
17
from vllm.logger import init_logger
18
from vllm.usage.usage_lib import UsageContext
vllm/entrypoints/launchers/api_server/app_state.py
new
+149
@@ -0,0 +1,149 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import warnings
4
+from argparse import Namespace
5
+from typing import cast
6
+
7
+from starlette.datastructures import State
8
+
9
+from vllm.engine.protocol import EngineClient
10
+from vllm.entrypoints.chat_utils import load_chat_template
11
+from vllm.entrypoints.openai.models.protocol import BaseModelPath
12
+from vllm.entrypoints.openai.models.serving import OpenAIServingModels
13
+from vllm.entrypoints.serve.tokenize.serving import ServingTokenization
14
+from vllm.entrypoints.serve.utils.api_utils import process_lora_modules
15
+from vllm.entrypoints.serve.utils.request_logger import RequestLogger
16
+from vllm.plugins.endpoint_plugins.interface import init_endpoint_plugins_state
17
+from vllm.renderers.online_derenderer import OnlineDerenderer
18
+from vllm.renderers.online_renderer import OnlineRenderer
19
+from vllm.tasks import FALLBACK_SUPPORTED_TASKS, POOLING_TASKS, SupportedTask
20
+
21
+
22
+async def init_app_state(
23
+ engine_client: EngineClient,
24
+ state: State,
25
+ args: Namespace,
26
+ supported_tasks: tuple["SupportedTask", ...] | None = None,
27
+) -> None:
28
+ vllm_config = engine_client.vllm_config
29
+
30
+ if args.tool_call_parser is not None:
31
+ from vllm.parser.metrics import init_parser_metrics
32
+
33
+ init_parser_metrics(
34
+ model_name=cast(str, vllm_config.model_config.served_model_name)
35
+ )
36
+
37
+ if supported_tasks is None:
38
+ warnings.warn(
39
+ "The 'supported_tasks' parameter was not provided to "
40
+ "init_app_state and will be required in a future version. "
41
+ "Please pass 'supported_tasks' explicitly.",
42
+ DeprecationWarning,
43
+ stacklevel=2,
44
+ )
45
+ supported_tasks = FALLBACK_SUPPORTED_TASKS
46
+
47
+ if args.served_model_name is not None:
48
+ served_model_names = args.served_model_name
49
+ else:
50
+ served_model_names = [args.model]
51
+
52
+ if args.enable_log_requests:
53
+ request_logger = RequestLogger(max_log_len=args.max_log_len)
54
+ else:
55
+ request_logger = None
56
+
57
+ base_model_paths = [
58
+ BaseModelPath(name=name, model_path=args.model) for name in served_model_names
59
+ ]
60
+
61
+ state.engine_client = engine_client
62
+ state.log_stats = not args.disable_log_stats
63
+ state.vllm_config = vllm_config
64
+ state.args = args
65
+ resolved_chat_template = load_chat_template(args.chat_template)
66
+
67
+ # Merge default_mm_loras into the static lora_modules
68
+ default_mm_loras = (
69
+ vllm_config.lora_config.default_mm_loras
70
+ if vllm_config.lora_config is not None
71
+ else {}
72
+ )
73
+ lora_modules = process_lora_modules(args.lora_modules, default_mm_loras)
74
+
75
+ state.openai_serving_models = OpenAIServingModels(
76
+ engine_client=engine_client,
77
+ base_model_paths=base_model_paths,
78
+ lora_modules=lora_modules,
79
+ )
80
+ await state.openai_serving_models.init_static_loras()
81
+
82
+ state.online_renderer = OnlineRenderer(
83
+ model_config=engine_client.model_config,
84
+ renderer=engine_client.renderer,
85
+ request_logger=request_logger,
86
+ chat_template=resolved_chat_template,
87
+ chat_template_content_format=args.chat_template_content_format,
88
+ trust_request_chat_template=args.trust_request_chat_template,
89
+ enable_auto_tools=args.enable_auto_tool_choice,
90
+ exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
91
+ tool_parser=args.tool_call_parser,
92
+ reasoning_parser=args.structured_outputs_config.reasoning_parser,
93
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
94
+ log_error_stack=args.log_error_stack,
95
+ )
96
+ state.online_renderer.warmup()
97
+
98
+ state.online_derenderer = OnlineDerenderer(
99
+ model_config=engine_client.model_config,
100
+ renderer=engine_client.renderer,
101
+ request_logger=request_logger,
102
+ chat_template=resolved_chat_template,
103
+ chat_template_content_format=args.chat_template_content_format,
104
+ trust_request_chat_template=args.trust_request_chat_template,
105
+ enable_auto_tools=args.enable_auto_tool_choice,
106
+ exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
107
+ tool_parser=args.tool_call_parser,
108
+ reasoning_parser=args.structured_outputs_config.reasoning_parser,
109
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
110
+ log_error_stack=args.log_error_stack,
111
+ )
112
+
113
+ state.serving_tokenization = ServingTokenization(
114
+ state.openai_serving_models,
115
+ state.online_renderer,
116
+ request_logger=request_logger,
117
+ chat_template=resolved_chat_template,
118
+ chat_template_content_format=args.chat_template_content_format,
119
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
120
+ trust_request_chat_template=args.trust_request_chat_template,
121
+ )
122
+
123
+ if "generate" in supported_tasks:
124
+ from vllm.entrypoints.generate.api_router import init_generate_state
125
+
126
+ await init_generate_state(
127
+ engine_client, state, args, request_logger, supported_tasks
128
+ )
129
+
130
+ from vllm.entrypoints.scale_out.factories import init_scale_out_state
131
+
132
+ init_scale_out_state(state, args, engine_client, request_logger)
133
+
134
+ if "transcription" in supported_tasks or "realtime" in supported_tasks:
135
+ from vllm.entrypoints.speech_to_text.factories import init_speech_to_text_state
136
+
137
+ init_speech_to_text_state(
138
+ engine_client, state, args, request_logger, supported_tasks
139
+ )
140
+
141
+ if any(task in POOLING_TASKS for task in supported_tasks):
142
+ from vllm.entrypoints.pooling.factories import init_pooling_state
143
+
144
+ init_pooling_state(engine_client, state, args, request_logger, supported_tasks)
145
+
146
+ await init_endpoint_plugins_state(engine_client, state, args)
147
+
148
+ state.enable_server_load_tracking = args.enable_server_load_tracking
149
+ state.server_load_metrics = 0
vllm/entrypoints/launchers/api_server/entry.py
new
+229
@@ -0,0 +1,229 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import asyncio
4
+import multiprocessing
5
+import multiprocessing.forkserver as forkserver
6
+import os
7
+import signal
8
+import socket
9
+import tempfile
10
+from argparse import Namespace
11
+from collections.abc import AsyncIterator
12
+from contextlib import asynccontextmanager
13
+from typing import Any
14
+
15
+import vllm.envs as envs
16
+from vllm.engine.arg_utils import AsyncEngineArgs
17
+from vllm.engine.protocol import EngineClient
18
+from vllm.logger import init_logger
19
+from vllm.reasoning import ReasoningParserManager
20
+from vllm.tool_parsers import ToolParserManager
21
+from vllm.usage.usage_lib import UsageContext
22
+from vllm.utils.system_utils import decorate_logs
23
+
24
+from ..app import build_app
25
+from ..launcher import serve_http, setup_server
26
+from ..utils.server_utils import get_uvicorn_log_config
27
+from .app_state import init_app_state
28
+
29
+prometheus_multiproc_dir: tempfile.TemporaryDirectory
30
+
31
+# Cannot use __name__ (https://github.com/vllm-project/vllm/pull/4765)
32
+logger = init_logger("vllm.entrypoints.launchers.api_server.entry")
33
+
34
+
35
+@asynccontextmanager
36
+async def build_async_engine_client(
37
+ args: Namespace,
38
+ *,
39
+ usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,
40
+ client_config: dict[str, Any] | None = None,
41
+) -> AsyncIterator[EngineClient]:
42
+ if os.getenv("VLLM_WORKER_MULTIPROC_METHOD") == "forkserver":
43
+ # The executor is expected to be mp.
44
+ # Pre-import heavy modules in the forkserver process
45
+ logger.debug("Setup forkserver with pre-imports")
46
+ multiprocessing.set_start_method("forkserver")
47
+ multiprocessing.set_forkserver_preload(["vllm.v1.engine.async_llm"])
48
+ forkserver.ensure_running()
49
+ logger.debug("Forkserver setup complete!")
50
+
51
+ # Context manager to handle engine_client lifecycle
52
+ # Ensures everything is shutdown and cleaned up on error/exit
53
+ engine_args = AsyncEngineArgs.from_cli_args(args)
54
+ if client_config:
55
+ engine_args._api_process_count = client_config.get("client_count", 1)
56
+ engine_args._api_process_rank = client_config.get("client_index", 0)
57
+
58
+ async with build_async_engine_client_from_engine_args(
59
+ engine_args,
60
+ usage_context=usage_context,
61
+ client_config=client_config,
62
+ ) as engine:
63
+ yield engine
64
+
65
+
66
+@asynccontextmanager
67
+async def build_async_engine_client_from_engine_args(
68
+ engine_args: AsyncEngineArgs,
69
+ *,
70
+ usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,
71
+ client_config: dict[str, Any] | None = None,
72
+) -> AsyncIterator[EngineClient]:
73
+ """
74
+ Create EngineClient, either:
75
+ - in-process using the AsyncLLMEngine Directly
76
+ - multiprocess using AsyncLLMEngine RPC
77
+
78
+ Returns the Client or None if the creation failed.
79
+ """
80
+
81
+ # Create the EngineConfig (determines if we can use V1).
82
+ vllm_config = engine_args.create_engine_config(usage_context=usage_context)
83
+
84
+ from vllm.v1.engine.async_llm import AsyncLLM
85
+
86
+ async_llm: AsyncLLM | None = None
87
+
88
+ # Don't mutate the input client_config
89
+ client_config = dict(client_config) if client_config else {}
90
+ client_count = client_config.pop("client_count", 1)
91
+ client_index = client_config.pop("client_index", 0)
92
+
93
+ try:
94
+ async_llm = AsyncLLM.from_vllm_config(
95
+ vllm_config=vllm_config,
96
+ usage_context=usage_context,
97
+ enable_log_requests=engine_args.enable_log_requests,
98
+ aggregate_engine_logging=engine_args.aggregate_engine_logging,
99
+ disable_log_stats=engine_args.disable_log_stats,
100
+ client_addresses=client_config,
101
+ client_count=client_count,
102
+ client_index=client_index,
103
+ )
104
+
105
+ # Don't keep the dummy data in memory
106
+ assert async_llm is not None
107
+ await async_llm.reset_mm_cache()
108
+
109
+ yield async_llm
110
+ finally:
111
+ if async_llm:
112
+ async_llm.shutdown(timeout=vllm_config.shutdown_timeout)
113
+
114
+
115
+async def build_and_serve(
116
+ engine_client: EngineClient,
117
+ listen_address: str,
118
+ sock: socket.socket,
119
+ args: Namespace,
120
+ **uvicorn_kwargs,
121
+) -> asyncio.Task:
122
+ """Build FastAPI app, initialize state, and start serving.
123
+
124
+ Returns the shutdown task for the caller to await.
125
+ """
126
+
127
+ # Get uvicorn log config (from file or with endpoint filter)
128
+ log_config = get_uvicorn_log_config(args)
129
+ if log_config is not None:
130
+ uvicorn_kwargs["log_config"] = log_config
131
+
132
+ supported_tasks = await engine_client.get_supported_tasks()
133
+ model_config = engine_client.model_config
134
+
135
+ logger.info("Supported tasks: %s", supported_tasks)
136
+ app = build_app(args, supported_tasks, model_config)
137
+ await init_app_state(engine_client, app.state, args, supported_tasks)
138
+
139
+ logger.info("Starting vLLM server on %s", listen_address)
140
+
141
+ return await serve_http(
142
+ app,
143
+ sock=sock,
144
+ enable_ssl_refresh=args.enable_ssl_refresh,
145
+ host=args.host,
146
+ port=args.port,
147
+ log_level=args.uvicorn_log_level,
148
+ # NOTE: When the 'disable_uvicorn_access_log' value is True,
149
+ # no access log will be output.
150
+ access_log=not args.disable_uvicorn_access_log,
151
+ timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE,
152
+ ssl_keyfile=args.ssl_keyfile,
153
+ ssl_certfile=args.ssl_certfile,
154
+ ssl_ca_certs=args.ssl_ca_certs,
155
+ ssl_cert_reqs=args.ssl_cert_reqs,
156
+ ssl_ciphers=args.ssl_ciphers,
157
+ h11_max_incomplete_event_size=args.h11_max_incomplete_event_size,
158
+ h11_max_header_count=args.h11_max_header_count,
159
+ **uvicorn_kwargs,
160
+ )
161
+
162
+
163
+async def run_server(args, **uvicorn_kwargs) -> None:
164
+ """Run a single-worker API server."""
165
+
166
+ decorate_logs("APIServer", skip_if_decorated=True)
167
+
168
+ # Interrupt initialization if SIGTERM arrives before uvicorn installs its
169
+ # own signal handlers. Once uvicorn is running it replaces this.
170
+ def _interrupt_init(*_) -> None:
171
+ raise KeyboardInterrupt("terminated")
172
+
173
+ signal.signal(signal.SIGTERM, _interrupt_init)
174
+
175
+ listen_address, sock = setup_server(args, reuse_port=False)
176
+ await run_server_worker(listen_address, sock, args, **uvicorn_kwargs)
177
+
178
+
179
+async def run_server_worker(
180
+ listen_address, sock, args, client_config=None, **uvicorn_kwargs
181
+) -> None:
182
+ """Run a single API server worker."""
183
+
184
+ if args.tool_parser_plugin and len(args.tool_parser_plugin) > 3:
185
+ ToolParserManager.import_tool_parser(args.tool_parser_plugin)
186
+
187
+ if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3:
188
+ ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin)
189
+
190
+ async with build_async_engine_client(
191
+ args,
192
+ client_config=client_config,
193
+ ) as engine_client:
194
+ shutdown_task = await build_and_serve(
195
+ engine_client, listen_address, sock, args, **uvicorn_kwargs
196
+ )
197
+ # NB: Await server shutdown only after the backend context is exited
198
+ try:
199
+ await shutdown_task
200
+ finally:
201
+ sock.close()
202
+
203
+
204
+def main():
205
+ import uvloop
206
+
207
+ from vllm.entrypoints.openai.cli_args import (
208
+ make_arg_parser,
209
+ validate_parsed_serve_args,
210
+ )
211
+ from vllm.entrypoints.serve.utils.api_utils import cli_env_setup
212
+ from vllm.utils.argparse_utils import FlexibleArgumentParser
213
+
214
+ # NOTE(simon):
215
+ # This section should be in sync with vllm/entrypoints/cli/main.py for CLI
216
+ # entrypoints.
217
+ cli_env_setup()
218
+ parser = FlexibleArgumentParser(
219
+ description="vLLM OpenAI-Compatible RESTful API server."
220
+ )
221
+ parser = make_arg_parser(parser)
222
+ args = parser.parse_args()
223
+ validate_parsed_serve_args(args)
224
+
225
+ uvloop.run(run_server(args))
226
+
227
+
228
+if __name__ == "__main__":
229
+ main()
vllm/entrypoints/launchers/app.py
new
+56
@@ -0,0 +1,56 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import warnings
4
+from argparse import Namespace
5
+
6
+from fastapi import FastAPI
7
+
8
+from vllm.config import ModelConfig
9
+from vllm.entrypoints.serve.exception_handling.register import init_exception_handler
10
+from vllm.entrypoints.serve.middleware.register import init_entrypoints_middleware
11
+from vllm.entrypoints.serve.sagemaker.api_router import sagemaker_standards_bootstrap
12
+from vllm.plugins.endpoint_plugins.interface import attach_endpoint_plugins
13
+from vllm.tasks import FALLBACK_SUPPORTED_TASKS, SupportedTask
14
+
15
+from .api_server.routers import register_api_routers
16
+from .utils.server_utils import lifespan
17
+
18
+
19
+def build_app(
20
+ args: Namespace,
21
+ supported_tasks: tuple["SupportedTask", ...] | None = None,
22
+ model_config: ModelConfig | None = None,
23
+) -> FastAPI:
24
+ if supported_tasks is None:
25
+ warnings.warn(
26
+ "The 'supported_tasks' parameter was not provided to "
27
+ "build_app and will be required in a future version. "
28
+ "Defaulting to ('generate',).",
29
+ DeprecationWarning,
30
+ stacklevel=2,
31
+ )
32
+ supported_tasks = FALLBACK_SUPPORTED_TASKS
33
+
34
+ if args.disable_fastapi_docs:
35
+ app = FastAPI(
36
+ openapi_url=None, docs_url=None, redoc_url=None, lifespan=lifespan
37
+ )
38
+ elif args.enable_offline_docs:
39
+ app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan)
40
+ else:
41
+ app = FastAPI(lifespan=lifespan)
42
+ app.state.args = args
43
+ app.root_path = args.root_path
44
+
45
+ register_api_routers(args, app, supported_tasks, model_config)
46
+
47
+ # Endpoint plugins are attached last so their routes are registered after all core
48
+ # routers. This runs even for the CPU only render server. A plugin eligible for
49
+ # the `render` task still gets its routes registered. It receives
50
+ # `engine_client=None` at Phase B (see `_init_endpoint_plugins_state`).
51
+ attach_endpoint_plugins(app, supported_tasks)
52
+
53
+ init_exception_handler(app)
54
+ init_entrypoints_middleware(args, app, supported_tasks)
55
+ app = sagemaker_standards_bootstrap(app)
56
+ return app
vllm/entrypoints/launchers/launcher.py
renamed
+94
-4
@@ -14,13 +14,23 @@ from fastapi import FastAPI
14
15
from vllm import envs
16
from vllm.engine.protocol import EngineClient
17
-from vllm.entrypoints.serve.utils.constants import (
17
+from vllm.entrypoints.launchers.utils.ssl import SSLCertRefresher
18
+from vllm.entrypoints.serve.utils.api_utils import (
19
+ log_non_default_args,
20
+ log_version_and_model,
21
+)
22
+from vllm.logger import init_logger
23
+from vllm.reasoning import ReasoningParserManager
24
+from vllm.tool_parsers import ToolParserManager
25
+from vllm.tracing import instrument
26
+from vllm.utils.network_utils import find_process_using_port, is_valid_ipv6_address
27
+from vllm.utils.system_utils import set_ulimit
28
+from vllm.version import __version__ as VLLM_VERSION
29
+
30
+from .utils.constants import (
31
H11_MAX_HEADER_COUNT_DEFAULT,
32
H11_MAX_INCOMPLETE_EVENT_SIZE_DEFAULT,
33
)
21
-from vllm.entrypoints.serve.utils.ssl import SSLCertRefresher
22
-from vllm.logger import init_logger
23
-from vllm.utils.network_utils import find_process_using_port
34
35
logger = init_logger(__name__)
36
@@ -190,3 +200,83 @@ def terminate_if_errored(server: uvicorn.Server, engine: EngineClient):
200
engine_errored = engine.errored and not engine.is_running
201
if not envs.VLLM_KEEP_ALIVE_ON_ENGINE_DEATH and engine_errored:
202
server.should_exit = True
203
+
204
+
205
+def create_server_socket(
206
+ addr: tuple[str, int],
207
+ *,
208
+ reuse_port: bool,
209
+) -> socket.socket:
210
+ family = socket.AF_INET
211
+ if is_valid_ipv6_address(addr[0]):
212
+ family = socket.AF_INET6
213
+
214
+ sock = socket.socket(family=family, type=socket.SOCK_STREAM)
215
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
216
+ if reuse_port:
217
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
218
+ sock.bind(addr)
219
+
220
+ return sock
221
+
222
+
223
+def create_server_unix_socket(path: str) -> socket.socket:
224
+ sock = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
225
+ sock.bind(path)
226
+ return sock
227
+
228
+
229
+def validate_api_server_args(args):
230
+ valid_tool_parses = ToolParserManager.list_registered()
231
+ if args.enable_auto_tool_choice and args.tool_call_parser not in valid_tool_parses:
232
+ raise KeyError(
233
+ f"invalid tool call parser: {args.tool_call_parser} "
234
+ f"(chose from {{ {','.join(valid_tool_parses)} }})"
235
+ )
236
+
237
+ valid_reasoning_parsers = ReasoningParserManager.list_registered()
238
+ if (
239
+ reasoning_parser := args.structured_outputs_config.reasoning_parser
240
+ ) and reasoning_parser not in valid_reasoning_parsers:
241
+ raise KeyError(
242
+ f"invalid reasoning parser: {reasoning_parser} "
243
+ f"(chose from {{ {','.join(valid_reasoning_parsers)} }})"
244
+ )
245
+
246
+
247
+@instrument(span_name="API server setup")
248
+def setup_server(args, *, reuse_port: bool):
249
+ """Validate API server args and create the server socket."""
250
+
251
+ log_version_and_model(logger, VLLM_VERSION, args.model)
252
+ log_non_default_args(args)
253
+
254
+ if args.tool_parser_plugin and len(args.tool_parser_plugin) > 3:
255
+ ToolParserManager.import_tool_parser(args.tool_parser_plugin)
256
+
257
+ if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3:
258
+ ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin)
259
+
260
+ validate_api_server_args(args)
261
+
262
+ # workaround to make sure that we bind the port before the engine is set up.
263
+ # This avoids race conditions with ray.
264
+ # see https://github.com/vllm-project/vllm/issues/8204
265
+ if args.uds:
266
+ sock = create_server_unix_socket(args.uds)
267
+ else:
268
+ sock_addr = (args.host or "", args.port)
269
+ sock = create_server_socket(sock_addr, reuse_port=reuse_port)
270
+
271
+ # workaround to avoid footguns where uvicorn drops requests with too
272
+ # many concurrent requests active
273
+ set_ulimit()
274
+
275
+ if args.uds:
276
+ listen_address = f"unix:{args.uds}"
277
+ else:
278
+ addr, port = sock_addr
279
+ is_ssl = args.ssl_keyfile and args.ssl_certfile
280
+ host_part = f"[{addr}]" if is_valid_ipv6_address(addr) else addr or "0.0.0.0"
281
+ listen_address = f"http{'s' if is_ssl else ''}://{host_part}:{port}"
282
+ return listen_address, sock
vllm/entrypoints/launchers/render/__init__.py
vllm/entrypoints/launchers/render/app_state.py
new
+104
@@ -0,0 +1,104 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+from argparse import Namespace
4
+
5
+from starlette.datastructures import State
6
+
7
+from vllm.config import VllmConfig
8
+from vllm.entrypoints.chat_utils import load_chat_template
9
+from vllm.entrypoints.openai.models.protocol import BaseModelPath
10
+from vllm.entrypoints.openai.models.serving import OpenAIModelRegistry
11
+from vllm.entrypoints.scale_out.factories import init_render_state
12
+from vllm.entrypoints.serve.tokenize.serving import ServingTokenization
13
+from vllm.entrypoints.serve.utils.request_logger import RequestLogger
14
+from vllm.plugins.endpoint_plugins.interface import init_endpoint_plugins_state
15
+from vllm.renderers import renderer_from_config
16
+from vllm.renderers.online_derenderer import OnlineDerenderer
17
+from vllm.renderers.online_renderer import OnlineRenderer
18
+
19
+
20
+async def init_render_app_state(
21
+ vllm_config: VllmConfig,
22
+ state: State,
23
+ args: Namespace,
24
+) -> None:
25
+ """Initialise FastAPI app state for a CPU-only render server.
26
+
27
+ Unlike :func:`init_app_state` this function does not require an
28
+ :class:`~vllm.engine.protocol.EngineClient`; it bootstraps the
29
+ preprocessing pipeline (renderer, input_processor)
30
+ directly from the :class:`~vllm.config.VllmConfig`.
31
+ """
32
+
33
+ served_model_names = args.served_model_name or [args.model]
34
+ model_registry = OpenAIModelRegistry(
35
+ model_config=vllm_config.model_config,
36
+ base_model_paths=[
37
+ BaseModelPath(name=name, model_path=args.model)
38
+ for name in served_model_names
39
+ ],
40
+ )
41
+
42
+ if args.enable_log_requests:
43
+ request_logger = RequestLogger(max_log_len=args.max_log_len)
44
+ else:
45
+ request_logger = None
46
+
47
+ renderer = renderer_from_config(vllm_config)
48
+ resolved_chat_template = load_chat_template(args.chat_template)
49
+
50
+ state.online_renderer = OnlineRenderer(
51
+ model_config=vllm_config.model_config,
52
+ renderer=renderer,
53
+ request_logger=request_logger,
54
+ chat_template=resolved_chat_template,
55
+ chat_template_content_format=args.chat_template_content_format,
56
+ trust_request_chat_template=args.trust_request_chat_template,
57
+ enable_auto_tools=args.enable_auto_tool_choice,
58
+ exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
59
+ tool_parser=args.tool_call_parser,
60
+ reasoning_parser=args.reasoning_parser,
61
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
62
+ log_error_stack=args.log_error_stack,
63
+ )
64
+ state.online_renderer.warmup()
65
+
66
+ state.online_derenderer = OnlineDerenderer(
67
+ model_config=vllm_config.model_config,
68
+ renderer=renderer,
69
+ request_logger=request_logger,
70
+ chat_template=resolved_chat_template,
71
+ chat_template_content_format=args.chat_template_content_format,
72
+ trust_request_chat_template=args.trust_request_chat_template,
73
+ enable_auto_tools=args.enable_auto_tool_choice,
74
+ exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
75
+ tool_parser=args.tool_call_parser,
76
+ reasoning_parser=args.reasoning_parser,
77
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
78
+ log_error_stack=args.log_error_stack,
79
+ )
80
+
81
+ state.openai_serving_models = model_registry
82
+ state.serving_tokenization = ServingTokenization(
83
+ model_registry,
84
+ state.online_renderer,
85
+ request_logger=request_logger,
86
+ chat_template=resolved_chat_template,
87
+ chat_template_content_format=args.chat_template_content_format,
88
+ default_chat_template_kwargs=args.default_chat_template_kwargs,
89
+ trust_request_chat_template=args.trust_request_chat_template,
90
+ )
91
+
92
+ init_render_state(state, request_logger)
93
+
94
+ state.vllm_config = vllm_config
95
+ # Disable stats logging — there is no engine to poll.
96
+ state.log_stats = False
97
+ state.engine_client = None
98
+ state.args = args
99
+ state.enable_server_load_tracking = False
100
+ state.server_load_metrics = 0
101
+
102
+ # No `EngineClient` exists for the render server, so plugins get `None` and
103
+ # must handle it themselves (see `EndpointPlugin.init_state`).
104
+ await init_endpoint_plugins_state(None, state, args)
vllm/entrypoints/launchers/render/entry.py
new
+120
@@ -0,0 +1,120 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+import argparse
4
+import asyncio
5
+import signal
6
+import socket
7
+from argparse import Namespace
8
+
9
+from vllm import AsyncEngineArgs, envs
10
+from vllm.config import VllmConfig
11
+from vllm.logger import init_logger
12
+
13
+from ..app import build_app
14
+from ..launcher import serve_http, setup_server
15
+from ..utils.server_utils import get_uvicorn_log_config
16
+from .app_state import init_render_app_state
17
+
18
+logger = init_logger("vllm.entrypoints.launchers.render.entry")
19
+
20
+
21
+async def build_and_serve_renderer(
22
+ vllm_config: VllmConfig,
23
+ listen_address: str,
24
+ sock: socket.socket,
25
+ args: Namespace,
26
+ **uvicorn_kwargs,
27
+) -> asyncio.Task:
28
+ """Build FastAPI app for a CPU-only render server, initialize state, and
29
+ start serving.
30
+
31
+ Returns the shutdown task for the caller to await.
32
+ """
33
+
34
+ # Get uvicorn log config (from file or with endpoint filter)
35
+ log_config = get_uvicorn_log_config(args)
36
+ if log_config is not None:
37
+ uvicorn_kwargs["log_config"] = log_config
38
+
39
+ app = build_app(args, ("render",))
40
+ await init_render_app_state(vllm_config, app.state, args)
41
+
42
+ logger.info("Starting vLLM server on %s", listen_address)
43
+
44
+ return await serve_http(
45
+ app,
46
+ sock=sock,
47
+ enable_ssl_refresh=args.enable_ssl_refresh,
48
+ host=args.host,
49
+ port=args.port,
50
+ log_level=args.uvicorn_log_level,
51
+ # NOTE: When the 'disable_uvicorn_access_log' value is True,
52
+ # no access log will be output.
53
+ access_log=not args.disable_uvicorn_access_log,
54
+ timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE,
55
+ ssl_keyfile=args.ssl_keyfile,
56
+ ssl_certfile=args.ssl_certfile,
57
+ ssl_ca_certs=args.ssl_ca_certs,
58
+ ssl_cert_reqs=args.ssl_cert_reqs,
59
+ ssl_ciphers=args.ssl_ciphers,
60
+ h11_max_incomplete_event_size=args.h11_max_incomplete_event_size,
61
+ h11_max_header_count=args.h11_max_header_count,
62
+ **uvicorn_kwargs,
63
+ )
64
+
65
+
66
+async def run_launch_fastapi(args: argparse.Namespace) -> None:
67
+ """Run the online serving layer with FastAPI (no GPU inference)."""
68
+
69
+ # Interrupt initialization if SIGTERM arrives before uvicorn installs
70
+ # its own signal handlers. Once uvicorn is running it replaces this.
71
+ def _interrupt_init(*_) -> None:
72
+ raise KeyboardInterrupt("terminated")
73
+
74
+ signal.signal(signal.SIGTERM, _interrupt_init)
75
+
76
+ # 1. Socket binding
77
+ listen_address, sock = setup_server(args, reuse_port=False)
78
+
79
+ # 2. Build and serve the API server
80
+ engine_args = AsyncEngineArgs.from_cli_args(args)
81
+ model_config = engine_args.create_model_config()
82
+
83
+ # Render servers preprocess data only — no inference, no quantized kernels.
84
+ # Clear quantization so VllmConfig skips quant dtype/capability validation.
85
+ model_config.quantization = None
86
+
87
+ # Render servers never allocate KV cache; suppress the spurious CPU KV
88
+ # cache space warning from CpuPlatform.check_and_update_config.
89
+ envs.VLLM_CPU_KVCACHE_SPACE = 0
90
+
91
+ vllm_config = VllmConfig(model_config=model_config)
92
+ shutdown_task = await build_and_serve_renderer(
93
+ vllm_config, listen_address, sock, args
94
+ )
95
+ try:
96
+ await shutdown_task
97
+ finally:
98
+ sock.close()
99
+
100
+
101
+if __name__ == "__main__":
102
+ import uvloop
103
+
104
+ from vllm.entrypoints.openai.cli_args import (
105
+ make_arg_parser,
106
+ validate_parsed_serve_args,
107
+ )
108
+ from vllm.entrypoints.serve.utils.api_utils import cli_env_setup
109
+ from vllm.utils.argparse_utils import FlexibleArgumentParser
110
+
111
+ cli_env_setup()
112
+ parser = FlexibleArgumentParser(
113
+ description="Starts a GPU-less rendering server "
114
+ "for preprocessing and postprocessing only"
115
+ )
116
+ parser = make_arg_parser(parser)
117
+ args = parser.parse_args()
118
+ validate_parsed_serve_args(args)
119
+
120
+ uvloop.run(run_launch_fastapi(args))
vllm/entrypoints/launchers/utils/__init__.py
vllm/entrypoints/launchers/utils/constants.py
renamed
-5
@@ -1,12 +1,7 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""
4
-Shared constants for vLLM entrypoints.
5
-"""
3
4
# HTTP header limits for h11 parser
5
# These constants help mitigate header abuse attacks
6
H11_MAX_INCOMPLETE_EVENT_SIZE_DEFAULT = 4194304 # 4 MB
7
H11_MAX_HEADER_COUNT_DEFAULT = 256
11
-
12
-MCP_PREFIX = "mcp_"
vllm/entrypoints/launchers/utils/server_utils.py
renamed
+1
-1
@@ -12,7 +12,7 @@ from vllm.engine.protocol import EngineClient
12
from vllm.logger import init_logger
13
from vllm.utils.gc_utils import freeze_gc_heap
14
15
-logger = init_logger("vllm.entrypoints.openai.server_utils")
15
+logger = init_logger(__name__)
16
17
18
def load_log_config(log_config_file: str | None) -> dict | None:
vllm/entrypoints/launchers/utils/ssl.py
renamed
vllm/entrypoints/openai/api_server.py
+46
-657
@@ -1,670 +1,59 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-import asyncio
4
-import multiprocessing
5
-import multiprocessing.forkserver as forkserver
6
-import os
7
-import signal
8
-import socket
9
-import tempfile
3
import warnings
11
-from argparse import Namespace
12
-from collections.abc import AsyncIterator
13
-from contextlib import asynccontextmanager
14
-from typing import Any, cast
4
16
-import uvloop
17
-from fastapi import FastAPI
18
-from starlette.datastructures import State
19
-
20
-import vllm.envs as envs
21
-from vllm.config import ModelConfig, VllmConfig
22
-from vllm.engine.arg_utils import AsyncEngineArgs
23
-from vllm.engine.protocol import EngineClient
24
-from vllm.entrypoints.chat_utils import load_chat_template
25
-from vllm.entrypoints.launcher import serve_http
5
+from vllm.entrypoints.launchers.api_server.app_state import init_app_state
6
+from vllm.entrypoints.launchers.api_server.entry import (
7
+ build_and_serve,
8
+ build_async_engine_client,
9
+ build_async_engine_client_from_engine_args,
10
+ run_server,
11
+ run_server_worker,
12
+)
13
from vllm.entrypoints.launchers.api_server.routers import register_api_routers
27
-from vllm.entrypoints.openai.cli_args import make_arg_parser, validate_parsed_serve_args
28
-from vllm.entrypoints.openai.models.protocol import BaseModelPath
29
-from vllm.entrypoints.openai.models.serving import OpenAIServingModels
30
-from vllm.entrypoints.serve.exception_handling.register import init_exception_handler
31
-from vllm.entrypoints.serve.middleware.register import init_entrypoints_middleware
32
-from vllm.entrypoints.serve.sagemaker.api_router import sagemaker_standards_bootstrap
33
-from vllm.entrypoints.serve.tokenize.serving import ServingTokenization
34
-from vllm.entrypoints.serve.utils.api_utils import (
35
- cli_env_setup,
36
- log_non_default_args,
37
- log_version_and_model,
38
- process_lora_modules,
14
+from vllm.entrypoints.launchers.app import build_app
15
+from vllm.entrypoints.launchers.launcher import (
16
+ create_server_socket,
17
+ create_server_unix_socket,
18
+ setup_server,
19
+ validate_api_server_args,
20
)
40
-from vllm.entrypoints.serve.utils.request_logger import RequestLogger
41
-from vllm.entrypoints.serve.utils.server_utils import (
42
- get_uvicorn_log_config,
43
- lifespan,
21
+from vllm.entrypoints.launchers.render.app_state import init_render_app_state
22
+from vllm.entrypoints.launchers.render.entry import build_and_serve_renderer
23
+
24
+warnings.warn(
25
+ "`vllm.entrypoints.openai.api_server` is deprecated and will likely be"
26
+ "unsupported in a future version. Use the corresponding function from "
27
+ "`vllm.entrypoints.launchers` instead.",
28
+ DeprecationWarning,
29
+ stacklevel=1,
30
)
45
-from vllm.logger import init_logger
46
-from vllm.reasoning import ReasoningParserManager
47
-from vllm.renderers.online_derenderer import OnlineDerenderer
48
-from vllm.renderers.online_renderer import OnlineRenderer
49
-from vllm.tasks import POOLING_TASKS, SupportedTask
50
-from vllm.tool_parsers import ToolParserManager
51
-from vllm.tracing import instrument
52
-from vllm.usage.usage_lib import UsageContext
53
-from vllm.utils.argparse_utils import FlexibleArgumentParser
54
-from vllm.utils.network_utils import is_valid_ipv6_address
55
-from vllm.utils.system_utils import decorate_logs, set_ulimit
56
-from vllm.version import __version__ as VLLM_VERSION
57
-
58
-prometheus_multiproc_dir: tempfile.TemporaryDirectory
59
-
60
-# Cannot use __name__ (https://github.com/vllm-project/vllm/pull/4765)
61
-logger = init_logger("vllm.entrypoints.openai.api_server")
62
-
63
-_FALLBACK_SUPPORTED_TASKS: tuple[SupportedTask, ...] = ("generate",)
64
-
65
-
66
-def _attach_endpoint_plugins(
67
- app: FastAPI, supported_tasks: tuple["SupportedTask", ...]
68
-) -> None:
69
- """Phase A of endpoint plugin wiring: discover, gate and attach routes.
70
-
71
- Attached last after all core routers. This is so endpoint plugin routes can
72
- shadow core routes with the same path (see `EndpointPlugin.attach_router`
73
- docstring). No-ops when no plugins are discovered/allowlisted.
74
- """
75
- from vllm.plugins import load_endpoint_plugins
76
-
77
- endpoint_plugins = load_endpoint_plugins(supported_tasks)
78
- for plugin in endpoint_plugins:
79
- plugin.attach_router(app)
80
- app.state.endpoint_plugins = endpoint_plugins
81
-
82
-
83
-async def _init_endpoint_plugins_state(
84
- engine_client: EngineClient | None, state: State, args: Namespace
85
-) -> None:
86
- """Phase B of endpoint plugin wiring: initialize per app plugin state.
87
-
88
- `state.endpoint_plugins` is set by `_attach_endpoint_plugins` (Phase A)
89
- in `build_app`. Some `init_app_state` callers (e.g. `run_batch.py`)
90
- build their own bare `State` without going through `build_app`. As a result
91
- `endpoint_plugins` may be absent and are treated that the same as "none attached".
92
-
93
- `engine_client` is `None` for the CPU only render server which has no
94
- engine (see `init_render_app_state`). Plugins must handle a `None`
95
- `engine_client` themselves (see `EndpointPlugin.init_state`).
96
- """
97
- for plugin in getattr(state, "endpoint_plugins", []):
98
- await plugin.init_state(engine_client, state, args)
99
-
100
-
101
-@asynccontextmanager
102
-async def build_async_engine_client(
103
- args: Namespace,
104
- *,
105
- usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,
106
- client_config: dict[str, Any] | None = None,
107
-) -> AsyncIterator[EngineClient]:
108
- if os.getenv("VLLM_WORKER_MULTIPROC_METHOD") == "forkserver":
109
- # The executor is expected to be mp.
110
- # Pre-import heavy modules in the forkserver process
111
- logger.debug("Setup forkserver with pre-imports")
112
- multiprocessing.set_start_method("forkserver")
113
- multiprocessing.set_forkserver_preload(["vllm.v1.engine.async_llm"])
114
- forkserver.ensure_running()
115
- logger.debug("Forkserver setup complete!")
116
-
117
- # Context manager to handle engine_client lifecycle
118
- # Ensures everything is shutdown and cleaned up on error/exit
119
- engine_args = AsyncEngineArgs.from_cli_args(args)
120
- if client_config:
121
- engine_args._api_process_count = client_config.get("client_count", 1)
122
- engine_args._api_process_rank = client_config.get("client_index", 0)
123
-
124
- async with build_async_engine_client_from_engine_args(
125
- engine_args,
126
- usage_context=usage_context,
127
- client_config=client_config,
128
- ) as engine:
129
- yield engine
130
-
131
-
132
-@asynccontextmanager
133
-async def build_async_engine_client_from_engine_args(
134
- engine_args: AsyncEngineArgs,
135
- *,
136
- usage_context: UsageContext = UsageContext.OPENAI_API_SERVER,
137
- client_config: dict[str, Any] | None = None,
138
-) -> AsyncIterator[EngineClient]:
139
- """
140
- Create EngineClient, either:
141
- - in-process using the AsyncLLMEngine Directly
142
- - multiprocess using AsyncLLMEngine RPC
143
-
144
- Returns the Client or None if the creation failed.
145
- """
146
-
147
- # Create the EngineConfig (determines if we can use V1).
148
- vllm_config = engine_args.create_engine_config(usage_context=usage_context)
149
-
150
- from vllm.v1.engine.async_llm import AsyncLLM
151
-
152
- async_llm: AsyncLLM | None = None
153
-
154
- # Don't mutate the input client_config
155
- client_config = dict(client_config) if client_config else {}
156
- client_count = client_config.pop("client_count", 1)
157
- client_index = client_config.pop("client_index", 0)
158
-
159
- try:
160
- async_llm = AsyncLLM.from_vllm_config(
161
- vllm_config=vllm_config,
162
- usage_context=usage_context,
163
- enable_log_requests=engine_args.enable_log_requests,
164
- aggregate_engine_logging=engine_args.aggregate_engine_logging,
165
- disable_log_stats=engine_args.disable_log_stats,
166
- client_addresses=client_config,
167
- client_count=client_count,
168
- client_index=client_index,
169
- )
170
-
171
- # Don't keep the dummy data in memory
172
- assert async_llm is not None
173
- await async_llm.reset_mm_cache()
174
-
175
- yield async_llm
176
- finally:
177
- if async_llm:
178
- async_llm.shutdown(timeout=vllm_config.shutdown_timeout)
179
-
180
-
181
-def build_app(
182
- args: Namespace,
183
- supported_tasks: tuple["SupportedTask", ...] | None = None,
184
- model_config: ModelConfig | None = None,
185
-) -> FastAPI:
186
- if supported_tasks is None:
187
- warnings.warn(
188
- "The 'supported_tasks' parameter was not provided to "
189
- "build_app and will be required in a future version. "
190
- "Defaulting to ('generate',).",
191
- DeprecationWarning,
192
- stacklevel=2,
193
- )
194
- supported_tasks = _FALLBACK_SUPPORTED_TASKS
195
-
196
- if args.disable_fastapi_docs:
197
- app = FastAPI(
198
- openapi_url=None, docs_url=None, redoc_url=None, lifespan=lifespan
199
- )
200
- elif args.enable_offline_docs:
201
- app = FastAPI(docs_url=None, redoc_url=None, lifespan=lifespan)
202
- else:
203
- app = FastAPI(lifespan=lifespan)
204
- app.state.args = args
205
- app.root_path = args.root_path
206
-
207
- register_api_routers(args, app, supported_tasks, model_config)
208
-
209
- # Endpoint plugins are attached last so their routes are registered after all core
210
- # routers. This runs even for the CPU only render server. A plugin eligible for
211
- # the `render` task still gets its routes registered. It receives
212
- # `engine_client=None` at Phase B (see `_init_endpoint_plugins_state`).
213
- _attach_endpoint_plugins(app, supported_tasks)
214
-
215
- init_exception_handler(app)
216
- init_entrypoints_middleware(args, app, supported_tasks)
217
- app = sagemaker_standards_bootstrap(app)
218
- return app
219
-
220
-
221
-async def init_app_state(
222
- engine_client: EngineClient,
223
- state: State,
224
- args: Namespace,
225
- supported_tasks: tuple["SupportedTask", ...] | None = None,
226
-) -> None:
227
- vllm_config = engine_client.vllm_config
228
-
229
- if args.tool_call_parser is not None:
230
- from vllm.parser.metrics import init_parser_metrics
231
-
232
- init_parser_metrics(
233
- model_name=cast(str, vllm_config.model_config.served_model_name)
234
- )
235
-
236
- if supported_tasks is None:
237
- warnings.warn(
238
- "The 'supported_tasks' parameter was not provided to "
239
- "init_app_state and will be required in a future version. "
240
- "Please pass 'supported_tasks' explicitly.",
241
- DeprecationWarning,
242
- stacklevel=2,
243
- )
244
- supported_tasks = _FALLBACK_SUPPORTED_TASKS
245
-
246
- if args.served_model_name is not None:
247
- served_model_names = args.served_model_name
248
- else:
249
- served_model_names = [args.model]
250
-
251
- if args.enable_log_requests:
252
- request_logger = RequestLogger(max_log_len=args.max_log_len)
253
- else:
254
- request_logger = None
255
-
256
- base_model_paths = [
257
- BaseModelPath(name=name, model_path=args.model) for name in served_model_names
258
- ]
259
-
260
- state.engine_client = engine_client
261
- state.log_stats = not args.disable_log_stats
262
- state.vllm_config = vllm_config
263
- state.args = args
264
- resolved_chat_template = load_chat_template(args.chat_template)
265
-
266
- # Merge default_mm_loras into the static lora_modules
267
- default_mm_loras = (
268
- vllm_config.lora_config.default_mm_loras
269
- if vllm_config.lora_config is not None
270
- else {}
271
- )
272
- lora_modules = process_lora_modules(args.lora_modules, default_mm_loras)
273
-
274
- state.openai_serving_models = OpenAIServingModels(
275
- engine_client=engine_client,
276
- base_model_paths=base_model_paths,
277
- lora_modules=lora_modules,
278
- )
279
- await state.openai_serving_models.init_static_loras()
280
-
281
- state.online_renderer = OnlineRenderer(
282
- model_config=engine_client.model_config,
283
- renderer=engine_client.renderer,
284
- request_logger=request_logger,
285
- chat_template=resolved_chat_template,
286
- chat_template_content_format=args.chat_template_content_format,
287
- trust_request_chat_template=args.trust_request_chat_template,
288
- enable_auto_tools=args.enable_auto_tool_choice,
289
- exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
290
- tool_parser=args.tool_call_parser,
291
- reasoning_parser=args.structured_outputs_config.reasoning_parser,
292
- default_chat_template_kwargs=args.default_chat_template_kwargs,
293
- log_error_stack=args.log_error_stack,
294
- )
295
- state.online_renderer.warmup()
296
-
297
- state.online_derenderer = OnlineDerenderer(
298
- model_config=engine_client.model_config,
299
- renderer=engine_client.renderer,
300
- request_logger=request_logger,
301
- chat_template=resolved_chat_template,
302
- chat_template_content_format=args.chat_template_content_format,
303
- trust_request_chat_template=args.trust_request_chat_template,
304
- enable_auto_tools=args.enable_auto_tool_choice,
305
- exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
306
- tool_parser=args.tool_call_parser,
307
- reasoning_parser=args.structured_outputs_config.reasoning_parser,
308
- default_chat_template_kwargs=args.default_chat_template_kwargs,
309
- log_error_stack=args.log_error_stack,
310
- )
311
-
312
- state.serving_tokenization = ServingTokenization(
313
- state.openai_serving_models,
314
- state.online_renderer,
315
- request_logger=request_logger,
316
- chat_template=resolved_chat_template,
317
- chat_template_content_format=args.chat_template_content_format,
318
- default_chat_template_kwargs=args.default_chat_template_kwargs,
319
- trust_request_chat_template=args.trust_request_chat_template,
320
- )
321
-
322
- if "generate" in supported_tasks:
323
- from vllm.entrypoints.generate.api_router import init_generate_state
324
-
325
- await init_generate_state(
326
- engine_client, state, args, request_logger, supported_tasks
327
- )
328
-
329
- from vllm.entrypoints.scale_out.factories import init_scale_out_state
330
-
331
- init_scale_out_state(state, args, engine_client, request_logger)
332
-
333
- if "transcription" in supported_tasks or "realtime" in supported_tasks:
334
- from vllm.entrypoints.speech_to_text.factories import init_speech_to_text_state
335
-
336
- init_speech_to_text_state(
337
- engine_client, state, args, request_logger, supported_tasks
338
- )
339
-
340
- if any(task in POOLING_TASKS for task in supported_tasks):
341
- from vllm.entrypoints.pooling.factories import init_pooling_state
342
-
343
- init_pooling_state(engine_client, state, args, request_logger, supported_tasks)
344
-
345
- await _init_endpoint_plugins_state(engine_client, state, args)
346
-
347
- state.enable_server_load_tracking = args.enable_server_load_tracking
348
- state.server_load_metrics = 0
349
-
350
-
351
-async def init_render_app_state(
352
- vllm_config: VllmConfig,
353
- state: State,
354
- args: Namespace,
355
-) -> None:
356
- """Initialise FastAPI app state for a CPU-only render server.
357
-
358
- Unlike :func:`init_app_state` this function does not require an
359
- :class:`~vllm.engine.protocol.EngineClient`; it bootstraps the
360
- preprocessing pipeline (renderer, input_processor)
361
- directly from the :class:`~vllm.config.VllmConfig`.
362
- """
363
- from vllm.entrypoints.chat_utils import load_chat_template
364
- from vllm.entrypoints.openai.models.serving import OpenAIModelRegistry
365
- from vllm.renderers import renderer_from_config
366
- from vllm.renderers.online_renderer import OnlineRenderer
367
-
368
- served_model_names = args.served_model_name or [args.model]
369
- model_registry = OpenAIModelRegistry(
370
- model_config=vllm_config.model_config,
371
- base_model_paths=[
372
- BaseModelPath(name=name, model_path=args.model)
373
- for name in served_model_names
374
- ],
375
- )
376
-
377
- if args.enable_log_requests:
378
- request_logger = RequestLogger(max_log_len=args.max_log_len)
379
- else:
380
- request_logger = None
381
-
382
- renderer = renderer_from_config(vllm_config)
383
- resolved_chat_template = load_chat_template(args.chat_template)
384
-
385
- state.online_renderer = OnlineRenderer(
386
- model_config=vllm_config.model_config,
387
- renderer=renderer,
388
- request_logger=request_logger,
389
- chat_template=resolved_chat_template,
390
- chat_template_content_format=args.chat_template_content_format,
391
- trust_request_chat_template=args.trust_request_chat_template,
392
- enable_auto_tools=args.enable_auto_tool_choice,
393
- exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
394
- tool_parser=args.tool_call_parser,
395
- reasoning_parser=args.reasoning_parser,
396
- default_chat_template_kwargs=args.default_chat_template_kwargs,
397
- log_error_stack=args.log_error_stack,
398
- )
399
- state.online_renderer.warmup()
400
-
401
- state.online_derenderer = OnlineDerenderer(
402
- model_config=vllm_config.model_config,
403
- renderer=renderer,
404
- request_logger=request_logger,
405
- chat_template=resolved_chat_template,
406
- chat_template_content_format=args.chat_template_content_format,
407
- trust_request_chat_template=args.trust_request_chat_template,
408
- enable_auto_tools=args.enable_auto_tool_choice,
409
- exclude_tools_when_tool_choice_none=args.exclude_tools_when_tool_choice_none,
410
- tool_parser=args.tool_call_parser,
411
- reasoning_parser=args.reasoning_parser,
412
- default_chat_template_kwargs=args.default_chat_template_kwargs,
413
- log_error_stack=args.log_error_stack,
414
- )
415
-
416
- state.openai_serving_models = model_registry
417
- state.serving_tokenization = ServingTokenization(
418
- model_registry,
419
- state.online_renderer,
420
- request_logger=request_logger,
421
- chat_template=resolved_chat_template,
422
- chat_template_content_format=args.chat_template_content_format,
423
- default_chat_template_kwargs=args.default_chat_template_kwargs,
424
- trust_request_chat_template=args.trust_request_chat_template,
425
- )
426
-
427
- from vllm.entrypoints.scale_out.factories import init_render_state
428
-
429
- init_render_state(state, request_logger)
430
-
431
- state.vllm_config = vllm_config
432
- # Disable stats logging — there is no engine to poll.
433
- state.log_stats = False
434
- state.engine_client = None
435
- state.args = args
436
- state.enable_server_load_tracking = False
437
- state.server_load_metrics = 0
438
-
439
- # No `EngineClient` exists for the render server, so plugins get `None` and
440
- # must handle it themselves (see `EndpointPlugin.init_state`).
441
- await _init_endpoint_plugins_state(None, state, args)
442
-
443
-
444
-def create_server_socket(
445
- addr: tuple[str, int],
446
- *,
447
- reuse_port: bool,
448
-) -> socket.socket:
449
- family = socket.AF_INET
450
- if is_valid_ipv6_address(addr[0]):
451
- family = socket.AF_INET6
452
-
453
- sock = socket.socket(family=family, type=socket.SOCK_STREAM)
454
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
455
- if reuse_port:
456
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
457
- sock.bind(addr)
458
-
459
- return sock
460
-
461
-
462
-def create_server_unix_socket(path: str) -> socket.socket:
463
- sock = socket.socket(family=socket.AF_UNIX, type=socket.SOCK_STREAM)
464
- sock.bind(path)
465
- return sock
466
-
467
-
468
-def validate_api_server_args(args):
469
- valid_tool_parses = ToolParserManager.list_registered()
470
- if args.enable_auto_tool_choice and args.tool_call_parser not in valid_tool_parses:
471
- raise KeyError(
472
- f"invalid tool call parser: {args.tool_call_parser} "
473
- f"(chose from {{ {','.join(valid_tool_parses)} }})"
474
- )
475
-
476
- valid_reasoning_parsers = ReasoningParserManager.list_registered()
477
- if (
478
- reasoning_parser := args.structured_outputs_config.reasoning_parser
479
- ) and reasoning_parser not in valid_reasoning_parsers:
480
- raise KeyError(
481
- f"invalid reasoning parser: {reasoning_parser} "
482
- f"(chose from {{ {','.join(valid_reasoning_parsers)} }})"
483
- )
484
-
485
-
486
-@instrument(span_name="API server setup")
487
-def setup_server(args, *, reuse_port: bool):
488
- """Validate API server args and create the server socket."""
489
-
490
- log_version_and_model(logger, VLLM_VERSION, args.model)
491
- log_non_default_args(args)
492
-
493
- if args.tool_parser_plugin and len(args.tool_parser_plugin) > 3:
494
- ToolParserManager.import_tool_parser(args.tool_parser_plugin)
495
-
496
- if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3:
497
- ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin)
498
-
499
- validate_api_server_args(args)
500
-
501
- # workaround to make sure that we bind the port before the engine is set up.
502
- # This avoids race conditions with ray.
503
- # see https://github.com/vllm-project/vllm/issues/8204
504
- if args.uds:
505
- sock = create_server_unix_socket(args.uds)
506
- else:
507
- sock_addr = (args.host or "", args.port)
508
- sock = create_server_socket(sock_addr, reuse_port=reuse_port)
509
-
510
- # workaround to avoid footguns where uvicorn drops requests with too
511
- # many concurrent requests active
512
- set_ulimit()
513
-
514
- if args.uds:
515
- listen_address = f"unix:{args.uds}"
516
- else:
517
- addr, port = sock_addr
518
- is_ssl = args.ssl_keyfile and args.ssl_certfile
519
- host_part = f"[{addr}]" if is_valid_ipv6_address(addr) else addr or "0.0.0.0"
520
- listen_address = f"http{'s' if is_ssl else ''}://{host_part}:{port}"
521
- return listen_address, sock
522
-
523
-
524
-async def build_and_serve(
525
- engine_client: EngineClient,
526
- listen_address: str,
527
- sock: socket.socket,
528
- args: Namespace,
529
- **uvicorn_kwargs,
530
-) -> asyncio.Task:
531
- """Build FastAPI app, initialize state, and start serving.
532
-
533
- Returns the shutdown task for the caller to await.
534
- """
535
-
536
- # Get uvicorn log config (from file or with endpoint filter)
537
- log_config = get_uvicorn_log_config(args)
538
- if log_config is not None:
539
- uvicorn_kwargs["log_config"] = log_config
540
-
541
- supported_tasks = await engine_client.get_supported_tasks()
542
- model_config = engine_client.model_config
543
-
544
- logger.info("Supported tasks: %s", supported_tasks)
545
- app = build_app(args, supported_tasks, model_config)
546
- await init_app_state(engine_client, app.state, args, supported_tasks)
547
-
548
- logger.info("Starting vLLM server on %s", listen_address)
549
-
550
- return await serve_http(
551
- app,
552
- sock=sock,
553
- enable_ssl_refresh=args.enable_ssl_refresh,
554
- host=args.host,
555
- port=args.port,
556
- log_level=args.uvicorn_log_level,
557
- # NOTE: When the 'disable_uvicorn_access_log' value is True,
558
- # no access log will be output.
559
- access_log=not args.disable_uvicorn_access_log,
560
- timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE,
561
- ssl_keyfile=args.ssl_keyfile,
562
- ssl_certfile=args.ssl_certfile,
563
- ssl_ca_certs=args.ssl_ca_certs,
564
- ssl_cert_reqs=args.ssl_cert_reqs,
565
- ssl_ciphers=args.ssl_ciphers,
566
- h11_max_incomplete_event_size=args.h11_max_incomplete_event_size,
567
- h11_max_header_count=args.h11_max_header_count,
568
- **uvicorn_kwargs,
569
- )
570
-
571
-
572
-async def build_and_serve_renderer(
573
- vllm_config: VllmConfig,
574
- listen_address: str,
575
- sock: socket.socket,
576
- args: Namespace,
577
- **uvicorn_kwargs,
578
-) -> asyncio.Task:
579
- """Build FastAPI app for a CPU-only render server, initialize state, and
580
- start serving.
581
-
582
- Returns the shutdown task for the caller to await.
583
- """
584
-
585
- # Get uvicorn log config (from file or with endpoint filter)
586
- log_config = get_uvicorn_log_config(args)
587
- if log_config is not None:
588
- uvicorn_kwargs["log_config"] = log_config
589
-
590
- app = build_app(args, ("render",))
591
- await init_render_app_state(vllm_config, app.state, args)
592
-
593
- logger.info("Starting vLLM server on %s", listen_address)
594
-
595
- return await serve_http(
596
- app,
597
- sock=sock,
598
- enable_ssl_refresh=args.enable_ssl_refresh,
599
- host=args.host,
600
- port=args.port,
601
- log_level=args.uvicorn_log_level,
602
- # NOTE: When the 'disable_uvicorn_access_log' value is True,
603
- # no access log will be output.
604
- access_log=not args.disable_uvicorn_access_log,
605
- timeout_keep_alive=envs.VLLM_HTTP_TIMEOUT_KEEP_ALIVE,
606
- ssl_keyfile=args.ssl_keyfile,
607
- ssl_certfile=args.ssl_certfile,
608
- ssl_ca_certs=args.ssl_ca_certs,
609
- ssl_cert_reqs=args.ssl_cert_reqs,
610
- ssl_ciphers=args.ssl_ciphers,
611
- h11_max_incomplete_event_size=args.h11_max_incomplete_event_size,
612
- h11_max_header_count=args.h11_max_header_count,
613
- **uvicorn_kwargs,
614
- )
615
-
616
-
617
-async def run_server(args, **uvicorn_kwargs) -> None:
618
- """Run a single-worker API server."""
619
-
620
- decorate_logs("APIServer", skip_if_decorated=True)
621
-
622
- # Interrupt initialization if SIGTERM arrives before uvicorn installs its
623
- # own signal handlers. Once uvicorn is running it replaces this.
624
- def _interrupt_init(*_) -> None:
625
- raise KeyboardInterrupt("terminated")
626
-
627
- signal.signal(signal.SIGTERM, _interrupt_init)
628
-
629
- listen_address, sock = setup_server(args, reuse_port=False)
630
- await run_server_worker(listen_address, sock, args, **uvicorn_kwargs)
631
-
632
-
633
-async def run_server_worker(
634
- listen_address, sock, args, client_config=None, **uvicorn_kwargs
635
-) -> None:
636
- """Run a single API server worker."""
637
-
638
- if args.tool_parser_plugin and len(args.tool_parser_plugin) > 3:
639
- ToolParserManager.import_tool_parser(args.tool_parser_plugin)
640
-
641
- if args.reasoning_parser_plugin and len(args.reasoning_parser_plugin) > 3:
642
- ReasoningParserManager.import_reasoning_parser(args.reasoning_parser_plugin)
31
644
- async with build_async_engine_client(
645
- args,
646
- client_config=client_config,
647
- ) as engine_client:
648
- shutdown_task = await build_and_serve(
649
- engine_client, listen_address, sock, args, **uvicorn_kwargs
650
- )
651
- # NB: Await server shutdown only after the backend context is exited
652
- try:
653
- await shutdown_task
654
- finally:
655
- sock.close()
32
33
+__all__ = [
34
+ "build_async_engine_client",
35
+ "build_async_engine_client_from_engine_args",
36
+ "build_app",
37
+ "init_app_state",
38
+ "init_render_app_state",
39
+ "create_server_socket",
40
+ "create_server_unix_socket",
41
+ "validate_api_server_args",
42
+ "setup_server",
43
+ "build_and_serve",
44
+ "build_and_serve_renderer",
45
+ "run_server",
46
+ "run_server_worker",
47
+ "register_api_routers",
48
+]
49
50
if __name__ == "__main__":
659
- # NOTE(simon):
660
- # This section should be in sync with vllm/entrypoints/cli/main.py for CLI
661
- # entrypoints.
662
- cli_env_setup()
663
- parser = FlexibleArgumentParser(
664
- description="vLLM OpenAI-Compatible RESTful API server."
51
+ warnings.warn(
52
+ "The `python -m vllm.entrypoints.openai.api_server` command is deprecated "
53
+ "and may be removed in a future release. Please use `vllm server` instead.",
54
+ DeprecationWarning,
55
+ stacklevel=1,
56
)
666
- parser = make_arg_parser(parser)
667
- args = parser.parse_args()
668
- validate_parsed_serve_args(args)
57
+ from vllm.entrypoints.launchers.api_server.entry import main
58
670
- uvloop.run(run_server(args))
59
+ main()
vllm/entrypoints/openai/cli_args.py
+3
-3
@@ -20,11 +20,11 @@ from vllm.entrypoints.chat_utils import (
20
ChatTemplateContentFormatOption,
21
validate_chat_template,
22
)
23
-from vllm.entrypoints.openai.models.protocol import LoRAModulePath
24
-from vllm.entrypoints.serve.utils.constants import (
23
+from vllm.entrypoints.launchers.utils.constants import (
24
H11_MAX_HEADER_COUNT_DEFAULT,
25
H11_MAX_INCOMPLETE_EVENT_SIZE_DEFAULT,
26
)
27
+from vllm.entrypoints.openai.models.protocol import LoRAModulePath
28
from vllm.tool_parsers import ToolParserManager
29
from vllm.utils.argparse_utils import FlexibleArgumentParser
30
@@ -444,6 +444,6 @@ def validate_parsed_serve_args(args: argparse.Namespace):
444
445
def create_parser_for_docs() -> FlexibleArgumentParser:
446
parser_for_docs = FlexibleArgumentParser(
447
- prog="-m vllm.entrypoints.openai.api_server"
447
+ prog="-m vllm.entrypoints.launchers.api_server.entry"
448
)
449
return make_arg_parser(parser_for_docs)
vllm/entrypoints/openai/dp_supervisor.py
+2
-2
@@ -23,7 +23,7 @@ import uvloop
23
from fastapi import FastAPI, Response
24
25
import vllm.envs as envs
26
-from vllm.entrypoints.launcher import NoSignalServer
26
+from vllm.entrypoints.launchers.launcher import NoSignalServer
27
from vllm.logger import init_logger
28
from vllm.utils.system_utils import (
29
decorate_logs,
@@ -236,7 +236,7 @@ def _build_dp_supervisor_app(supervisor: DPSupervisor) -> FastAPI:
236
237
238
def _run_python_vllm_dp_server(child_args: argparse.Namespace) -> None:
239
- from vllm.entrypoints.openai.api_server import run_server
239
+ from vllm.entrypoints.launchers.api_server.entry import run_server
240
241
uvloop.run(run_server(child_args))
242
vllm/entrypoints/openai/responses/context.py
+1
-1
@@ -39,7 +39,6 @@ from vllm.entrypoints.openai.responses.utils import (
39
build_response_output_items,
40
construct_tool_dicts,
41
)
42
-from vllm.entrypoints.serve.utils.constants import MCP_PREFIX
42
from vllm.outputs import RequestOutput
43
from vllm.parser.abstract_parser import Parser
44
from vllm.tokenizers import TokenizerLike
@@ -58,6 +57,7 @@ _TOOL_NAME_TO_TYPE_MAP = {
57
"python": "code_interpreter",
58
"container": "container",
59
}
60
+MCP_PREFIX = "mcp_"
61
62
63
def _map_tool_name_to_tool_type(tool_name: str) -> str:
vllm/entrypoints/openai/run_batch.py
+3
-4
@@ -31,7 +31,7 @@ from vllm.config import config
31
from vllm.connections import global_http_connection
32
from vllm.engine.arg_utils import AsyncEngineArgs
33
from vllm.engine.protocol import EngineClient
34
-from vllm.entrypoints.openai.api_server import init_app_state
34
+from vllm.entrypoints.launchers.api_server.entry import init_app_state
35
from vllm.entrypoints.openai.chat_completion.protocol import (
36
ChatCompletionRequest,
37
ChatCompletionResponse,
@@ -836,8 +836,7 @@ async def run_batch(
836
error_msg=f"URL {request.url} was used. "
837
"Supported endpoints: /v1/chat/completions, /v1/embeddings,"
838
" /v1/audio/transcriptions, /v1/audio/translations, /score, "
839
- " /rerank. See vllm/entrypoints/openai/api_server.py "
840
- "for supported score/rerank versions.",
839
+ " /rerank.",
840
)
841
)
842
@@ -848,7 +847,7 @@ async def run_batch(
847
848
849
async def main(args: Namespace):
851
- from vllm.entrypoints.openai.api_server import build_async_engine_client
850
+ from vllm.entrypoints.launchers.api_server.entry import build_async_engine_client
851
from vllm.usage.usage_lib import UsageContext
852
853
validate_run_batch_args(args)
vllm/entrypoints/serve/exception_handling/handlers/vllm_error.py
+1
-1
@@ -4,7 +4,7 @@
4
from fastapi import Request
5
from starlette.responses import JSONResponse
6
7
-from vllm.entrypoints.launcher import terminate_if_errored
7
+from vllm.entrypoints.launchers.launcher import terminate_if_errored
8
from vllm.entrypoints.openai.engine.protocol import GenerationError
9
from vllm.exceptions import VLLMError
10
from vllm.logger import init_logger
vllm/plugins/endpoint_plugins/interface.py
+38
-2
@@ -20,7 +20,7 @@ for the loading/gating rules and `docs/usage/security.md` for the security
20
posture of exposing plugin defined routes.
21
22
The CPU only render server (see `build_and_serve_renderer` in
23
-`vllm/entrypoints/openai/api_server.py`) has no `EngineClient`. A plugin
23
+`vllm/entrypoints/launchers/render`) has no `EngineClient`. A plugin
24
eligible for the `render` task (`required_tasks` is `None` or includes
25
`"render"`) still gets `attach_router` called but `init_state` receives
26
`engine_client=None`. Plugins that cannot function without an engine should
@@ -34,8 +34,9 @@ from typing import TYPE_CHECKING, Protocol, runtime_checkable
34
from fastapi import FastAPI
35
from starlette.datastructures import State
36
37
+from vllm.engine.protocol import EngineClient
38
+
39
if TYPE_CHECKING:
38
- from vllm.engine.protocol import EngineClient
40
from vllm.tasks import SupportedTask
41
42
@@ -85,3 +86,38 @@ class EndpointPlugin(Protocol):
86
cannot function without an engine.
87
"""
88
...
89
+
90
+
91
+def attach_endpoint_plugins(
92
+ app: FastAPI, supported_tasks: tuple["SupportedTask", ...]
93
+) -> None:
94
+ """Phase A of endpoint plugin wiring: discover, gate and attach routes.
95
+
96
+ Attached last after all core routers. This is so endpoint plugin routes can
97
+ shadow core routes with the same path (see `EndpointPlugin.attach_router`
98
+ docstring). No-ops when no plugins are discovered/allowlisted.
99
+ """
100
+ from vllm.plugins import load_endpoint_plugins
101
+
102
+ endpoint_plugins = load_endpoint_plugins(supported_tasks)
103
+ for plugin in endpoint_plugins:
104
+ plugin.attach_router(app)
105
+ app.state.endpoint_plugins = endpoint_plugins
106
+
107
+
108
+async def init_endpoint_plugins_state(
109
+ engine_client: EngineClient | None, state: State, args: Namespace
110
+) -> None:
111
+ """Phase B of endpoint plugin wiring: initialize per app plugin state.
112
+
113
+ `state.endpoint_plugins` is set by `_attach_endpoint_plugins` (Phase A)
114
+ in `build_app`. Some `init_app_state` callers (e.g. `run_batch.py`)
115
+ build their own bare `State` without going through `build_app`. As a result
116
+ `endpoint_plugins` may be absent and are treated that the same as "none attached".
117
+
118
+ `engine_client` is `None` for the CPU only render server which has no
119
+ engine (see `init_render_app_state`). Plugins must handle a `None`
120
+ `engine_client` themselves (see `EndpointPlugin.init_state`).
121
+ """
122
+ for plugin in getattr(state, "endpoint_plugins", []):
123
+ await plugin.init_state(engine_client, state, args)
vllm/tasks.py
+1
@@ -41,3 +41,4 @@ FrontendTask = Literal["render"]
41
FRONTEND_TASKS: tuple[FrontendTask, ...] = get_args(FrontendTask)
42
43
SupportedTask = Literal[GenerationTask, PoolingTask, FrontendTask]
44
+FALLBACK_SUPPORTED_TASKS: tuple[SupportedTask, ...] = ("generate",)
vllm/v1/utils.py
+1
-1
@@ -503,7 +503,7 @@ def run_api_server_worker_proc(
503
) -> None:
504
"""Entrypoint for individual API server worker processes."""
505
506
- from vllm.entrypoints.openai.api_server import run_server_worker
506
+ from vllm.entrypoints.launchers.api_server.entry import run_server_worker
507
508
client_config = client_config or {}
509
server_index = client_config.get("client_index", 0)