| 1 | # Architecture Overview |
| 2 | |
| 3 | This document provides an overview of the vLLM architecture. |
| 4 | |
| 5 | [TOC] |
| 6 | |
| 7 | ## Entrypoints |
| 8 | |
| 9 | vLLM provides a number of entrypoints for interacting with the system. The |
| 10 | following diagram shows the relationship between them. |
| 11 | |
| 12 |  |
| 13 | |
| 14 | ### LLM Class |
| 15 | |
| 16 | The LLM class provides the primary Python interface for doing offline inference, |
| 17 | which is interacting with a model without using a separate model inference |
| 18 | server. |
| 19 | |
| 20 | Here is a sample of `LLM` class usage: |
| 21 | |
| 22 | ??? code |
| 23 | |
| 24 | ```python |
| 25 | from vllm import LLM, SamplingParams |
| 26 | |
| 27 | # Define a list of input prompts |
| 28 | prompts = [ |
| 29 | "Hello, my name is", |
| 30 | "The capital of France is", |
| 31 | "The largest ocean is", |
| 32 | ] |
| 33 | |
| 34 | # Define sampling parameters |
| 35 | sampling_params = SamplingParams(temperature=0.8, top_p=0.95) |
| 36 | |
| 37 | # Initialize the LLM engine with the OPT-125M model |
| 38 | llm = LLM(model="facebook/opt-125m") |
| 39 | |
| 40 | # Generate outputs for the input prompts |
| 41 | outputs = llm.generate(prompts, sampling_params) |
| 42 | |
| 43 | # Print the generated outputs |
| 44 | for output in outputs: |
| 45 | prompt = output.prompt |
| 46 | generated_text = output.outputs[0].text |
| 47 | print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}") |
| 48 | ``` |
| 49 | |
| 50 | More API details can be found in the [Offline Inference](../api/README.md#offline-inference) section of the API docs. |
| 51 | |
| 52 | The code for the `LLM` class can be found in [vllm/entrypoints/llm.py](../../vllm/entrypoints/llm.py). |
| 53 | |
| 54 | ### Online Serving |
| 55 | |
| 56 | The second primary interface to vLLM is via its online server. |
| 57 | This server can be started using the `vllm serve` command. |
| 58 | |
| 59 | ```bash |
| 60 | vllm serve <model> |
| 61 | ``` |
| 62 | |
| 63 | The code for the `vllm` CLI can be found in [vllm/entrypoints/cli/main.py](../../vllm/entrypoints/cli/main.py). |
| 64 | |
| 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 |
| 68 | |
| 69 | vLLM V1 uses a multi-process architecture to separate concerns and maximize throughput. Understanding this architecture is important for properly sizing CPU resources in your deployment. The key processes are: |
| 70 | |
| 71 | ### API Server Process |
| 72 | |
| 73 | The API server process handles HTTP requests (e.g., the OpenAI-compatible API), performs input processing (tokenization, multi-modal data loading), and streams results back to clients. It communicates with the engine core process(es) via ZMQ sockets. |
| 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 | |
| 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 | |
| 81 | The engine core process runs the scheduler, manages KV cache, and coordinates model execution across GPU workers. It runs a busy loop that continuously schedules requests and dispatches work to the GPU workers. |
| 82 | |
| 83 | There is **1 engine core process per data parallel rank**. For example, with `--data-parallel-size 4`, there are 4 engine core processes. |
| 84 | |
| 85 | The code can be found in [vllm/v1/engine/core.py](../../vllm/v1/engine/core.py) and [vllm/v1/engine/utils.py](../../vllm/v1/engine/utils.py). |
| 86 | |
| 87 | ### GPU Worker Processes |
| 88 | |
| 89 | Each GPU is managed by a dedicated worker process. The worker process loads model weights, executes forward passes, and manages GPU memory. Workers communicate with the engine core process that owns them. |
| 90 | |
| 91 | There is **1 worker process per GPU**. The total number of GPU worker processes equals `tensor_parallel_size x pipeline_parallel_size` per engine core. |
| 92 | |
| 93 | The code can be found in [vllm/v1/executor/multiproc_executor.py](../../vllm/v1/executor/multiproc_executor.py) and [vllm/v1/worker/gpu_worker.py](../../vllm/v1/worker/gpu_worker.py). |
| 94 | |
| 95 | ### DP Coordinator Process (conditional) |
| 96 | |
| 97 | When using data parallelism (`--data-parallel-size > 1`), an additional coordinator process manages load balancing across DP ranks and coordinates synchronized forward passes for MoE models. |
| 98 | |
| 99 | There is **1 DP coordinator process** (only when data parallelism is enabled). |
| 100 | |
| 101 | The code can be found in [vllm/v1/engine/coordinator.py](../../vllm/v1/engine/coordinator.py). |
| 102 | |
| 103 | ### Process Count Summary |
| 104 | |
| 105 | For a deployment with `N` GPUs, `TP` tensor parallel size, `DP` data parallel size, and `A` API server count: |
| 106 | |
| 107 | | Process Type | Count | Notes | |
| 108 | | - | - | - | |
| 109 | | API Server | `A` (default `DP`) | Handles HTTP requests and input processing | |
| 110 | | Engine Core | `DP` (default 1) | Scheduler and KV cache management | |
| 111 | | GPU Worker | `N` (= `DP x PP x TP`) | One per GPU, executes model forward passes | |
| 112 | | DP Coordinator | 1 if `DP > 1`, else 0 | Load balancing across DP ranks | |
| 113 | | **Total** | **`A + DP + N` (+ 1 if DP > 1)** | | |
| 114 | |
| 115 | For example, a typical single-node deployment with 4 GPUs (`vllm serve -tp=4`) has: |
| 116 | |
| 117 | - 1 API server + 1 engine core + 4 GPU workers = **6 processes** |
| 118 | |
| 119 | <figure markdown="1"> |
| 120 |  |
| 121 | </figure> |
| 122 | |
| 123 | A data parallel deployment with 8 GPUs (`vllm serve -tp=2 -dp=4`) has: |
| 124 | |
| 125 | - 4 API servers + 4 engine cores + 8 GPU workers + 1 DP coordinator = **17 processes** |
| 126 | |
| 127 | <figure markdown="1"> |
| 128 |  |
| 129 | </figure> |
| 130 | |
| 131 | For CPU resource sizing recommendations, see |
| 132 | [CPU Resources for GPU Deployments](../configuration/optimization.md#cpu-resources-for-gpu-deployments). |
| 133 | |
| 134 | ## LLM Engine |
| 135 | |
| 136 | The `LLMEngine` and `AsyncLLMEngine` classes are central to the functioning of |
| 137 | the vLLM system, handling model inference and asynchronous request processing. |
| 138 | |
| 139 |  |
| 140 | |
| 141 | ### LLMEngine |
| 142 | |
| 143 | The `LLMEngine` class is the core component of the vLLM engine. It is |
| 144 | responsible for receiving requests from clients and generating outputs from the |
| 145 | model. The `LLMEngine` includes input processing, model execution (possibly |
| 146 | distributed across multiple hosts and/or GPUs), scheduling, and output |
| 147 | processing. |
| 148 | |
| 149 | - **Input Processing**: Handles tokenization of input text using the specified |
| 150 | tokenizer. |
| 151 | - **Scheduling**: Chooses which requests are processed in each step. |
| 152 | - **Model Execution**: Manages the execution of the language model, including |
| 153 | distributed execution across multiple GPUs. |
| 154 | - **Output Processing**: Processes the outputs generated by the model, decoding the |
| 155 | token IDs from a language model into human-readable text. |
| 156 | |
| 157 | The code for `LLMEngine` can be found in [vllm/engine/llm_engine.py](../../vllm/engine/llm_engine.py). |
| 158 | |
| 159 | ### AsyncLLMEngine |
| 160 | |
| 161 | The `AsyncLLMEngine` class is an asynchronous wrapper for the `LLMEngine` class. |
| 162 | It uses `asyncio` to create a background loop that continuously processes |
| 163 | incoming requests. The `AsyncLLMEngine` is designed for online serving, where it |
| 164 | can handle multiple concurrent requests and stream outputs to clients. |
| 165 | |
| 166 | The OpenAI-compatible API server uses the `AsyncLLMEngine`. There is also a demo |
| 167 | API server that serves as a simpler example in [examples/applications/api_server/server.py](../../examples/applications/api_server/server.py). |
| 168 | |
| 169 | The code for `AsyncLLMEngine` can be found in [vllm/engine/async_llm_engine.py](../../vllm/engine/async_llm_engine.py). |
| 170 | |
| 171 | ## Worker |
| 172 | |
| 173 | A worker is a process that runs the model inference. vLLM follows the common |
| 174 | practice of using one process to control one accelerator device, such as GPUs. |
| 175 | For example, if we use tensor parallelism of size 2 and pipeline parallelism of |
| 176 | size 2, we will have 4 workers in total. Workers are identified by their |
| 177 | `rank` and `local_rank`. `rank` is used for global orchestration, while |
| 178 | `local_rank` is mainly used for assigning the accelerator device and accessing |
| 179 | local resources such as the file system and shared memory. |
| 180 | |
| 181 | ## Model Runner |
| 182 | |
| 183 | Every worker has one model runner object, responsible for loading and running |
| 184 | the model. Much of the model execution logic resides here, such as preparing |
| 185 | input tensors and capturing cudagraphs. |
| 186 | |
| 187 | ## Model |
| 188 | |
| 189 | Every model runner object has one model object, which is the actual |
| 190 | `torch.nn.Module` instance. See [huggingface_integration](huggingface_integration.md) for how various |
| 191 | configurations affect the class we ultimately get. |
| 192 | |
| 193 | ## Class Hierarchy |
| 194 | |
| 195 | The following figure shows the class hierarchy of vLLM: |
| 196 | |
| 197 |  |
| 198 | |
| 199 | There are several important design choices behind this class hierarchy: |
| 200 | |
| 201 | 1\. **Extensibility**: All classes in the hierarchy accept a configuration object |
| 202 | containing all the necessary information. The [VllmConfig](https://github.com/vllm-project/vllm/blob/d1c6799b8870e513bf4f2305cbf6cda9fc3d773b/vllm/config.py#L2036) |
| 203 | class is the main configuration object that is passed around. The class |
| 204 | hierarchy is quite deep, and every class needs to read the configuration it is |
| 205 | interested in. By encapsulating all configurations in one object, we can easily |
| 206 | pass the configuration object around and access the configuration we need. |
| 207 | Suppose we want to add a new feature (this is often the case given how fast the |
| 208 | field of LLM inference is evolving) that only touches the model runner. We will |
| 209 | have to add a new configuration option in the `VllmConfig` class. Since we pass |
| 210 | the whole config object around, we only need to add the configuration option to |
| 211 | the `VllmConfig` class, and the model runner can access it directly. We don't |
| 212 | need to change the constructor of the engine, worker, or model class to pass the |
| 213 | new configuration option. |
| 214 | |
| 215 | 2\. **Uniformity**: The model runner needs a unified interface to create and |
| 216 | initialize the model. vLLM supports more than 50 types of popular open-source |
| 217 | models. Each model has its own initialization logic. If the constructor |
| 218 | signature varies with models, the model runner does not know how to call the |
| 219 | constructor accordingly, without complicated and error-prone inspection logic. |
| 220 | By making the constructor of the model class uniform, the model runner can |
| 221 | easily create and initialize the model without knowing the specific model type. |
| 222 | This is also useful for composing models. Vision-language models often consist |
| 223 | of a vision model and a language model. By making the constructor uniform, we |
| 224 | can easily create a vision model and a language model and compose them into a |
| 225 | vision-language model. |
| 226 | |
| 227 | !!! note |
| 228 | To support this change, all vLLM models' signatures have been updated to: |
| 229 | |
| 230 | ```python |
| 231 | def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): |
| 232 | ``` |
| 233 | |
| 234 | To avoid accidentally passing incorrect arguments, the constructor is now keyword-only. This ensures that the constructor will raise an error if old configurations are passed. vLLM developers have already made this change for all models within vLLM. For out-of-tree registered models, developers need to update their models, for example by adding shim code to adapt the old constructor signature to the new one: |
| 235 | |
| 236 | ??? code |
| 237 | |
| 238 | ```python |
| 239 | class MyOldModel(nn.Module): |
| 240 | def __init__( |
| 241 | self, |
| 242 | config, |
| 243 | cache_config: Optional[CacheConfig] = None, |
| 244 | quant_config: Optional[QuantizationConfig] = None, |
| 245 | lora_config: Optional[LoRAConfig] = None, |
| 246 | prefix: str = "", |
| 247 | ) -> None: |
| 248 | ... |
| 249 | |
| 250 | from vllm.config import VllmConfig |
| 251 | class MyNewModel(MyOldModel): |
| 252 | def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""): |
| 253 | config = vllm_config.model_config.hf_config |
| 254 | cache_config = vllm_config.cache_config |
| 255 | quant_config = vllm_config.quant_config |
| 256 | lora_config = vllm_config.lora_config |
| 257 | super().__init__(config, cache_config, quant_config, lora_config, prefix) |
| 258 | |
| 259 | from packaging import version |
| 260 | if version.parse(__version__) >= version.parse("0.6.4"): |
| 261 | MyModel = MyNewModel |
| 262 | else: |
| 263 | MyModel = MyOldModel |
| 264 | ``` |
| 265 | |
| 266 | This way, the model can work with both old and new versions of vLLM. |
| 267 | |
| 268 | 3\. **Sharding and Quantization at Initialization**: Certain features require |
| 269 | changing the model weights. For example, tensor parallelism needs to shard the |
| 270 | model weights, and quantization needs to quantize the model weights. There are |
| 271 | two possible ways to implement this feature. One way is to change the model |
| 272 | weights after the model is initialized. The other way is to change the model |
| 273 | weights during the model initialization. vLLM chooses the latter. The first |
| 274 | approach is not scalable to large models. Suppose we want to run a 405B model |
| 275 | (with roughly 810GB weights) with 16 H100 80GB GPUs. Ideally, every GPU should |
| 276 | only load 50GB weights. If we change the model weights after the model is |
| 277 | initialized, we need to load the full 810GB weights to every GPU and then shard |
| 278 | the weights, leading to a huge memory overhead. Instead, if we shard the weights |
| 279 | during the model initialization, every layer will only create a shard of the |
| 280 | weights it needs, leading to a much smaller memory overhead. The same idea |
| 281 | applies to quantization. Note that we also add an additional argument `prefix` |
| 282 | to the model's constructor so that the model can initialize itself differently |
| 283 | based on the prefix. This is useful for non-uniform quantization, where |
| 284 | different parts of the model are quantized differently. The `prefix` is |
| 285 | usually an empty string for the top-level model and a string like `"vision"` |
| 286 | or `"language"` for the sub-models. In general, it matches the name of the |
| 287 | module's state dict in the checkpoint file. |
| 288 | |
| 289 | One disadvantage of this design is that it is hard to write unit tests for |
| 290 | individual components in vLLM because every component needs to be initialized by |
| 291 | a complete config object. We solve this problem by providing a default |
| 292 | initialization function that creates a default config object with all fields set |
| 293 | to `None`. If the component we want to test only cares about a few fields in |
| 294 | the config object, we can create a default config object and set the fields we |
| 295 | care about. This way, we can test the component in isolation. Note that many |
| 296 | tests in vLLM are end-to-end tests that test the whole system, so this is not a |
| 297 | big problem. |
| 298 | |
| 299 | In summary, the complete config object `VllmConfig` can be treated as an |
| 300 | engine-level global state that is shared among all vLLM classes. |