跳到内容

优化与调优

本指南涵盖 vLLM V1 的优化策略和性能调优。

提示

内存不足?请参阅本指南了解如何节省内存。

抢占

由于 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 架构中重新计算的开销较低。

分块预填充

分块预填充允许 vLLM 将大型预填充分块处理,并将其与解码请求一起批处理。此功能通过更好地平衡计算密集型(预填充)和内存密集型(解码)操作来提高吞吐量和延迟。

在 V1 中,**只要可行,分块预填充默认启用**。启用分块预填充后,调度策略会优先处理解码请求。它会对所有待处理的解码请求进行批处理,然后再调度任何预填充操作。当 max_num_batched_tokens 预算中有可用 token 时,它会调度待处理的预填充。如果待处理的预填充请求无法放入 max_num_batched_tokens 中,它会自动对其进行分块。

此策略有两个好处:

  • 它改善了 ITL 和生成解码,因为解码请求得到了优先处理。
  • 它通过将计算密集型(预填充)和内存密集型(解码)请求放置在同一批次中,帮助实现更好的 GPU 利用率。

使用分块预填充进行性能调优

您可以通过调整 max_num_batched_tokens 来调优性能。

  • 较小的值(例如 2048)可以实现更好的 token 间延迟 (ITL),因为预填充项较少,不会减慢解码速度。
  • 较大的值可以实现更好的首次 token 时间 (TTFT),因为您可以在批次中处理更多预填充 token。
  • 为了获得最佳吞吐量,我们建议将 max_num_batched_tokens > 8192,特别是对于在大型 GPU 上运行的小型模型。
  • 如果 max_num_batched_tokensmax_model_len 相同,这几乎等同于 V0 的默认调度策略(除了它仍然优先处理解码)。
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 层将根据张量并行大小和数据并行大小的乘积进行分片。

用于多模态编码器的批次级 DP

默认情况下,TP 用于对多模态编码器的权重进行分片,就像对语言解码器一样,以减少每个 GPU 的内存和计算负载。

但是,由于多模态编码器的大小与语言解码器相比非常小,因此 TP 的收益相对较小。另一方面,TP 由于在每层之后都执行 all-reduce 操作,会产生显著的通信开销。

鉴于此,通过 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 缓存会自动启用,以避免它们之间重复传输相同的多模态输入。

键复制缓存

默认情况下,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:存储多模态项目的处理后张量数据