Tour: Start-up of vllm serve
Code version: vLLM @ 3e44c0d · 2026-10-22
In one sentence
We follow vllm serve on one GPU from the shell command to "Application startup complete", in two processes and 18 stops.
The idea#
A cold start of vllm serve can take minutes. The log shows many lines, from two processes. This tour connects each part of the log to the code that writes it. Then you can read your own server log and see where the time goes.
With one GPU (TP = PP = DP = 1), vllm serve runs two processes. The API server process is the vllm command itself: it parses the flags, builds the VllmConfig and later runs the HTTP server. The EngineCore process is a child. With one GPU, the uni executor runs the worker inside that process, so it also loads the model and owns the GPU (The process and thread architecture of vllm serve).
An everyday comparison. Think of a theatre before the first show. The front office prints the tickets and the cast list, and it opens the doors only when the stage manager calls "ready". Backstage, the crew builds the set, tunes the lights and rehearses each scene once. The comparison stops in one place. Here the front office writes the full script (the config) before the crew starts, and the crew gets a copy.
The tour Tour: Life of a plugin walks the same start-up from the side of the plugins, with TP = 2. This tour follows the config, the processes and the GPU work.
The example#
vllm serve meta-llama/Llama-3.1-8B-Instruct # one 80 GB H100, no other flags
- Usage context
OPENAI_API_SERVER, so the token budget is 8192 andmax_num_seqsis 1024 when the driver reports 70 GiB or more (vllm/engine/arg_utils.py:2970-2983). - Optimization level O2:
torch.compileand CUDA graph modeFULL_AND_PIECEWISE(vllm/config/vllm.py:434,vllm/config/vllm.py:310). - The executor backend is
uni, because the world size is 1 (vllm/config/parallel.py:1099-1100).
The picture#
Read the left lane from the top to stop 10. Then follow the arrow to the right lane for stops 11 to 16, and back to the left lane for stops 17 and 18. Each log line of vLLM starts with the process name and the PID, for example (EngineCore pid=4242) INFO ... (vllm/logger.py:28-32).
| Stop | What happens | File and function | Process |
|---|---|---|---|
| 1 | The console script starts; spawn mode | entrypoints/cli/main.py: main | API server |
| 2 | Parsers are built, argv is parsed | cli/main.py; engine/arg_utils.py: AsyncEngineArgs.add_cli_args | API server |
| 3 | The serve command picks one API server | entrypoints/cli/serve.py: ServeSubcommand.cmd | API server |
| 4 | The port is bound before the engine starts | launchers/launcher.py: setup_server | API server |
| 5 | Namespace to EngineArgs | launchers/api_server/entry.py: build_async_engine_client | API server |
| 6 | The model config, first | arg_utils.py: create_engine_config | API server |
| 7 | The other sub-configs and VllmConfig(...) | arg_utils.py: create_engine_config | API server |
| 8 | The late defaults | config/vllm.py: VllmConfig.__post_init__ | API server |
| 9 | The front-end engine object | v1/engine/async_llm.py: AsyncLLM.__init__ | API server |
| 10 | Sockets, and the engine core process starts | v1/engine/core_client.py: MPClient.__init__; v1/engine/utils.py: launch_core_engines | API server |
| 11 | The handshake and EngineCore.__init__ | v1/engine/core.py: run_engine_core, EngineCoreProc.__init__ | engine core |
| 12 | The worker sets up the GPU | v1/executor/uniproc_executor.py; v1/worker/gpu_worker.py: init_device | engine core |
| 13 | The weights load | gpu_worker.py: load_model; v1/worker/gpu/model_runner.py | engine core |
| 14 | Profile run (with compilation) and KV cache size | core.py: _initialize_kv_caches; gpu_worker.py: determine_available_memory | engine core |
| 15 | The KV cache tensors are allocated | gpu_worker.py: initialize_from_config | engine core |
| 16 | Warm-up and CUDA graph capture | gpu_worker.py: compile_or_warm_up_model | engine core |
| 17 | READY, and the front end continues | core.py: _perform_handshake; v1/engine/utils.py: wait_for_engine_startup | both |
| 18 | The app is built and uvicorn serves | entry.py: build_and_serve; launcher.py: serve_http | API server |
All paths are under vllm/; launchers/ is vllm/entrypoints/launchers/.
1The console script starts, and the CLI selects spawn#
The command vllm is a console script for vllm.entrypoints.cli.main:main (pyproject.toml:42-43). main() first imports the module of each subcommand, then prepares the environment:
def main():
if maybe_run_omni():
return
import vllm.entrypoints.cli.benchmark.main
import vllm.entrypoints.cli.collect_env
import vllm.entrypoints.cli.download_kernels
import vllm.entrypoints.cli.launch
import vllm.entrypoints.cli.openai
import vllm.entrypoints.cli.preload
import vllm.entrypoints.cli.run_batch
import vllm.entrypoints.cli.serve
import vllm.entrypoints.cli.snapshot
from vllm.entrypoints.common.startup import VLLM_SUBCMD_PARSER_EPILOG, cli_env_setup
from vllm.utils.argparse_utils import FlexibleArgumentParser
CMD_MODULES = [
vllm.entrypoints.cli.openai,
vllm.entrypoints.cli.serve,
vllm.entrypoints.cli.launch,
vllm.entrypoints.cli.benchmark.main,
vllm.entrypoints.cli.collect_env,
vllm.entrypoints.cli.download_kernels,
vllm.entrypoints.cli.preload,
vllm.entrypoints.cli.run_batch,
vllm.entrypoints.cli.snapshot,
]
if sys.argv[1:2] != ["snapshot"]:
cli_env_setup()- Lines 43-51. The imports already do work.
cli/serve.pyimportscommon/cli_args.py, which importsengine/arg_utils.py. That module has a module-levelfrom vllm.platforms import current_platform, so the platform is detected now (vllm/entrypoints/cli/serve.py:13,vllm/entrypoints/common/cli_args.py:16,vllm/engine/arg_utils.py:122). The page Platform plugins and current_platform explains the detection. - Lines 67-68.
cli_env_setup()setsVLLM_WORKER_MULTIPROC_METHOD=spawnwhen you did not set it (vllm/entrypoints/common/startup.py:147-149). So the engine core of stop 10 is a new Python interpreter, a spawned process.
2The parsers are built, and argv is parsed#
The CLI builds the parser of every subcommand before it reads your flags:
subparsers = parser.add_subparsers(required=False, dest="subparser")
cmds = {}
for cmd_module in CMD_MODULES:
if cmd_module is vllm.entrypoints.cli.snapshot:
new_cmds = cmd_module.cmd_init(
create_requested=sys.argv[1:3] == ["snapshot", "create"]
)
else:
new_cmds = cmd_module.cmd_init()
for cmd in new_cmds:
cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd)
cmds[cmd.name] = cmd
args = parser.parse_args()
if args.subparser in cmds:
cmd = cmds[args.subparser]
configure_logging_from_args(args)
cmd.validate(args)
if hasattr(args, "dispatch_function"):
args.dispatch_function(args)The serve parser calls make_arg_parser, which ends with AsyncEngineArgs.add_cli_args (vllm/entrypoints/cli/serve.py:172, vllm/entrypoints/common/cli_args.py:465-466):
def add_cli_args(
parser: FlexibleArgumentParser, async_args_only: bool = False
) -> FlexibleArgumentParser:
# Initialize plugin to update the parser, for example, The plugin may
# add a new kind of quantization method to --quantization argument or
# a new device to --device argument.
load_general_plugins()
if not async_args_only:
parser = EngineArgs.add_cli_args(parser)
parser.add_argument(
"--enable-log-requests",
action=argparse.BooleanOptionalAction,
default=AsyncEngineArgs.enable_log_requests,
help="Enable logging request information, dependent on log level:\n"
"- INFO: Request ID, parameters and LoRA request.\n"
"- DEBUG: Prompt inputs (e.g: text, token IDs).\n"
"You can set the minimum log level via `VLLM_LOGGING_LEVEL`.\n"
"You can also set a separate log level for request JSON body "
"via `VLLM_REQUEST_BODY_LOG_LEVEL` (default: DEBUG).",
)
current_platform.pre_register_and_update(parser)
return parser- Line 3243. The general plugins run for the first time in this process. Later calls return at once (
vllm/plugins/__init__.py:82-85). - Line 3245. About 300 flags are made from the config classes (How configuration works, Step 4). The help text is read only with
--help, which saves time (vllm/engine/arg_utils.py:285-288). - Line 3257. The platform hook
pre_register_and_update(parser). The parsers oflaunch renderandrun-batchcalladd_cli_argstoo, so the hook runs three times here (vllm/entrypoints/cli/launch.py:39,vllm/entrypoints/cli/run_batch.py:62).
Line 110 of main.py parses the command line. FlexibleArgumentParser first inserts a --config YAML file, changes _ to -, expands -O3 and turns -cc.key=value into JSON (vllm/utils/argparse_utils.py:313-502). Line 113 configures logging; line 114 checks the serve flags.
3ServeSubcommand.cmd picks one API server#
args.dispatch_function is ServeSubcommand.cmd. It moves the positional model into args.model and decides how many API server processes to run (vllm/entrypoints/cli/serve.py:54-57). --api-server-count is not set, so it becomes the data parallel size, 1 (vllm/entrypoints/cli/serve.py:113-130):
if is_multi_port:
run_dp_supervisor(args)
elif args.api_server_count < 1:
run_headless(args)
elif args.api_server_count > 1 or rust_frontend_path:
run_multi_api_server(args)
else:
# Single API server (this process).
args.api_server_count = None
uvloop.run(run_server(args))One API server runs in this process, inside a uvloop event loop (lines 155-156). With more API servers, the CLI process would stay a supervisor and start them as children (line 152, deep dive "DP = 2").
4setup_server binds the port before the engine starts#
run_server names the process for the log, catches an early SIGTERM, and calls setup_server (vllm/entrypoints/launchers/api_server/entry.py:164-176):
def setup_server(args, *, reuse_port: bool):
"""Validate API server args and create the server socket."""
log_version_and_model(logger, VLLM_VERSION, args.model)
log_non_default_args(args)
init_parser_plugin(args)
validate_api_server_args(args)
# workaround to make sure that we bind the port before the engine is set up.
# This avoids race conditions with ray.
# see https://github.com/vllm-project/vllm/issues/8204
if args.uds:
sock = create_server_unix_socket(args.uds)
else:
sock_addr = (args.host or "", args.port)
sock = create_server_socket(sock_addr, reuse_port=reuse_port)- Line 367. The banner: "vLLM server version %s, serving model %s", or an ASCII logo with the same values (
vllm/entrypoints/common/startup.py:16-38). - Line 368. "non-default args: {...}" lists every flag that differs from its default, with secrets shown as
***(vllm/entrypoints/common/startup.py:49-60,vllm/entrypoints/common/startup.py:94-96). To find the defaults, it builds the whole parser once more (vllm/entrypoints/common/startup.py:70-74). Sopre_register_and_update(parser)runs a fourth time. - Lines 372-379. The socket is bound now, before the engine starts. The comment names a race with Ray as the reason. A busy port therefore fails in seconds, not after the model loads.
The API server only binds the port here. It does not accept connections until uvicorn runs at stop 18.
5The Namespace becomes EngineArgs#
run_server_worker enters build_async_engine_client, which makes the EngineArgs:
# Context manager to handle engine_client lifecycle
# Ensures everything is shutdown and cleaned up on error/exit
engine_args = AsyncEngineArgs.from_cli_args(args)
from vllm.entrypoints.common.cli_args import propagate_flash_late_interaction
propagate_flash_late_interaction(args, engine_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)from_cli_args copies each parsed value into the dataclass field of the same name (vllm/engine/arg_utils.py:1961-1970). Then EngineArgs.__post_init__ runs. It turns dict values into config objects and calls load_general_plugins(), which returns at once here (vllm/engine/arg_utils.py:873-924). The call is for the offline LLM class, which builds no parser.
6create_engine_config: the model config first#
build_async_engine_client_from_engine_args calls create_engine_config() with the usage context OPENAI_API_SERVER (vllm/entrypoints/launchers/api_server/entry.py:68-82):
logging_config = self.create_logging_config()
configure_logging_if_needed(logging_config)
current_platform.pre_register_and_update()
device_config = DeviceConfig(device=cast(Device, current_platform.device_type))
current_platform.validate_environ(self.fail_on_environ_validation)
# Check if the model is a speculator and override model/tokenizer/config
# BEFORE creating ModelConfig, so the config is created with the target model
# Skip speculator detection for cloud storage models (eg: S3, GCS) since
# HuggingFace cannot load configs directly from S3 URLs. S3 models can still
# use speculators with explicit --speculative-config.
if not is_cloud_storage(self.model):
(self.model, self.tokenizer, self.speculative_config) = (
maybe_override_with_speculators(
model=self.model,
tokenizer=self.tokenizer,
revision=self.revision,
trust_remote_code=self.trust_remote_code,
vllm_speculative_config=self.speculative_config,
hf_token=self.hf_token,
)
)
model_config = self.create_model_config()
self.model = model_config.model
self.model_weights = model_config.model_weights
self.tokenizer = model_config.tokenizer
self._check_feature_supported()
self._set_default_chunked_prefill_and_prefix_caching_args(model_config)- Line 2246.
pre_register_and_update()runs a fifth time, now without a parser. - Line 2250.
validate_environwarns about eachVLLM_*name in the environment that vLLM does not know (Environment variables). - Line 2269.
ModelConfigreads the Hugging Face config. It asks the model registry and logs "Resolved architecture: LlamaForCausalLM" (vllm/config/model.py:741-744). Later in the same__post_init__, it computesmax_model_lenand logs "Using max model len 131072" (vllm/config/model.py:824,vllm/config/model.py:2170). - Line 2275. Chunked prefill and prefix caching get their defaults from the model: both on for Llama (
vllm/engine/arg_utils.py:3028-3068).
7create_engine_config: the other sub-configs#
The function builds CacheConfig, ParallelConfig, the speculative config, the batch defaults, SchedulerConfig and the rest, in this order (vllm/engine/arg_utils.py:2296, vllm/engine/arg_utils.py:2558, vllm/engine/arg_utils.py:2624-2672). Three values of our example are decided here:
- Executor backend.
ParallelConfig.__post_init__sets"uni", because the world size is 1 (vllm/config/parallel.py:1099-1100). - Batch limits. 8192 tokens and 1024 requests, from
get_batch_defaults(vllm/engine/arg_utils.py:3153-3168). - Block size.
Nonebecomes 16 in aCacheConfigvalidator (vllm/config/cache.py:452-462). The platform can change it after the model loads (stop 13).
The function ends with one constructor call:
config = VllmConfig(
model_config=model_config,
cache_config=cache_config,
parallel_config=parallel_config,
scheduler_config=scheduler_config,
device_config=device_config,
load_config=load_config,
offload_config=offload_config,
attention_config=attention_config,8VllmConfig.__post_init__ sets the late defaults#
The constructor runs the __post_init__ of VllmConfig (vllm/config/vllm.py:1492-2318). For our example, it turns async scheduling on (vllm/config/vllm.py:1697-1698) and sets the compilation defaults:
current_platform.apply_config_platform_defaults(self)
if self.compilation_config.mode is None:
if self.optimization_level > OptimizationLevel.O0:
self.compilation_config.mode = CompilationMode.VLLM_COMPILE
else:
self.compilation_config.mode = CompilationMode.NONE
# By default, enable torch wrapping only when using custom Inductor lowering
if self.compilation_config.ir_enable_torch_wrap is None:
self.compilation_config.ir_enable_torch_wrap = (
self.compilation_config.mode == CompilationMode.VLLM_COMPILE
and self.compilation_config.backend == "inductor"
)
if all(s not in self.compilation_config.custom_ops for s in ("all", "none")):
if (
self.compilation_config.backend == "inductor"
and self.compilation_config.mode != CompilationMode.NONE
):
self.compilation_config.custom_ops.append("none")
else:
self.compilation_config.custom_ops.append("all")
# This populates IR op priorities,
# must happen after compilation mode and backend are decided,
# but before fusion defaults are applied as those may depend on op priority.
self.kernel_config.set_platform_defaults(self)
default_config = OPTIMIZATION_LEVEL_TO_CONFIG[self.optimization_level]
self._apply_optimization_level_defaults(default_config)- Line 1804. The platform hook
apply_config_platform_defaults. - Lines 1806-1810. O2 is above O0, so the compilation mode is
VLLM_COMPILE. - Lines 1819-1826. With Inductor, the custom op list gets
"none"(CustomOp, out-of-tree ops and IR op providers). - Lines 1833-1834. The O2 table fills the fields that are still
None: CUDA graph modeFULL_AND_PIECEWISEand the fusion passes (vllm/config/vllm.py:296-316).
Later in the method, _set_cudagraph_sizes picks the capture sizes, at most 512 here: min(1024 × 1 × 2, 512) (vllm/config/vllm.py:2439-2449). Then the platform has the last word: CUDA sets worker_cls to vllm.v1.worker.gpu_worker.Worker (vllm/config/vllm.py:2046, vllm/platforms/cuda.py:330-331). The config is now complete in the API server.
9AsyncLLM: tokenizer, processors, then the engine core#
The API server makes AsyncLLM (vllm/entrypoints/launchers/api_server/entry.py:93-103). from_vllm_config picks the executor class from the config: UniProcExecutor for "uni" (vllm/v1/engine/async_llm.py:264-276, vllm/v1/executor/abstract.py:84-87). The constructor loads the stat logger plugins, the renderer with the tokenizer, and the input and output processors (vllm/v1/engine/async_llm.py:155-186). Then it starts the engine core:
# EngineCore (starts the engine in background process).
# Hand the renderer to the client so it can start the frontend MM
# warmup only after engine-core fork (the why is in
# BaseRenderer.start_mm_warmup_in_background). The warmup is joined
# by reset_mm_cache / warmup / shutdown.
self.engine_core = EngineCoreClient.make_async_mp_client(
vllm_config=vllm_config,
executor_class=executor_class,
log_stats=self.log_stats,
client_addresses=client_addresses,
client_count=client_count,
client_index=client_index,
renderer=renderer,
)AsyncLLM always uses a multiprocess client, so the engine core always runs in its own process for vllm serve. With DP = 1, the client class is AsyncMPClient (vllm/v1/engine/core_client.py:158-173).
10MPClient binds the sockets and spawns the engine core#
MPClient.__init__ binds two ZMQ sockets: a ROUTER for requests to the engines, and a PULL for their outputs. Then it launches the engines:
else:
# Engines are managed by this client.
bind_addresses = get_engine_zmq_bind_addresses(vllm_config)
self.input_socket = self.resources.input_socket = make_zmq_socket(
self.ctx,
bind_addresses.inputs[0],
zmq.ROUTER,
bind=True,
router_handover=enable_input_socket_handover,
)
self.resources.output_socket = make_zmq_socket(
self.ctx, bind_addresses.outputs[0], zmq.PULL
)
# EngineZmqAddresses contains only endpoints resolved by the
# sockets that own the bind operation.
addresses = EngineZmqAddresses(
inputs=[self.input_socket.getsockopt(zmq.LAST_ENDPOINT).decode()],
outputs=[
self.resources.output_socket.getsockopt(
zmq.LAST_ENDPOINT
).decode()
],
)
with launch_core_engines(
vllm_config, executor_class, log_stats, addresses
) as engine_launch:launch_core_engines binds a third socket, the handshake ROUTER, and starts the process:
with zmq_socket_ctx(
local_handshake_address, zmq.ROUTER, bind=True
) as handshake_socket:
# Start local engines.
if local_engine_count:
local_engine_manager = CoreEngineProcManager(
vllm_config=vllm_config,
executor_class=executor_class,
log_stats=log_stats,
handshake_address=handshake_address,
client_handshake_address=client_handshake_address,
local_client=True,
local_engine_count=local_engine_count,
start_index=dp_rank,
local_start_index=local_start_index or 0,
tensor_queue=tensor_queue,
)
else:
local_engine_manager = None
if (
coord_store is not None
and coordinator is None
and (local_engine_manager is not None)
):
local_engine_manager._coord_store = coord_store
launch = CoreEngineLaunch(
local_engine_manager, coordinator, addresses, tensor_queue
)
yield launch
wait_for_engine_startup(
handshake_socket,
engines_to_handshake,
parallel_config,
dp_size > 1 and vllm_config.model_config.is_moe,
vllm_config.cache_config,
launch,
)- Lines 1362-1373.
CoreEngineProcManagermakes a process with the targetEngineCoreProc.run_engine_coreand the nameEngineCore, and starts it (vllm/v1/engine/utils.py:279-288,vllm/v1/engine/utils.py:330). Thevllm_configis a keyword argument, sospawnpickles it (vllm/v1/engine/utils.py:257-264). - Line 1387. The
yieldreturns toMPClient, which starts the multimodal warm-up of the front end (vllm/v1/engine/core_client.py:710). - Lines 1388-1395. When the
withblock ofMPClientends,wait_for_engine_startupruns. The API server now waits for stop 17.
11The engine core process: handshake and EngineCore.__init__#
We are now in the new process. run_engine_core sets the process title and the log prefix EngineCore (vllm/v1/engine/core.py:1462-1472), installs signal handlers, and makes an EngineCoreProc (vllm/v1/engine/core.py:1538-1543). Its constructor first does the start-up handshake with the front end:
# Send registration message.
handshake_socket.send(
msgspec.msgpack.encode(
{
"status": "HELLO",
"local": local_client,
"headless": headless,
}
)
)
# Receive initialization message.
logger.debug("Waiting for init message from front-end.")
if not handshake_socket.poll(timeout=HANDSHAKE_TIMEOUT_MINS * 60_000):
raise RuntimeError(
"Did not receive response from front-end "
f"process within {HANDSHAKE_TIMEOUT_MINS} "
f"minutes"
)
init_bytes = handshake_socket.recv()The engine sends HELLO and waits at most 5 minutes for the init message with the socket addresses (vllm/v1/engine/core.py:113). The front end answers at once (vllm/v1/engine/utils.py:1512-1546). Inside the handshake context, EngineCore.__init__ runs (vllm/v1/engine/core.py:1190-1226):
# plugins need to be loaded at the engine/scheduler level too
from vllm.plugins import load_general_plugins
load_general_plugins()
self.vllm_config = vllm_config
if not vllm_config.parallel_config.data_parallel_rank_local:
logger.info(
"Initializing a V1 LLM engine (v%s) with config: %s",
VLLM_VERSION,
vllm_config,
)
self.log_stats = log_stats
# Opaque weight version supplied by the caller.
self._weight_version = "default"
# Setup Model.
self.model_executor = executor_class(vllm_config)- Line 132. The general plugins run again: this is a new interpreter.
- Lines 135-140. The log line "Initializing a V1 LLM engine (v...) with config: model='meta-llama/Llama-3.1-8B-Instruct', ...". It prints 33 resolved fields of the config (
vllm/config/vllm.py:2788-2823). - Line 147. The executor constructor. Stops 12 and 13 run inside it.
12The uni executor: init_device#
The uni executor makes the worker object in this process and starts it:
def _init_executor(self) -> None:
"""Initialize the worker and load the model."""
self.driver_worker = WorkerWrapperBase(rpc_rank=0)
distributed_init_method, rank, local_rank = self._distributed_args()
kwargs = dict(
vllm_config=self.vllm_config,
local_rank=local_rank,
rank=rank,
distributed_init_method=distributed_init_method,
is_driver_worker=True,
shared_worker_lock=Lock(),
)
# Set net device env vars for the worker if VLLM_GPU_NIC_PCIE_MAPPING is set
set_worker_net_device(local_rank, self.vllm_config)
self.driver_worker.init_worker(all_kwargs=[kwargs])
self.driver_worker.init_device()
if envs.VLLM_ELASTIC_EP_SCALE_UP_LAUNCH:
self.driver_worker.elastic_ep_execute("load_model")
else:
self.driver_worker.load_model()
current_platform.update_block_size_for_backend(self.vllm_config)init_worker resolves worker_cls from stop 8 and makes the GPU worker (vllm/v1/worker/worker_base.py:292-302, Hooks in the engine core and the workers). init_device then prepares the GPU:
# Initialize the distributed environment BEFORE taking
# memory snapshot
# This ensures NCCL buffers are allocated before we measure
# available memory
init_worker_distributed_environment(
self.vllm_config,
self.rank,
self.distributed_init_method,
self.local_rank,
current_platform.dist_backend,
)
if self.use_v2_model_runner:
logger.info_once("Using V2 Model Runner")
warn_if_flashinfer_kernels_missing()
# Set random seed.
set_random_seed(self.model_config.seed)- Lines 501-507.
torch.distributedstarts with a world size of 1. The comment says that this must happen before the memory snapshot, so that NCCL buffers count as used memory. - Line 510. The log line "Using V2 Model Runner".
Then the worker makes cuBLAS handles, takes the memory snapshot and computes the requested memory, gpu_memory_utilization × total (vllm/v1/worker/gpu_worker.py:523-541). It builds Model Runner V2 (vllm/v1/worker/gpu_worker.py:567-580).
13load_model: the weights#
Worker.load_model calls the model runner (vllm/v1/worker/gpu_worker.py:591-598). The runner builds the model and loads the weights:
with DeviceMemoryProfiler() as m:
model_loader = get_model_loader(self.vllm_config.load_config)
logger.info_once("Loading model from scratch...")
# Capture warmup providers selected while constructing the model.
with self.jit_warmup_registry.activate():
self.model = model or model_loader.load_model(
vllm_config=self.vllm_config,
model_config=self.vllm_config.model_config,
) time_after_load = time.perf_counter()
self.model_memory_usage = m.consumed_memory
logger.info(
"Model loading took %s GiB memory and %.6f seconds",
format_gib(m.consumed_memory),
time_after_load - time_before_load,
)The log shows four lines here. The first is "Loading model from scratch...". Then the constructors log "Initializing model took 0.412310 seconds" (vllm/model_executor/model_loader/base_loader.py:82-85). The default loader logs "Loading weights took 4.12 seconds" (vllm/model_executor/model_loader/default_loader.py:505-508). The last is "Model loading took 14.99 GiB memory and 5.031062 seconds" (illustrative values; the code prints the seconds with six decimals).
A download of the weights, if needed, happens inside load_model: the default loader downloads a model that is not local before it reads the files (vllm/model_executor/model_loader/default_loader.py:155-157, vllm/model_executor/model_loader/default_loader.py:269-277). The page "Weight loading: from checkpoint files to GPU parameters" explains the loader.
Back in the executor, update_block_size_for_backend checks that block size 16 suits the attention backend (line 75 of the excerpt in stop 12).
14_initialize_kv_caches: the profile run, compilation and the KV cache size#
Back in EngineCore.__init__, the engine sizes the KV cache (vllm/v1/engine/core.py:160):
has_kv_cache = any(kv_cache_spec for kv_cache_spec in kv_cache_specs)
if has_kv_cache:
if envs.VLLM_ELASTIC_EP_SCALE_UP_LAUNCH:
# NOTE(yongji): should already be set
# during _eep_scale_up_before_kv_init
assert self.available_gpu_memory_for_kv_cache > 0
available_gpu_memory = [self.available_gpu_memory_for_kv_cache] * len(
kv_cache_specs
)
else:
# Profiles the peak memory usage of the model to determine how
# much memory can be allocated for kv cache.
available_gpu_memory = self.model_executor.determine_available_memory()
self.available_gpu_memory_for_kv_cache = available_gpu_memory[0]
else:
# Attention free models don't need memory for kv cache
available_gpu_memory = [0] * len(kv_cache_specs)
assert len(kv_cache_specs) == len(available_gpu_memory)
# Track max_model_len before KV cache config to detect auto-fit changes
max_model_len_before = vllm_config.model_config.max_model_len
kv_cache_configs = get_kv_cache_configs(
vllm_config, kv_cache_specs, available_gpu_memory
)determine_available_memory runs a profile run: one forward pass with max_num_batched_tokens dummy tokens (vllm/v1/worker/gpu_worker.py:682-697). This is the first call of the compiled model, so torch.compile runs now. The log shows:
- "Starting torch.compile" (
vllm/compilation/monitor.py:39-40); - "Dynamo bytecode transform time: 6.10 s" (
vllm/compilation/backends.py:1186-1190); - "Compiling a graph for compile range ... takes 20.31 s", once per compile range (
vllm/compilation/backends.py:415-422); - "torch.compile took 29.70 s in total" (
vllm/compilation/monitor.py:20,vllm/compilation/monitor.py:56-62).
Then the worker measures the memory and logs "Available KV cache memory: 54.6 GiB" (vllm/v1/worker/gpu_worker.py:744-748, vllm/v1/worker/gpu_worker.py:763-766). Line 311 turns the bytes into blocks. With 2 MiB per block for Llama-3.1-8B, that is 27,955 blocks (v1/worker/gpu_worker.py: the GPU Worker). Then the engine core logs the capacity:
logger.info_once(
"%s KV cache size: %s tokens, "
"Maximum concurrency for %s tokens per request: %.2fx",
pools,
f"{num_tokens:,}",
f"{max_model_len:,}",
max_concurrency,
)The call is at vllm/v1/engine/core.py:342. With the numbers of the worked example on The KV cache manager: blocks, the block pool and memory sizing, the line is:
GPU KV cache size: 447,280 tokens, Maximum concurrency for 131,072 tokens per request: 3.41x
15initialize_from_config: the KV cache tensors#
The engine core sends the KV cache config to the worker (vllm/v1/engine/core.py:362). The worker allocates the tensors:
def initialize_from_config(self, kv_cache_config: KVCacheConfig) -> None:
"""Allocate GPU KV cache with the specified kv_cache_config."""
# Update local config with adjusted num blocks after profiling,
# so that it's available to the warmup stage.
self.cache_config.num_gpu_blocks = kv_cache_config.num_blocks with runtime_pool:
self.model_runner.initialize_kv_cache(
kv_cache_config,
kv_cache_allocation_context=mem_pool_context,
)A KV connector would start here too (vllm/v1/worker/gpu_worker.py:878). In our example there is none. After this stop, about 54.6 GiB of the GPU belong to the KV cache.
16compile_or_warm_up_model: warm-up and CUDA graph capture#
The last GPU stop (vllm/v1/engine/core.py:378-379):
for compile_range in compile_ranges:
if not any(x in compile_range for x in all_sizes):
warmup_sizes.append(compile_range.end)
# We skip EPLB here since we don't want to record dummy metrics
for size in sorted(warmup_sizes, reverse=True):
logger.info("Compile and warming up model for size %d", size)
self.model_runner._dummy_run(
size,
skip_eplb=True,
remove_lora=False,
randomize_inputs=self.randomize_dummy_inputs,
)
self.model_runner.maybe_remove_all_loras(self.model_runner.lora_config)
# Warmup and tune the kernels used during model execution before
# cuda graph capture.
kernel_warmup(self)
if self.use_v2_model_runner: # noqa: SIM102
if self.vllm_config.kernel_config.enable_jit_warmup:
# A workspace resize after capture frees what the graphs point at.
warmup_kernels(
self.model_runner, self.execute_model, self.sample_tokens
)
cuda_graph_memory_bytes = 0
if not self.model_config.enforce_eager:
with self._get_cudagraph_capture_context():
cuda_graph_memory_bytes = self.model_runner.capture_model()- Lines 956-968. Each compile range that has no captured size gets one warm-up run at its end size. Each size in
compile_sizesthat is not a capture size gets one too (lines 941-948). In our example, the capture sizes cover the one compile range, so this loop does not run. The log is "Compile and warming up model for size %d". - Line 973. Kernel warm-up and tuning before capture.
- Lines 983-985. CUDA graph capture, unless
--enforce-eager. The log is "Graph capturing finished in 9 secs, took 0.46 GiB" (vllm/v1/worker/gpu/model_runner.py:1146-1152). The comment there says it "usually takes 5~20 seconds".
Then the worker logs "CUDA graph pool memory: ..." and a long line "Free memory on device ... Replace gpu_memory_utilization config with --kv-cache-memory=..." (vllm/v1/worker/gpu_worker.py:994-1057). The real flag name is --kv-cache-memory-bytes (vllm/engine/arg_utils.py:1385). The short form also works, because argparse accepts a unique prefix of a flag name by default (derived; no allow_abbrev=False in vllm/). Back in the engine core, one line sums up stops 14 to 16:
elif compile_time > 0:
logger.info_once(
"init engine (profile, create kv cache, warmup model) took "
"%.2f s (compilation: %.2f s)",
elapsed,
compile_time,
)For example: "init engine (profile, create kv cache, warmup model) took 52.40 s (compilation: 29.70 s)". EngineCore.__init__ then builds the scheduler with the final block count, picks step_with_batch_queue, and turns on the environment variable cache (vllm/v1/engine/core.py:177-185, vllm/v1/engine/core.py:227-229, vllm/v1/engine/core.py:241-243).
17READY: the front end continues#
EngineCore.__init__ has returned. When the handshake context ends, the engine sends READY:
# Send ready message.
ready_msg = {
"status": "READY",
"local": local_client,
"headless": headless,
}
# Include config hash for DP configuration validation
if vllm_config.parallel_config.data_parallel_size > 1:
ready_msg["parallel_config_hash"] = (
vllm_config.parallel_config.compute_hash()
)
if vllm_config.parallel_config.enable_elastic_ep:
ready_msg["coord_store_port"] = (
vllm_config.parallel_config._coord_store_port
)
ready_msg["elastic_ep_max_dp_size"] = (
vllm_config.parallel_config.elastic_ep_max_dp_size
)
ready_msg["num_redundant_experts"] = (
vllm_config.parallel_config.eplb_config.num_redundant_experts
)
handshake_socket.send(msgspec.msgpack.encode(ready_msg))Before READY, still inside the with block, the engine core starts its input and output threads (vllm/v1/engine/core.py:1243-1265). The input thread connects to the front end and sends the ready response as its first message (vllm/v1/engine/core.py:1961-1968). The constructor waits for the input thread, then the block ends and READY goes out (vllm/v1/engine/core.py:1267-1272). After READY, the engine core calls vllm_config.__post_init__() again on its copy (vllm/v1/engine/core.py:1336-1337; see How configuration works, Step 9).
In the API server, wait_for_engine_startup has polled the handshake socket every 10 seconds (vllm/v1/engine/utils.py:43):
while any(conn_pending) or any(start_pending):
events = poller.poll(STARTUP_POLL_PERIOD_MS)
if not events:
if any(conn_pending):
logger.debug(
"Waiting for %d local, %d remote core engine proc(s) to connect.",
*conn_pending,
)
if any(start_pending):
logger.debug(
"Waiting for %d local, %d remote core engine proc(s) to start.",
*start_pending,
)
continueA poll with no event logs at DEBUG level and continues, so this wait has no time limit. Only a dead process stops it, with "Engine core initialization failed. See root cause above." (lines 1448-1477). The READY message moves the engine to the state READY (lines 1547-1578). Then MPClient waits for the ready response on its ROUTER socket, now with the limit VLLM_ENGINE_READY_TIMEOUT_S, 600 s by default (vllm/v1/engine/core_client.py:753-771, vllm/envs.py:810-814). It copies max_model_len, num_gpu_blocks and block_size from the response into its own config (vllm/v1/engine/core_client.py:865-879).
18build_and_serve: the app, then uvicorn#
AsyncLLM exists now. Back in run_server_worker, the API server builds the HTTP app and serves it:
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(- Line 132. A utility call asks the engine core for its tasks. The log shows a tuple, for example "Supported tasks: ('generate',)" (
vllm/v1/engine/async_llm.py:390). - Line 136.
build_appmakes the FastAPI app with the routes, endpoint plugins, exception handlers and middleware (vllm/entrypoints/launchers/app.py:19-56). See The OpenAI-compatible server: launch modes, app, serving classes and streaming. - Line 137.
init_app_statemakes the serving objects, for example the chat serving class (vllm/entrypoints/launchers/api_server/app_state.py:24-30). - Line 139. "Starting vLLM server on http://0.0.0.0:8000".
serve_http logs "Available routes are:" and one line per route (vllm/entrypoints/launchers/launcher.py:188-208). Then it starts uvicorn on the socket from stop 4:
engine_client = app.state.engine_client
watchdog_task = (
loop.create_task(watchdog_loop(server, engine_client))
if engine_client is not None
else None
)
server_task = loop.create_task(server.serve(sockets=[sock] if sock else None))uvicorn runs the app's lifespan function. It starts the periodic stats log and freezes the start-up heap for the garbage collector (vllm/entrypoints/launchers/utils/server_utils.py:76-96). uvicorn then prints "Application startup complete." This text is in uvicorn, not in the vLLM repository. From now on, requests reach the server; the tour Tour: Life of a chat request follows one.
Deep dive#
The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.
Deep diveYour start-up log, line by line
The INFO lines of a cold start, in order, with the stop and the code that writes each one. Values are illustrative.
| Stop | Prefix | Log text | Written at |
|---|---|---|---|
| 4 | APIServer | vLLM server version ..., serving model ... (or the logo) | vllm/entrypoints/common/startup.py:16-38 |
| 4 | APIServer | non-default args: {'model_tag': '...', 'model': '...'} | vllm/entrypoints/common/startup.py:94-96 |
| 6 | APIServer | Resolved architecture: LlamaForCausalLM | vllm/config/model.py:744 |
| 6 | APIServer | Using max model len 131072 | vllm/config/model.py:2170 |
| 11 | EngineCore | Initializing a V1 LLM engine (v...) with config: model=..., ... | vllm/v1/engine/core.py:136-140 |
| 12 | EngineCore | Using V2 Model Runner | vllm/v1/worker/gpu_worker.py:510 |
| 13 | EngineCore | Loading model from scratch... | vllm/v1/worker/gpu/model_runner.py:399 |
| 13 | EngineCore | Initializing model took 0.412310 seconds | vllm/model_executor/model_loader/base_loader.py:82-85 |
| 13 | EngineCore | Loading weights took 4.12 seconds | vllm/model_executor/model_loader/default_loader.py:506-508 |
| 13 | EngineCore | Model loading took 14.99 GiB memory and 5.031062 seconds | vllm/v1/worker/gpu/model_runner.py:431-435 |
| 14 | EngineCore | Starting torch.compile | vllm/compilation/monitor.py:39-40 |
| 14 | EngineCore | Dynamo bytecode transform time: 6.10 s | vllm/compilation/backends.py:1186-1190 |
| 14 | EngineCore | Compiling a graph for compile range ... takes 20.31 s | vllm/compilation/backends.py:415-422 |
| 14 | EngineCore | torch.compile took 29.70 s in total | vllm/compilation/monitor.py:56-62 |
| 14 | EngineCore | Available KV cache memory: 54.6 GiB | vllm/v1/worker/gpu_worker.py:763-766 |
| 14 | EngineCore | GPU KV cache size: 447,280 tokens, Maximum concurrency for 131,072 tokens per request: 3.41x | vllm/v1/core/kv_cache_utils.py:1101-1108 |
| 16 | EngineCore | Graph capturing finished in 9 secs, took 0.46 GiB | vllm/v1/worker/gpu/model_runner.py:1148-1152 |
| 16 | EngineCore | init engine (profile, create kv cache, warmup model) took 52.40 s (compilation: 29.70 s) | vllm/v1/engine/core.py:394-400 |
| 18 | APIServer | Supported tasks: ('generate',) | vllm/entrypoints/launchers/api_server/entry.py:135 |
| 18 | APIServer | Starting vLLM server on http://0.0.0.0:8000 | vllm/entrypoints/launchers/api_server/entry.py:139 |
| 18 | APIServer | Available routes are: / Route: ..., Methods: ... | vllm/entrypoints/launchers/launcher.py:188-197 |
| 18 | (uvicorn) | Application startup complete. | uvicorn, outside this repository |
Other lines can appear between them, for example the attention backend choice (vllm/platforms/cuda.py:508-511) and the CUDA graph memory lines of stop 16. With one GPU, the worker's lines carry the prefix EngineCore, because the worker runs in that process.
Deep diveWhat changes with TP = 2
vllm serve meta-llama/Llama-3.1-8B-Instruct -tp 2
- Stop 7. The world size is 2, so
ParallelConfigpicks"mp"when Ray is not active (vllm/config/parallel.py:1055-1061,vllm/config/parallel.py:1096). Stop 9 picksMultiprocExecutor(vllm/v1/executor/abstract.py:80-83). - Stop 12. The executor makes a shared-memory broadcast queue and spawns one process per rank, named
VllmWorker-0andVllmWorker-1(vllm/v1/executor/multiproc_executor.py:165-216,vllm/v1/executor/multiproc_executor.py:745-750). Each worker gets its own pickledvllm_config(lines 731-743). - Stops 12-13 in parallel. Each worker runs
init_deviceandload_modelat the same time. The log prefixes becomeWorker_TP0andWorker_TP1after the process groups exist (vllm/v1/executor/multiproc_executor.py:1083-1116). Each worker sends READY over a pipe (vllm/v1/executor/multiproc_executor.py:930-937), and the executor waits for both (vllm/v1/executor/multiproc_executor.py:787-811). - Stop 14. Each worker measures its own memory. The planner uses the smallest block count of all ranks (
vllm/v1/core/kv_cache_planning.py:1619-1634). - Stop 13. Each worker turns on its own environment cache at the end of
WorkerProc.__init__, afterload_modeland before the profile run (vllm/v1/executor/multiproc_executor.py:680-688,vllm/v1/executor/multiproc_executor.py:708-710).
So the server has four vLLM processes: API server, engine core and two workers. The page Executors and workers: from the engine core to the GPUs lists every executor call. Tensor parallelism itself is explained in Parallelism basics.
Deep diveWhat changes with DP = 2
vllm serve meta-llama/Llama-3.1-8B-Instruct -dp 2
- Stop 3.
--api-server-countdefaults to the DP size, 2 (vllm/entrypoints/cli/serve.py:124-130). Sorun_multi_api_serverruns, notrun_server(vllm/entrypoints/cli/serve.py:151-152). - Stops 4-8 in the CLI process. It binds the port with
SO_REUSEPORT, builds its ownVllmConfigand binds the ZMQ listeners for both API servers (vllm/entrypoints/cli/serve.py:298,vllm/entrypoints/cli/serve.py:307-334). - Stop 10.
launch_core_enginesstarts a DP coordinator process, because the internal load balancer needs its queue statistics (vllm/v1/engine/utils.py:1230-1274). It starts two engine core processes,EngineCore_DP0andEngineCore_DP1(vllm/v1/engine/utils.py:284). - Inside the
withblock, before the engines are ready, the CLI process starts two API server processes,ApiServer_0andApiServer_1(vllm/entrypoints/cli/serve.py:366-377,vllm/v1/utils.py:248-254). Each one runs stops 5-9 again: its ownEngineArgs,create_engine_configandAsyncLLM, which connects to the existing engines (vllm/v1/utils.py:509-529,vllm/v1/engine/core_client.py:643-669). - Engines. Llama is not a MoE model, so each engine core is an independent
EngineCoreProc(vllm/v1/engine/core.py:1534-1543). The world size across DP is 2, so the backend is"mp"and each engine core has one worker process. - Stop 17. The API server processes did not launch the engines, so they skip
wait_for_engine_startup. They wait for the ready responses with the 600 s limit ofVLLM_ENGINE_READY_TIMEOUT_S(vllm/v1/engine/core_client.py:753-768). If the engines load for longer than that, the API servers fail with the timeout error.
The count of vLLM processes is 8: the CLI supervisor, 2 API servers, the coordinator, 2 engine cores and 2 workers (derived). Helper processes of Python's multiprocessing are not counted. See Data parallel serving: several engine cores behind one endpoint.
Deep diveWhat changes with --enforce-eager
- Stop 8.
__post_init__sets the compilation mode and the CUDA graph mode toNONEand turns off the JIT kernel warm-up. It logs "Enforce eager set, disabling torch.compile, CUDAGraphs, and JIT kernel warmup ..." (vllm/config/vllm.py:1745-1763). Later it clears the capture sizes (vllm/config/vllm.py:1982-1988). - Stop 14. The profile run runs the model eagerly: no "torch.compile took" line.
- Stop 16. No capture (
vllm/v1/worker/gpu_worker.py:983-985). The summary line has no "(compilation: ...)" part, because the compile time is 0 (vllm/v1/engine/core.py:401-405).
Start-up is faster, but each decode step pays the full Python and kernel launch cost. -O0 has the same effect on compilation and graphs (vllm/config/vllm.py:254-274, vllm/config/vllm.py:1806-1810). The CUDA graph idea is in GPUs and kernels.
Deep diveA warm start: the compile cache
Start the same command a second time, with the same config, code and environment. Stop 14 changes:
- The AOT path finds its file under
$VLLM_CACHE_ROOT/torch_compile_cache/torch_aot_compile/<sha256>/rank_0_0/model(vllm/compilation/decorators.py:570-596). It logs "Directly load AOT compilation from path ..." (vllm/compilation/decorators.py:326-330) and still logs the total time, now small (vllm/compilation/decorators.py:302-307). - The key of that folder is a hash of the env factors,
VllmConfig.compute_hash()and, with the mega artifact, the Inductor factors (vllm/compilation/caching.py:572-588). - A changed source file or a load error falls back to a full compile, with "Compiling model again due to a load failure from ..." (
vllm/compilation/decorators.py:332-348).
Stop 16 does not change: CUDA graphs are captured at every start (vllm/v1/worker/gpu_worker.py:983-985). Weight loading does not change either. The page torch.compile and CUDA graphs in vLLM explains both cache folders.
Deep diveWhere start-up time goes, and how to reduce it
| Phase (stop) | Log line to read | What reduces it |
|---|---|---|
| Imports and parsers (1-4) | time to "non-default args" | Nothing to tune; --help is slow because it reads all docstrings (vllm/engine/arg_utils.py:285-288). |
| Config, tokenizer, registry (6-9) | "Resolved architecture" | Keep the Hugging Face cache and $VLLM_CACHE_ROOT/modelinfos between starts (Out-of-tree models and the model registry). |
| Weights (13) | "Loading weights took", "Model loading took" | A fast local disk; download the weights before the start; --load-format for a faster loader ("Weight loading: from checkpoint files to GPU parameters"); vllm preload, which keeps weights in GPU memory for fast restarts (vllm/entrypoints/cli/preload.py:342-349). |
| Compile (14) | "torch.compile took ... in total" | Keep $VLLM_CACHE_ROOT (a container volume); do not change the hashed settings; -O0 or --enforce-eager skip it. |
| Memory profile (14) | "Available KV cache memory" | VLLM_ENABLE_STARTUP_PLAN=1 reuses the measured value on the same hardware and config (vllm/v1/worker/startup_plan.py:3-18). The profile run itself still runs. |
| Capture (16) | "Graph capturing finished in ..." | -O1 (piecewise graphs only); fewer sizes with -cc.max_cudagraph_capture_size; --enforce-eager skips it. |
| Wait for READY (17) | (none at INFO) | With one API server there is no limit. With several API servers, raise VLLM_ENGINE_READY_TIMEOUT_S when the engines load slowly. |
What can go wrong#
- The port is busy.
sock.bindfails at stop 4, before any GPU work (vllm/entrypoints/launchers/launcher.py:349-353). - The engine core fails. It logs "EngineCore failed to start." with the stack trace (
vllm/v1/engine/core.py:1570-1572). The API server then raises "Engine core initialization failed. See root cause above." (vllm/v1/engine/utils.py:1468-1477). Read theEngineCorelines above it for the real error, for example a CUDA out-of-memory error. - Not enough free memory. The worker checks at start that the free memory is at least
gpu_memory_utilization× total, and raisesValueErrorif not (vllm/v1/worker/utils.py:761-770). Another process on the same GPU is the usual cause. - The KV cache is too small for
max_model_len. The planner raisesValueErrorat stop 14: "To serve at least one request with the model's max seq len ...". The message gives an estimated maximum length (vllm/v1/core/kv_cache_planning.py:68-88). - The front end waits for a long time with no log. With one API server, the wait at stop 17 logs only at DEBUG level. Read the
EngineCorelines to see the progress. - A timeout with several API servers. "Timed out waiting for engine core processes to start" after
VLLM_ENGINE_READY_TIMEOUT_S(vllm/v1/engine/core_client.py:757-768).
Why it matters. A server that does not start, or starts slowly, costs capacity during each deploy and each restart. When you know the 18 stops, you can read the log, find the slow phase, and pick the setting that shortens it.
Check yourself#
- 1In which process does the model load when you runeasy
vllm serveon one GPU?Show answer
In the engine core process. The uni executor makes the worker object in that process and calls
load_modelon it (vllm/v1/executor/uniproc_executor.py:54-74). - 2Why does a busy port fail in seconds and not after the model loads?easy
Show answer
setup_serverbinds the socket before the engine starts, at stop 4 (vllm/entrypoints/launchers/launcher.py:372-379). - 3Which stop runsmedium
torch.compileon a cold start, and why there?Show answer
Stop 14. The profile run in
determine_available_memoryis the first call of the compiled model, so the compilation happens inside it (vllm/v1/worker/gpu_worker.py:682-697). The memory profile then also measures the compiled model. - 4You setmedium
VLLM_ENGINE_READY_TIMEOUT_S=60and start a single-GPU server whose weights take 5 minutes to load. What happens?Show answer
The server starts normally. With one API server, the front end waits in
wait_for_engine_startup, which has no time limit (vllm/v1/engine/utils.py:1434-1447). The 60 s limit applies only to the ready response after READY (vllm/v1/engine/core_client.py:753-768). - 5How many times doeshard
pre_register_and_updaterun in the API server process forvllm serve <model>, and where?Show answer
Five times. Three parser builds at stop 2 (
serve,launch render,run-batch) each callAsyncEngineArgs.add_cli_args(vllm/engine/arg_utils.py:3257).log_non_default_argsat stop 4 builds a fourth parser (vllm/entrypoints/common/startup.py:70-71).create_engine_configcalls the hook a fifth time, without a parser (vllm/engine/arg_utils.py:2246). (Derived from these call sites.)