vLLM Deep Dive Part 1: Architecture Overview
第一部分:vLLM 架构概览
简介
在深入研究具体组件之前,我们需要先了解 vLLM 的整体架构。本文梳理了各主要组件及其交互方式,为后续各部分的深入探讨奠定基础。
全局视角
vLLM 的设计围绕以下几个核心原则:
- 关注点分离:不同职责(调度、执行、服务)由各自独立的组件负责处理
- 进程隔离:V1 架构采用多进程设计,以提升健壮性和 CPU 利用率
- 异步处理:请求在流水线中流转,不会产生阻塞
- 默认分布式:从底层设计上即支持多 GPU 执行
高层数据流
当你向 vLLM 发送一个请求时,整个流程如下:
1 | User Request (HTTP/gRPC) |
V1 多进程架构
V1 架构(于 2024 年底引入)采用多进程设计,以实现更好的 CPU 利用率和进程隔离。下面逐一介绍各进程类型:
1. API Server 进程
职责:处理前端请求、I/O,以及与 engine core 的通信
主要任务:
- 接收并校验 OpenAI-compatible HTTP 请求
- 对输入文本进行 tokenize
- 加载多模态数据(图像、音频)
- 将输出以流式方式返回给客户端
- 处理 API 鉴权及其他前端请求逻辑
核心实现:OpenAI-compatible HTTP 服务位于 vllm/entrypoints/openai/api_server.py;gRPC 服务位于 vllm/entrypoints/grpc_server.py。
API server 对于模型执行而言是无状态的。它不了解 GPU 内存、KV cache 或模型权重,只负责:
- 将用户请求转换为
EngineCoreRequest对象 - 通过 ZMQ 将其发送给 engine core
- 接收返回的
EngineCoreOutput对象 - 将其转换为 API 响应
进程数量:默认单 API server;在在线 data parallel 部署中,api_server_count 的默认值会随 internal / hybrid / external load-balancing 模式变化,并可由 --api-server-count 覆盖。
CPU 线程:使用 VLLM_MEDIA_LOADING_THREAD_COUNT 个线程(默认 8)并行加载媒体文件。
2. Engine Core 进程
职责:调度请求并协调模型执行
主要任务:
- 维护请求队列
- 运行 scheduler 以决定计算内容
- 管理 KV cache 分配
- 协调 GPU worker
- 处理请求抢占与换出
核心实现:vllm/v1/engine/core.py
engine core 运行一个紧凑的循环(run_busy_loop,core.py:1138):
1 | def run_busy_loop(self): |
进程数量:每个 data parallel rank 对应一个。设置 --data-parallel-size 4 时,将启动 4 个 engine core。
CPU 用量:运行忙循环以实现低延迟的调度决策。
3. GPU Worker 进程
职责:在 GPU 上执行模型前向传播
主要任务:
- 将模型权重加载到 GPU
- 执行前向传播
- 管理 GPU 内存
- 运行 CUDA kernel(attention、FFN 等)
- 参与集合通信操作(用于 tensor parallelism / pipeline parallelism)
核心实现:vllm/v1/worker/gpu_worker.py
在 MultiprocExecutor 下,通常会看到“每个 GPU 一个 worker 进程”;但在单机单卡默认的 UniProcExecutor 路径中,worker 会运行在 EngineCore 进程内,而不是独立的 OS 进程。worker 负责:
- 加载其对应分片的模型权重
- 从对应的 engine core 接收执行请求
- 运行模型前向传播
- 将执行结果交回 executor / engine core 的后续处理路径
进程数量:取决于 executor 后端。对于 8 个 GPU、设置 --tensor-parallel-size 4 --data-parallel-size 2 这类多 GPU 部署,通常会看到 8 个 worker,由 2 个 engine core 分别协调 2 组 tensor parallel worker。
4. DP Coordinator 进程(按需启动)
职责:在 data parallel 部署中汇总引擎状态,并为前端负载均衡与 wave coordination 提供协调信息
主要任务:
- 汇总各 DP engine 的 waiting / running 队列统计
- 将这些统计发布给 front-end client,供其做负载均衡决策
- 维护 request wave / running state,并在需要时广播
START_DP_WAVE
核心实现:vllm/v1/engine/coordinator.py
进程数量:当 --data-parallel-size > 1 时启动 1 个,否则不启动。
进程数量示例
下面来看几个具体示例。需要注意的是,实际 OS 进程数量会随 executor 后端与 load-balancing 模式变化;以下示例以当前 CLI 默认路径为参考:
示例 1:单 GPU
1 | vllm serve meta-llama/Llama-3-8B |
进程:
- 1 个 API Server(当前进程)
- 1 个 Engine Core
- GPU worker 以内嵌方式运行在 EngineCore 进程内(
UniProcExecutor) - 共计:通常约 2 个 OS 进程
示例 2:Tensor Parallelism(4 个 GPU)
1 | vllm serve meta-llama/Llama-3-70B --tensor-parallel-size 4 |
进程:
- 1 个 API Server
- 1 个 Engine Core
- 4 个 GPU Worker(每个 GPU 各一个)
- 共计:6 个进程
示例 3:Data Parallelism(4 个 GPU)
1 | vllm serve meta-llama/Llama-3-8B --data-parallel-size 4 |
进程:
- 1 个 launcher / manager
- 4 个 API Server
- 4 个 Engine Core(每个 DP rank 各一个)
- 4 个 GPU Worker(每个 GPU 各一个)
- 1 个 DP Coordinator
- 共计:单机 internal-LB 部署下通常约 14 个进程
示例 4:混合并行(8 个 GPU)
1 | vllm serve meta-llama/Llama-3-70B --tensor-parallel-size 2 --data-parallel-size 4 |
进程:
- 1 个 launcher / manager
- 4 个 API Server
- 4 个 Engine Core(每个 DP rank 各一个)
- 8 个 GPU Worker(每个 DP rank 2 个,每个 GPU 各一个)
- 1 个 DP Coordinator
- 共计:单机 internal-LB 部署下通常约 18 个进程
核心组件详解
LLMEngine
LLMEngine 类是离线推理(直接使用 Python API)的主要入口点。
位置:vllm/v1/engine/llm_engine.py
主要职责:
- 创建并管理 engine core
- 通过
InputProcessor处理输入 - 通过
OutputProcessor转换输出 - 管理请求生命周期
使用示例:
1 | from vllm import LLM, SamplingParams |
在底层,LLM 创建 LLMEngine,LLMEngine 再创建 EngineCore,由其协调各 worker。
Scheduler
Scheduler 是 vLLM 的核心大脑,负责决策:
- 每次迭代处理哪些请求
- 每个请求计算多少 token
- 何时以及对哪些请求进行抢占
- 如何分配 KV cache 块
位置:vllm/v1/core/sched/scheduler.py
Scheduler 维护三个队列:
- 等待队列(Waiting):等待开始处理的新请求
- 运行队列(Running):正在被处理的请求
- 暂跳队列(Skipped Waiting):因依赖关系而被临时跳过的请求
调度算法(简化版):
1 | def schedule(self) -> SchedulerOutput: |
我们将在第三部分深入探讨 scheduler。
KV Cache Manager
使用 PagedAttention 管理 attention key-value cache 的内存。
位置:vllm/v1/core/kv_cache_manager.py
核心概念:
- Block(块):固定大小的 KV cache 单元(例如 16 个 token)
- Block Pool(块池):预分配的块集合
- Block Table(块表):逻辑位置到物理块的映射
- Prefix Cache(前缀缓存):用于共享公共 prompt 前缀的块
示例:若块大小为 16,共有 1000 个块,则可以服务于:
- 1 个拥有 16,000 个 token 的请求,或
- 16 个各有 1,000 个 token 的请求,或
- 任何能放入 1000 个块的组合
我们将在第二部分深入剖析 PagedAttention。
Worker 与后端 Runner
worker 进程负责加载模型并执行前向传播。
Worker(vllm/v1/worker/gpu_worker.py):
- 管理 GPU 设备
- 初始化模型权重
- 与其他 worker 协调(用于 tensor parallelism / pipeline parallelism)
具体后端的 model runner(例如 GPUModelRunner / CPUModelRunner / XPUModelRunner,以及较新的 GPU runner 路径):
- 准备输入 tensor
- 执行模型前向传播
- 应用 CUDA graph 优化
- 产出供 executor / engine core 后续处理的执行结果
请求生命周期
让我们跟踪一个完整请求在系统中的流转过程:
1. 请求到达
1 | POST /v1/completions |
2. API Server 处理
- 校验请求
- 对 prompt 进行 tokenize:
[791, 3139, 315, 9822, 374] - 创建
EngineCoreRequest对象 - 通过 ZMQ 发送至 engine core
3. Engine Core 接收
- 将请求加入 scheduler 的等待队列
- 请求 ID 通常已在前端
InputProcessor阶段分配/随机化,并随EngineCoreRequest一起传入 - 初始化请求状态
4. 首次调度迭代
- Scheduler 发现等待队列中的新请求
- 检查 prefix cache / KV cache 状态
- 为本轮需要计算的 token 分配 KV cache 块(块大小为 16 token 时,这个例子中通常只需要 1 个块)
- 生成本轮
SchedulerOutput
5. Worker 执行
- executor / worker 接收调度结果
- 准备输入 tensor
- 运行模型前向传播
- 若需要采样,则通过
model_executor.sample_tokens(...)生成本轮 token 输出
6. 输出处理
- engine core 使用
scheduler.update_from_output(...)更新请求状态 - 将
EngineCoreOutput通过 ZMQ 发送给 API server
7. API Server 流式输出
- 从 engine core 接收 token
- Detokenize:”Paris”
- 流式返回给客户端:
data: {"text": "Paris", "finish_reason": null}
8. 后续迭代
- 请求移入运行队列
- Scheduler 持续分配 token
- 每次迭代:计算 1 个新 token(decode 阶段)
- 将每个 token 流式发送给客户端
9. 请求完成
- 达到 max_tokens 上限或生成 EOS
- 释放 KV cache 块(或保留在 prefix cache 中)
- 向客户端发送最终完成结果
- 从 scheduler 中移除请求
配置与初始化
vLLM 的配置统一集中在 VllmConfig 中:
1 | from vllm.config import VllmConfig |
许多核心组件会共享或持有同一个 VllmConfig,以确保配置一致性。
主要配置项:
- ModelConfig:模型名称、dtype、tokenizer
- CacheConfig:KV cache 大小、块大小、prefix caching
- SchedulerConfig:最大批量大小、调度策略
- ParallelConfig:TP/PP/DP 规模、分布式后端
类层次结构
类层次结构遵循一致的模式:
1 | LLMEngine |
许多核心类会接收或持有 VllmConfig,但并非所有类都直接以它作为构造参数;例如 OutputProcessor 并不直接接收 VllmConfig。
进程间通信
ZMQ Sockets
API server 与 engine core 通过 ZMQ 进行通信,消息编码采用基于 MsgpackEncoder / MsgpackDecoder 的 multipart frame:
- front-end / API server 一侧会把
EngineCoreRequest编码成 multipart 消息并通过send_multipart(...)发送 - engine core 一侧通过
recv_multipart(...)接收,再按消息类型用MsgpackDecoder解码 - 在需要传输 tensor 的路径上,还会配合 tensor IPC,而不是把所有内容都塞进单个 pickle payload
为什么选择 ZMQ?
- 高性能(微秒级延迟)
- 灵活的通信模式(req-rep、pub-sub、push-pull)
- 内置队列管理
- 语言无关
NCCL 用于 GPU 通信
GPU worker 使用 NCCL 进行集合通信操作:
1 | # Tensor parallelism: all-reduce across GPUs |
通信模式:
- Tensor Parallel:在 attention/FFN 之后执行 All-reduce
- Pipeline Parallel:各阶段之间执行发送/接收操作
- Data Parallel:前向传播期间无需通信
内存布局
理解内存布局对于 vLLM 至关重要:
GPU 内存分布
1 | Total GPU Memory: 80GB (H100) |
KV Cache 内存
对于块大小为 16 token 的 8B 模型:
- 每个块存储 32 个 attention 层的 K 和 V
- 每块大小:16 token × 32 层 × 2(K,V)× 4096 维 × 2 字节 = 8.4 MB
- 若 KV cache 分配 60GB:约 7,100 个块
- 总容量:跨所有请求约 113,600 个 token
性能特征
H100 GPU 上的典型性能表现:
| 指标 | 数值 |
|---|---|
| 吞吐量(Throughput) | ~8,000 tokens/sec(Llama-3-8B) |
| 延迟(TTFT) | ~20-50ms |
| 延迟(TPOT) | ~10-15ms |
| 最大批量大小(Max Batch Size) | ~256 并发请求 |
| 内存效率(Memory Efficiency) | ~95%(相比不使用 PagedAttention 时的 ~60%) |
下一步
现在我们已经了解了整体架构,接下来可以深入探讨各具体组件:
- 第二部分:PagedAttention 与 KV cache 管理
- 第三部分:Scheduler 的决策过程
- 第四部分:请求处理与状态管理
- 第五部分:分布式执行与并行策略
关键要点
- V1 采用多进程架构,以提升 CPU 利用率和故障隔离能力
- Scheduler 是核心协调者,负责决策每次迭代的计算内容
- KV cache 管理(PagedAttention)是内存效率的关键所在
- 进程数量随并行度扩展:但会受到 executor 后端、load-balancing 模式以及是否存在 launcher / manager 进程的影响
- ZMQ 实现了高效的进程间通信
- 许多核心组件共享或持有统一的
VllmConfig,以保持配置一致性
参考资料
在下一篇文章中,我们将详细探讨 PagedAttention,了解 vLLM 如何通过精巧的内存管理实现近乎零浪费的内存利用。