vLLM Explained

What vLLM is today

BeginnerLearn the basicsabout 7 min read + 6 min deep dives

Code version: vLLM @ 3e44c0d · 2026-10-22

In one sentence

vLLM is an engine that runs LLMs and serves them over HTTP; one engine core, V1, sits behind both of its entry points.

The idea#

What vLLM is#

vLLM calls itself "a fast and easy-to-use library for LLM inference and serving" (README.md:24). The package description says "a high-throughput and memory-efficient inference and serving engine for LLMs" (pyproject.toml:22). An inference engine runs a trained model and makes its tokens. It adds batching, a KV cache and a scheduler around the model (What an LLM does). A serving engine also accepts requests from many users over the network and streams the answers back.

This site explains vLLM as a serving engine. The main path is the command vllm serve. It starts an OpenAI-compatible server: "Launch a local OpenAI-compatible API server to serve LLM completions via HTTP" (vllm/entrypoints/cli/serve.py:39-40). Any client that can talk to the OpenAI API can then talk to your model.

One engine: V1#

vLLM has exactly one engine at this commit: the V1 engine in vllm/v1/. The older V0 engine is gone. Only two old names remain, and both are aliases of V1 classes:

So when you read "AsyncLLMEngine" in old examples, it is the V1 AsyncLLM. This site uses only the V1 names.

Two ways to use it#

  1. Online serving with vllm serve (this site). An HTTP client sends a request, for example POST /v1/chat/completions. The API server turns it into an engine request and streams the tokens back. The page The OpenAI-compatible API explains the API.
  2. Offline inference with the LLM class (a side note). Your Python script makes LLM(model=...) and calls generate() or chat(). There is no HTTP server. The class builds a V1 LLMEngine (vllm/entrypoints/llm.py:341-343). Its own docstring says that it "is intended to be used for offline inference" (vllm/entrypoints/llm.py:171-173).

What runs where#

One vllm serve deployment is a group of processes. Each one has its own job:

  • The front end (the API server process) runs the HTTP routes, the chat template, the tokenizer and AsyncLLM.
  • The EngineCore process runs the scheduler and the KV cache manager in a loop. It tells the workers what to compute in each engine step.
  • The workers own the GPUs and run the model. With one GPU, the worker is an object inside the engine core process.

The page The process and thread architecture of vllm serve explains this split in full. The page Processes, ZMQ and shared memory explains how the processes talk to each other.

An everyday comparison#

Think of a restaurant. The waiter (the front end) takes orders from many tables and brings the dishes back. The head chef (the engine core) decides which dishes to cook in the next round. The cooks at the stoves (the workers) do the cooking. A take-away counter (the LLM class) uses the same kitchen, but without waiters.

The comparison stops here: a real kitchen cooks each dish once. Without speculative decoding, vLLM makes at most one new token per request in each step, and repeats the step until each answer is complete.

The picture#

Figure: vllm serve and the LLM class are two entry points. Both reach the same EngineCore class. This site follows the upper path.
Open full size ↗

Read panel 1 from left to right: this is the path of every request on this site. Panel 2 shows the offline path. It shares the engine core, but the front end is your own script. The blue, amber and violet colours mark the online front end, the offline path and the shared engine core.

In vLLM#

This is a basics page, so this part gives only pointers. The tour Tour: Life of a chat request follows one request through all of the code.

  1. The command. The package installs one console script, vllm, that calls vllm.entrypoints.cli.main:main (pyproject.toml:42-43). With one API server, vllm serve runs the server in the same process as the command (vllm/entrypoints/cli/serve.py:154-156).
  2. The front end. The API server makes an AsyncLLM (vllm/entrypoints/launchers/api_server/entry.py:94-99). AsyncLLM always starts the engine core in a separate process, through make_async_mp_client (vllm/v1/engine/async_llm.py:193-201). The page The engine client: AsyncLLM in the API server explains it.
  3. The engine core. EngineCoreProc in vllm/v1/engine/core.py runs the busy loop. The page The engine core loop: threads, queues and the busy loop explains it.
All that is left of the V0 async enginevllm/engine/async_llm_engine.py:1-7GitHub ↗
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

from vllm.v1.engine.async_llm import AsyncLLM

AsyncLLMEngine = AsyncLLM  # type: ignore
"""The `AsyncLLMEngine` class is an alias of [vllm.v1.engine.async_llm.AsyncLLM][]."""
The old name is one line. It points to the V1 AsyncLLM.

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 dive

The offline LLM class: how it reaches the engine core

The LLM class is a side path on this site. It differs from vllm serve in three ways:

  1. It uses the synchronous V1 LLMEngine, not AsyncLLM (vllm/entrypoints/llm.py:341-343).
  2. LLMEngine turns on multiprocess mode when VLLM_ENABLE_V1_MULTIPROCESSING is true (vllm/v1/engine/llm_engine.py:186-188). The default is "1" (vllm/envs.py:1442-1444). Then the client is a SyncMPClient with ZMQ and an engine core process. With VLLM_ENABLE_V1_MULTIPROCESSING=0, the client is an InprocClient, and the engine core runs in your process (vllm/v1/engine/core_client.py:128-136).
  3. It has statistics off by default (vllm/entrypoints/llm.py:225-226).

AsyncLLM does not read VLLM_ENABLE_V1_MULTIPROCESSING. It always calls make_async_mp_client (vllm/v1/engine/async_llm.py:193), and an asyncio client without multiprocessing raises NotImplementedError (vllm/v1/engine/core_client.py:114-118). So for vllm serve, the engine core is always a separate process.

    def make_client(
        multiprocess_mode: bool,
        asyncio_mode: bool,
        vllm_config: VllmConfig,
        executor_class: type[Executor],
        log_stats: bool,
        renderer: BaseRenderer | None = None,
    ) -> "EngineCoreClient":
        # renderer is passed through to the multiprocess clients, which start
        # the frontend MM warmup (renderer.start_mm_warmup_in_background) once
        # the engine-core processes have been forked, so warmup overlaps
        # engine-core load without a live thread at fork() time. In-process
        # clients do not take a renderer: the engine core is built in this
        # process, so there is no other process to overlap with and the MM
        # warmup runs synchronously inside renderer.warmup() instead.
        # TODO: support this for debugging purposes.
        if asyncio_mode and not multiprocess_mode:
            raise NotImplementedError(
                "Running EngineCore in asyncio without multiprocessing "
                "is not currently supported."
            )

        if multiprocess_mode and asyncio_mode:
            return EngineCoreClient.make_async_mp_client(
                vllm_config,
                executor_class,
                log_stats,
                renderer=renderer,
            )

        if multiprocess_mode and not asyncio_mode:
            return SyncMPClient(
                vllm_config,
                executor_class,
                log_stats,
                renderer=renderer,
            )

        return InprocClient(vllm_config, executor_class, log_stats)
EngineCoreClient.make_client(): the three client types. AsyncLLM uses the first branch; the LLM class uses one of the other two.
Deep dive

Hardware: built-in platforms and platform plugins

vLLM supports NVIDIA GPUs, AMD GPUs, Intel GPUs and x86, ARM and PowerPC CPUs. It also lists hardware plugins, for example Google TPUs, Intel Gaudi, IBM Spyre and Huawei Ascend (README.md:51).

In the code, each process selects one platform object, current_platform. The selection works like this (vllm/platforms/__init__.py:240-292):

  1. If VLLM_TARGET_DEVICE is cpu, the CPU platform wins at once (vllm/platforms/__init__.py:244-248).
  2. vLLM loads the out-of-tree platform plugins of the entry-point group vllm.platform_plugins (vllm/platforms/__init__.py:250).
  3. It calls every built-in detector and every plugin detector. A detector returns a class name or None. The five built-in detectors are tpu, cuda, rocm, xpu and cpu (vllm/platforms/__init__.py:231-237).
  4. One active plugin wins over the built-in detectors. Two active plugins, or two active built-in detectors, raise RuntimeError. No active detector gives UnspecifiedPlatform (vllm/platforms/__init__.py:272-292).

The TPU platform class itself lives in a separate package, tpu_inference (vllm/platforms/tpu.py:9-19). The pages Platform plugins and current_platform and How vLLM finds and loads plugins explain the plugins.

Deep dive

What is new compared with DeepInfer

DeepInfer "was originally built primarily on vLLM" (DeepInfer Explained). Much of its design looks like a simple vLLM. The table lists the main differences. The vLLM column cites the vLLM code. The page vLLM and DeepInfer compared gives the full comparison.

TopicDeepInfervLLM at this commit
How you use itOffline only: your script calls LLM.generate(), and the engine loop calls step() (Offline API and the step loop)Online first: vllm serve starts an OpenAI-compatible HTTP server (vllm/entrypoints/cli/serve.py:39-40). The LLM class also exists (vllm/entrypoints/llm.py:341-343).
ProcessesThe engine loop and the driver worker run in your script process; context-parallel followers run in their own processes (Worker)A front-end process and an engine core process, joined by ZMQ sockets (vllm/v1/engine/core_client.py:672-682); more worker processes with TP > 1
ModelsOne model, DeepSeek-V4.1-Flash (Model map)"200+ model architectures" (README.md:53-60), found by architecture name in the model registry (vllm/model_executor/models/registry.py:73, vllm/model_executor/models/registry.py:1565)
HardwareNVIDIA SM100 and Huawei Ascend 950 (NVIDIA and Ascend)Five built-in platform detectors plus out-of-tree platform plugins (vllm/platforms/__init__.py:231-237, vllm/platforms/__init__.py:250)
SchedulerC++; each step is a prefill batch or a decode batch, never both (The scheduler)Python; no prefill or decode phase, so one step can mix both (vllm/v1/core/sched/scheduler.py:624-633)
KV cache blocks128 tokens per block; no prefix cache in the release (Paged KV cache)Default 16 tokens, which the platform can change for the attention backend (vllm/config/cache.py:182, vllm/platforms/interface.py:771-781). Prefix caching is on by default when the model supports it (vllm/engine/arg_utils.py:3032, vllm/engine/arg_utils.py:3062-3063).
ParallelismDP, CP and EP; no TP and no PP (Parallelism basics)"Tensor, pipeline, data, expert, and context parallelism" (README.md:45)
PluginsNo entry-point groups in its packaging filesFive plugin groups in vllm/plugins/__init__.py (vllm/plugins/__init__.py:16-30) plus vllm.logits_processors (vllm/v1/worker/gpu/sample/logits_processor/loader.py:34)
torch.compileNot usedOn by default: optimization level O2 gives the mode VLLM_COMPILE (vllm/config/vllm.py:434, vllm/config/vllm.py:1806-1810)
Deep dive

Release and version numbers

The version is not written in a source file. The build reads it from git tags with setuptools-scm (pyproject.toml:8, pyproject.toml:49-50):

  1. get_vllm_version() calls setuptools_scm.get_version() with the command git describe --dirty --tags --long --abbrev=40 --match v[0-9]* (setup.py:26-35, setup.py:1360-1363). It writes the result to vllm/_version.py.
  2. It adds a local suffix for the build target (setup.py:1364-1400). VLLM_SKIP_VERSION_SUFFIX turns this off. The suffixes are +precompiled, +cu<ver> when the CUDA version is not the main one, +rocm<ver>, +tpu, +cpu, +xpu or +empty. The main CUDA version is 13.0 by default (vllm/envs.py:624-626).
  3. VLLM_VERSION_OVERRIDE replaces the whole version (setup.py:1352-1358).
  4. At run time, vllm/version.py imports __version__ from _version.py. When that fails, it warns and uses "dev" (vllm/version.py:4-12).

Worked example. At the pinned commit, the git describe command of setup.py gives v0.32.1rc0-307-g3e44c0db26.... That is the tag v0.32.1rc0, then 307 commits, then g and the commit hash. The hash has all 40 hex digits, because of --abbrev=40. So this code comes after the 0.32.1 release candidate 0. The exact string that setuptools-scm makes from this (a .dev307 version with the hash) depends on that library and is not in the vLLM repository.

You can read the version in three places. vllm --version reads the installed package metadata (vllm/entrypoints/cli/main.py:92-97). GET /version on a running server returns {"version": ...} (vllm/entrypoints/serve/instrumentator/basic.py:53-56). Python code reads vllm.__version__ (vllm/__init__.py:7).

Deep dive

Two parts with one paragraph each: the Rust front end and vllm/models/

The Rust front end. The folder rust/ holds "a Rust drop-in alternative frontend for vLLM". It talks to the Python engine core processes "via ZMQ over the existing engine boundary", and its README calls it "experimental" and "not feature-complete" (rust/README.md:3-5). VLLM_USE_RUST_FRONTEND=1 turns it on; the default is off (vllm/envs.py:1452-1456). Then vllm serve starts the Rust binary instead of the Python API server processes (vllm/entrypoints/cli/serve.py:346-361). This site does not explain it further.

The per-family model packages. Besides vllm/model_executor/models/, there is a second model tree, vllm/models/, with one package per model family, for example deepseek_v4 and qwen4_exp (vllm/models/). The model registry points some architecture names to these packages, for example "DeepseekV4ForCausalLM": ("vllm.models.deepseek_v4", ...) (vllm/model_executor/models/registry.py:99). This site uses llama.py as its one model example and does not explain these packages.

What can go wrong#

Why it matters. Know your entry point and its processes. Then you know which pages of this site apply to you, and which process owns each flag and each log line.

Check yourself#

  1. 1
    Which command starts the OpenAI-compatible server, and which Python function does it call first?
    easy
    Show answer

    vllm serve. The console script vllm calls vllm.entrypoints.cli.main:main (pyproject.toml:42-43), which dispatches to the serve subcommand (vllm/entrypoints/cli/main.py:116-117).

  2. 2
    An old example imports AsyncLLMEngine. Which class does it get?
    easy
    Show answer

    The V1 AsyncLLM. The module only contains the alias AsyncLLMEngine = AsyncLLM (vllm/engine/async_llm_engine.py:4-6).

  3. 3
    You set VLLM_ENABLE_V1_MULTIPROCESSING=0 and start vllm serve. Does the engine core now run inside the API server process?
    medium
    Show answer

    No. AsyncLLM always calls make_async_mp_client (vllm/v1/engine/async_llm.py:193). The variable only changes the LLMEngine of the offline LLM class (vllm/v1/engine/llm_engine.py:186-188).

  4. 4
    A machine has an NVIDIA GPU and you install one out-of-tree platform plugin whose detector also returns a class name. Which platform does vLLM select, and what happens with two such plugins?
    hard
    Show answer

    The plugin platform: when exactly one out-of-tree plugin is active, it wins over the built-in CUDA detector (vllm/platforms/__init__.py:277-279). With two active plugins, start-up stops with RuntimeError("Only one platform plugin can be activated ...") (vllm/platforms/__init__.py:272-276).

  5. 5
    You build vLLM from source with CUDA 12.9 at this commit. Which suffix does the version get, and why?
    hard
    Show answer

    +cu129, unless VLLM_SKIP_VERSION_SUFFIX is set or the build uses precompiled extensions. The CUDA version 12.9 is not the main version 13.0 (vllm/envs.py:624-626). So the build adds cu and the first three digits of the version without dots (setup.py:1376-1382).