优化与调优¶
本指南涵盖 vLLM V1 的优化策略和性能调优。
提示
内存不足?请参阅此指南,了解如何节省内存。
优化级别¶
vLLM 提供 4 个优化级别(-O0、-O1、-O2、-O3),允许用户在启动时间和性能之间进行权衡。
-O0:无优化。启动速度最快,但性能最低。-O1:快速优化。包含简单的编译和快速融合,以及分段 CUDA 图 (PIECEWISE cudagraphs)。-O2:默认优化。包含额外的编译范围、更多的融合以及完整和分段的 CUDA 图 (FULL_AND_PIECEWISE cudagraphs)。-O3:积极优化。目前与-O2相同,但未来可能包含更多耗时的或实验性的优化。
有关详细信息,请参阅优化级别文档。
抢占 (Preemption)¶
由于 Transformer 架构的自回归特性,有时 KV 缓存空间不足以处理所有批处理请求。在这种情况下,vLLM 可以抢占请求以释放 KV 缓存空间供其他请求使用。当有足够的 KV 缓存空间时,被抢占的请求将重新计算。发生这种情况时,您可能会看到以下警告。
WARNING 05-09 00:49:33 scheduler.py:1057 Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space. This can affect the end-to-end performance. Increase gpu_memory_utilization or tensor_parallel_size to provide more KV cache memory. total_cumulative_preemption_cnt=1
虽然该机制确保了系统健壮性,但抢占和重新计算会对端到端延迟产生不利影响。如果您频繁遇到抢占,请考虑以下操作:
- 增加
gpu_memory_utilization。vLLM 使用此百分比的内存预分配 GPU 缓存。通过提高利用率,可以提供更多的 KV 缓存空间。 - 减小
max_num_seqs或max_num_batched_tokens。这减少了批处理中并发请求的数量,从而减少了对 KV 缓存空间的需求。 - 增加
tensor_parallel_size。这将模型权重分片到多个 GPU,使每个 GPU 有更多内存用于 KV 缓存。但是,增加该值可能会导致过多的同步开销。 - 增加
pipeline_parallel_size。这将模型层分布到多个 GPU,减少了每个 GPU 上模型权重所需的内存,间接留出了更多内存用于 KV 缓存。但是,增加该值可能会导致延迟惩罚。
您可以通过 vLLM 公开的 Prometheus 指标监控抢占请求的数量。此外,您可以通过设置 disable_log_stats=False 来记录抢占请求的累计数量。
在 vLLM V1 中,默认的抢占模式是 RECOMPUTE 而不是 SWAP,因为在 V1 架构中重新计算的开销较低。
分块预填充 (Chunked Prefill)¶
分块预填充允许 vLLM 将大型预填充任务处理为更小的块,并将其与解码请求一起批处理。此功能通过更好地平衡计算密集型(预填充)和内存密集型(解码)操作,有助于提高吞吐量和延迟。
在 V1 中,只要可能,分块预填充默认处于启用状态。启用分块预填充后,调度策略会优先考虑解码请求。它会在安排任何预填充操作之前,先批处理所有待处理的解码请求。当 max_num_batched_tokens 预算中有可用 tokens 时,它会安排待处理的预填充。如果待处理的预填充请求无法放入 max_num_batched_tokens,它会自动对其进行分块。
此策略有两个好处:
- 它改善了 ITL(Token 间延迟)和生成解码,因为解码请求被优先处理。
- 通过将计算密集型(预填充)和内存密集型(解码)请求定位到同一批次,有助于实现更好的 GPU 利用率。
使用分块预填充进行性能调优¶
您可以通过调整 max_num_batched_tokens 来调优性能:
- 较小的值(例如 2048)可获得更好的 Token 间延迟 (ITL),因为减慢解码速度的预填充任务较少。
- 较大的值可获得更好的首个 Token 延迟 (TTFT),因为您可以在一个批次中处理更多的预填充 Token。
- 为了获得最佳吞吐量,我们建议将
max_num_batched_tokens > 8192,特别是对于大型 GPU 上的较小模型。 - 如果
max_num_batched_tokens与max_model_len相同,这几乎等同于 V0 的默认调度策略(除了它仍然优先考虑解码)。
警告
当分块预填充被禁用时,max_num_batched_tokens 必须大于 max_model_len。
在这种情况下,如果 max_num_batched_tokens < max_model_len,vLLM 可能会在服务器启动时崩溃。
from vllm import LLM
# Set max_num_batched_tokens to tune performance
llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct", max_num_batched_tokens=16384)
有关更多详细信息,请参阅相关论文(https://arxiv.org/pdf/2401.08671 或 https://arxiv.org/pdf/2308.16369)。
并行策略¶
vLLM 支持多种并行策略,这些策略可以组合使用,以优化不同硬件配置下的性能。
张量并行 (TP)¶
张量并行将模型参数分片到每个模型层内的多个 GPU 上。这是单节点内大型模型推理最常见的策略。
何时使用:
- 当模型太大而无法放入单个 GPU 时。
- 当您需要减少每个 GPU 的内存压力以腾出更多 KV 缓存空间以实现更高吞吐量时。
from vllm import LLM
# Split model across 4 GPUs
llm = LLM(model="meta-llama/Llama-3.3-70B-Instruct", tensor_parallel_size=4)
对于太大而无法放入单个 GPU 的模型(如 70B 参数模型),张量并行是必不可少的。
流水线并行 (PP)¶
流水线并行将模型层分布在多个 GPU 上。每个 GPU 按顺序处理模型的不同部分。
何时使用:
- 当您已经用尽高效的张量并行,但仍需要进一步分发模型或跨节点分发时。
- 对于层分发比张量分片更高效的非常深且窄的模型。
流水线并行可以与张量并行结合使用,用于超大型模型。
from vllm import LLM
# Combine pipeline and tensor parallelism
llm = LLM(
model="meta-llama/Llama-3.3-70B-Instruct,
tensor_parallel_size=4,
pipeline_parallel_size=2,
)
专家并行 (EP)¶
专家并行是一种专门针对混合专家 (MoE) 模型的并行形式,其中不同的专家网络分布在不同的 GPU 上。
何时使用:
- 专门针对 MoE 模型(如 DeepSeekV3、Qwen3MoE、Llama-4)。
- 当您希望平衡 GPU 之间的专家计算负载时。
通过设置 enable_expert_parallel=True 启用专家并行,它将对 MoE 层使用专家并行而不是张量并行。它将使用与您为张量并行设置的相同的并行度。
数据并行 (DP)¶
数据并行在多个 GPU 组中复制整个模型,并并行处理不同的请求批次。
何时使用:
- 当您有足够的 GPU 来复制整个模型时。
- 当您需要扩展吞吐量而不是模型大小时。
- 在多用户环境中,请求批次之间的隔离是有益的。
数据并行可以与其他并行策略结合使用,并通过 data_parallel_size=N 进行设置。请注意,MoE 层将根据张量并行大小和数据并行大小的乘积进行分片。
多插槽 GPU 节点的 NUMA 绑定¶
在多插槽 GPU 服务器上,如果 GPU 工作进程的 CPU 执行和内存分配偏离了距离 GPU 最近的 NUMA 节点,性能就会下降。vLLM 可以在 Python 子进程启动之前使用 numactl 固定每个工作进程,这样解释器、导入和早期分配器状态就会从一开始就使用所需的 NUMA 策略创建。
使用 --numa-bind 启用此功能。默认情况下,vLLM 会自动检测 GPU 到 NUMA 的映射,并为每个工作进程使用 --cpunodebind=<node> --membind=<node>。当您需要自定义 CPU 策略时,请添加 --numa-bind-cpus,vLLM 将切换到 --physcpubind=<cpu-list> --membind=<node>。
这些 --numa-bind* 选项仅适用于 GPU 执行进程。它们不配置 CPU 后端的独立线程亲和性控制。自动 GPU 到 NUMA 检测目前仅针对基于 CUDA/NVML 的平台实现;其他 GPU 后端如果使用这些选项,则必须提供明确的绑定列表。
--numa-bind-nodes 为每个可见 GPU 采用一个非负 NUMA 节点索引,顺序与 GPU 索引相同。--numa-bind-cpus 为每个可见 GPU 采用一个 numactl CPU 列表,顺序与 GPU 索引相同。每个 CPU 列表必须使用 numactl --physcpubind 语法,例如 0-3、0,2,4-7 或 16-31,48-63。
# Auto-detect NUMA nodes for visible GPUs
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 4 \
--numa-bind
# Explicit NUMA-node mapping
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 4 \
--numa-bind \
--numa-bind-nodes 0 0 1 1
# Explicit CPU pinning, useful for PCT or other high-frequency core layouts
vllm serve meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 4 \
--numa-bind \
--numa-bind-nodes 0 0 1 1 \
--numa-bind-cpus 0-3 4-7 48-51 52-55
注意事项
- CLI 使用会自动强制多进程使用
spawn方法。如果您通过 Python API 启用 NUMA 绑定,请同时设置VLLM_WORKER_MULTIPROC_METHOD=spawn。 - 自动检测依赖于主机的 NVML 和 NUMA 支持。如果它无法可靠地确定映射,请显式传递
--numa-bind-nodes。 - 显式的
--numa-bind-nodes和--numa-bind-cpus值必须是有效的numactl输入。vLLM 会进行少量验证,但有效的绑定语义仍由numactl决定。 - 当前的实现绑定了 GPU 执行进程,例如
EngineCore和多进程工作进程。它不对前端 API 服务器进程或 DP 协调器应用 NUMA 绑定。 - 在容器化环境中,NUMA 策略系统调用可能需要额外的权限,例如通过
docker run运行时使用--cap-add SYS_NICE。
CPU 后端线程亲和性¶
CPU 后端使用与 --numa-bind 不同的机制。CPU 执行是通过特定于 CPU 的环境变量进行配置的,例如 VLLM_CPU_OMP_THREADS_BIND、VLLM_CPU_NUM_OF_RESERVED_CPU 和 CPU_VISIBLE_MEMORY_NODES,而不是面向 GPU 的 --numa-bind* CLI 选项。
默认情况下,VLLM_CPU_OMP_THREADS_BIND=auto 从每个 CPU 工作进程的可用 CPU 和 NUMA 拓扑中推导出 OpenMP 布局。要覆盖自动策略,请使用 CPU 后端记录的 CPU 列表格式显式设置 VLLM_CPU_OMP_THREADS_BIND,或使用 nobind 禁用此行为。
有关当前的 CPU 后端设置和调优指南,请参阅:
仅限 GPU 的 --numa-bind、--numa-bind-nodes 和 --numa-bind-cpus 选项不配置 CPU 工作进程亲和性。
多模态编码器的批处理级数据并行¶
默认情况下,TP 被用来像语言解码器一样对多模态编码器的权重进行分片,以减少每个 GPU 上的内存和计算负载。
然而,由于多模态编码器的大小与语言解码器相比非常小,TP 的收益相对较少。另一方面,由于每一层之后都要执行全规约 (all-reduce),TP 会产生巨大的通信开销。
鉴于此,改用 TP 对批处理输入数据进行分片(本质上是执行批处理级 DP)可能更有利。实践证明,对于 tensor_parallel_size=8,这可将吞吐量和 TTFT 提高约 10%。对于使用硬件非优化 Conv3D 操作的视觉编码器,批处理级 DP 与常规 TP 相比可额外提升 40% 的性能。
尽管如此,由于多模态编码器的权重在每个 TP 等级上被复制,内存消耗会有少量增加,如果您当前内存已经非常紧张,这可能会导致 OOM(内存溢出)。
您可以通过设置 mm_encoder_tp_mode="data" 来启用批处理级 DP,例如:
from vllm import LLM
llm = LLM(
model="Qwen/Qwen2.5-VL-72B-Instruct",
tensor_parallel_size=4,
# When mm_encoder_tp_mode="data",
# the vision encoder uses TP=4 (not DP=1) to shard the input data,
# so the TP size becomes the effective DP size.
# Note that this is independent of the DP size for language decoder which is used in expert parallel setting.
mm_encoder_tp_mode="data",
# The language decoder uses TP=4 to shard the weights regardless
# of the setting of mm_encoder_tp_mode
)
重要
批处理级 DP 不应与 API 请求级 DP 混淆(后者由 data_parallel_size 控制)。
批处理级 DP 需要在每个模型的基础上实现,并通过在模型类中设置 supports_encoder_tp_data = True 来启用。无论如何,您都需要在引擎参数中设置 mm_encoder_tp_mode="data" 才能使用此功能。
已知支持的模型(及相应的基准测试):
- dots_ocr (PR #25466)
- GLM-4.1V 或更高版本 (PR #23168)
- InternVL (PR #23909)
- Kimi-VL (PR #23817)
- Llama4 (PR #18368)
- MiniCPM-V-2.5 或更高版本 (PR #23327, PR #23948)
- Qwen2-VL 或更高版本 (PR #22742, PR #24955, PR #25445)
- Step3 (PR #22697)
输入处理¶
并行处理¶
您可以通过 API 服务器横向扩展并行运行输入处理。当输入处理(在 API 服务器内部运行)相对于模型执行(在引擎核心内部运行)成为瓶颈,且您有多余的 CPU 能力时,这非常有用。
# Run 4 API processes and 1 engine core process
vllm serve Qwen/Qwen2.5-VL-3B-Instruct --api-server-count 4
# Run 4 API processes and 2 engine core processes
vllm serve Qwen/Qwen2.5-VL-3B-Instruct --api-server-count 4 -dp 2
注意
API 服务器横向扩展仅适用于在线推理。
警告
默认情况下,每个 API 服务器使用 8 个 CPU 线程从请求数据中加载媒体项(例如图像)。
如果您应用 API 服务器横向扩展,请考虑调整 VLLM_MEDIA_LOADING_THREAD_COUNT 以避免 CPU 资源耗尽。
多模态缓存¶
多模态缓存避免了重复传输或处理相同的多模态数据,这在多轮对话中很常见。
处理器缓存¶
自动启用多模态处理器缓存,以避免在 BaseMultiModalProcessor 中重复处理相同的多模态输入。
IPC 缓存¶
当 API (P0) 和引擎核心 (P1) 进程之间存在一一对应关系时,自动启用多模态 IPC 缓存,以避免在它们之间重复传输相同的多模态输入。
键复制缓存 (Key-Replicated Cache)¶
默认情况下,IPC 缓存使用 键复制缓存,其中缓存键存在于 API (P0) 和引擎核心 (P1) 进程中,但实际缓存数据仅驻留在 P1 中。
共享内存缓存¶
当涉及多个工作进程时(例如 TP > 1),共享内存缓存效率更高。可以通过设置 mm_processor_cache_type="shm" 来启用它。在此模式下,缓存键存储在 P0 上,而缓存数据本身驻留在所有进程均可访问的共享内存中。
配置¶
您可以通过设置 mm_processor_cache_gb 的值(默认为 4 GiB)来调整缓存大小。
如果您无法从缓存中获得太多收益,可以通过 mm_processor_cache_gb=0 完全禁用 IPC 和处理器缓存。
示例
# Use a larger cache
llm = LLM(
model="Qwen/Qwen2.5-VL-3B-Instruct",
mm_processor_cache_gb=8,
)
# Use a shared-memory based IPC cache
llm = LLM(
model="Qwen/Qwen2.5-VL-3B-Instruct",
tensor_parallel_size=2,
mm_processor_cache_type="shm",
mm_processor_cache_gb=8,
)
# Disable the cache
llm = LLM(
model="Qwen/Qwen2.5-VL-3B-Instruct",
mm_processor_cache_gb=0,
)
缓存放置¶
根据配置,P0 和 P1 上多模态缓存的内容如下:
| mm_processor_cache_type | 缓存类型 | P0 缓存 | P1 引擎缓存 | P1 工作进程缓存 | 最大内存 |
|---|---|---|---|---|---|
| lru | 处理器缓存 | K + V | 不适用 | 不适用 | mm_processor_cache_gb * data_parallel_size |
| lru | 键复制缓存 | K | K + V | 不适用 | mm_processor_cache_gb * api_server_count |
| shm | 共享内存缓存 | K | 不适用 | V | mm_processor_cache_gb * api_server_count |
| 不适用 | 已禁用 | 不适用 | 不适用 | 不适用 | 0 |
K:存储多模态项的哈希值 V:存储多模态项的已处理张量数据
GPU 部署的 CPU 资源¶
vLLM V1 使用多进程架构(参见 V1 进程架构),其中每个进程都需要 CPU 资源。CPU 内核预配不足是导致性能下降的常见原因,特别是在虚拟化环境中。
最低 CPU 要求¶
对于具有 N 个 GPU 的部署,至少有:
- 1 个 API 服务器进程 -- 处理 HTTP 请求、分词和输入处理
- 1 个引擎核心进程 -- 运行调度程序并协调 GPU 工作进程
- N 个 GPU 工作进程 -- 每个 GPU 一个,执行模型前向传递
这意味着至少有 2 + N 个进程在争用 CPU 时间。
警告
使用比进程数更少的物理 CPU 内核会导致争用并显著降低吞吐量和延迟。引擎核心进程运行忙等待循环,对 CPU 匮乏特别敏感。
最低要求为 2 + N 个物理内核(1 个用于 API 服务器,1 个用于引擎核心,每个 GPU 工作进程 1 个)。在实践中,分配更多的内核可以提高性能,因为操作系统、PyTorch 后台线程和其他系统进程也需要 CPU 时间。
重要
请注意,我们这里指的是 物理 CPU 内核。如果您的系统启用了超线程,那么 1 个 vCPU = 1 个超线程 = 1/2 个物理 CPU 内核,因此您至少需要 2 x (2 + N) 个 vCPU。
数据并行和多 API 服务器部署¶
使用数据并行或多个 API 服务器时,CPU 要求会增加:
其中 A 是 API 服务器计数(默认为 DP),DP 是数据并行大小,N 是 GPU 总数。例如,在 8 个 GPU 上使用 DP=4, TP=2:
性能影响¶
CPU 预配不足特别会影响:
- 输入处理吞吐量 -- 分词、聊天模板渲染和多模态数据加载都在 CPU 上运行
- 调度延迟 -- 引擎核心调度程序在 CPU 上运行,直接影响向 GPU 工作进程分发新 Token 的速度
- 输出处理 -- 反分词、网络传输,特别是流式 Token 响应都使用 CPU 周期
如果您观察到 GPU 利用率低于预期,则 CPU 争用可能是瓶颈。增加可用 CPU 内核的数量甚至提高时钟速度,可以显著改善端到端性能。
Attention 后端选择¶
vLLM 支持为不同硬件和用例优化的多种 Attention 后端。后端会根据您的 GPU 架构、模型类型和配置自动选择,但您也可以手动指定一个以获得最佳性能。
有关可用后端、其功能支持以及如何配置它们的详细信息,请参阅 Attention 后端功能支持文档。