跳到内容

优化与调优

本指南涵盖 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_seqsmax_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_tokensmax_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.08671https://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-30,2,4-716-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_BINDVLLM_CPU_NUM_OF_RESERVED_CPUCPU_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" 才能使用此功能。

已知支持的模型(及相应的基准测试):

输入处理

并行处理

您可以通过 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 资源耗尽。

注意

API 服务器横向扩展会禁用 多模态 IPC 缓存,因为它要求 API 进程和引擎核心进程之间存在一一对应关系。

这不会影响 多模态处理器缓存

多模态缓存

多模态缓存避免了重复传输或处理相同的多模态数据,这在多轮对话中很常见。

处理器缓存

自动启用多模态处理器缓存,以避免在 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,
)

缓存放置

根据配置,P0P1 上多模态缓存的内容如下:

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 要求会增加:

Minimum physical cores = A + DP + N + (1 if DP > 1 else 0)

其中 A 是 API 服务器计数(默认为 DP),DP 是数据并行大小,N 是 GPU 总数。例如,在 8 个 GPU 上使用 DP=4, TP=2

4 API servers + 4 engine cores + 8 GPU workers + 1 DP coordinator = 17 processes

性能影响

CPU 预配不足特别会影响:

  • 输入处理吞吐量 -- 分词、聊天模板渲染和多模态数据加载都在 CPU 上运行
  • 调度延迟 -- 引擎核心调度程序在 CPU 上运行,直接影响向 GPU 工作进程分发新 Token 的速度
  • 输出处理 -- 反分词、网络传输,特别是流式 Token 响应都使用 CPU 周期

如果您观察到 GPU 利用率低于预期,则 CPU 争用可能是瓶颈。增加可用 CPU 内核的数量甚至提高时钟速度,可以显著改善端到端性能。

Attention 后端选择

vLLM 支持为不同硬件和用例优化的多种 Attention 后端。后端会根据您的 GPU 架构、模型类型和配置自动选择,但您也可以手动指定一个以获得最佳性能。

有关可用后端、其功能支持以及如何配置它们的详细信息,请参阅 Attention 后端功能支持文档。