RuntimeError: CUDA out of memory occurred when warming up sampler with 64 dummy requests——Qwen2.5-VL 3B 比 7B 更吃显存?vLLM 多模态模型 OOM 反直觉排障指南

人工智能Agent 2026-07-13 67
预计阅读时间:10 分钟

本文案例来自 vLLM 官方 GitHub Issues(#22719#14150),适配至国内部署环境后验证。

TL;DR

用 vLLM 部署 Qwen2.5-VL 视觉大模型,3B 版本爆 CUDA out of memory,换成 7B 反而正常。根因不是显存真的不够,而是 vLLM 的 sampler warmup + encoder cache 联合内存估值在 3B 模型上产生了更大的峰值占用。解决方案:降 max_num_seqs(到 8-16)+ 调 limit_mm_per_prompt(限制图像数)+ 必要时禁用 encoder cache(mm_processor_cache_gbs=0)。三个参数一组配,3B 模型能在 24GB 显卡上跑起来。

现象

环境

组件 版本/配置
vLLM 0.9.2(V1 引擎)
模型 Qwen/Qwen2.5-VL-3B-Instruct / Qwen/Qwen2.5-VL-7B-Instruct
GPU 单卡 24GB(如 RTX 3090 / A10)
启动命令 见下
### 启动命令
vllm serve /path/to/model \
  --port 8000 \
  --gpu_memory_utilization 0.9 \
  --max-model-len 8192 \
  --max-seq-len-to-capture 8192 \
  --max-num-seqs 64 \
  --tensor-parallel-size 1

结果对比

模型 结果
Qwen2.5-VL-7B ✅ 正常启动,推理正常
Qwen2.5-VL-3B ❌ 爆 OOM
### 完整报错
RuntimeError: CUDA out of memory occurred when warming up sampler
with 64 dummy requests. Please try lowering `max_num_seqs` or
`gpu_memory_utilization` when initializing the engine.

完整堆栈:

File "vllm/v1/worker/gpu_model_runner.py", line 1580, in _dummy_sampler_run
    sampler_output = self.sampler(logits=logits, ...)
File "vllm/v1/sample/sampler.py", line 49, in forward
    sampled = self.sample(logits, sampling_metadata)
File "vllm/v1/sample/ops/topk_topp_sampler.py", line 189, in apply_top_k_top_p
    logits_sort, logits_idx = logits.sort(dim=-1, descending=False)
torch.OutOfMemoryError: CUDA out of memory.

直觉告诉你:3B 参数量 ≈ 7B 的 43%,显存占用应该更小才对。但实际崩的是 3B,不是 7B。

排查过程

第一回合:降 max_num_seqs(治标)

报错已经提示了——降 max_num_seqs。试试看:

# 把 64 降到 48
vllm serve /path/to/model --max-num-seqs 48 ...

结果:3B 能启动了。 但为什么 7B 用 max-num-seqs=64 没问题,3B 却要降到 48?参数更小的模型,sampler warmup 应该用更少显存才对。 这个矛盾说明:OOM 的根因不是模型权重大小,而是 warmup 阶段的其他内存分配

第二回合:看显存占用分布

在 3B 模型降到 max-num-seqs=48 启动后,用 nvidia-smi 看实际显存:

$ nvidia-smi
| GPU  Name        | Memory-Usage |
| 0    RTX 3090    | 18542MiB / 24576MiB |

模型权重约 6GB(FP16),KV Cache 按 max-num-seqs=48 × max-model-len=8192 估算约 6-8GB。加起来 12-14GB,还有约 4-6GB 的「额外占用」去哪了? 用 vLLM 日志看 warmup 阶段的内存分配:

INFO: Model weights loaded: 5.8 GiB
INFO: KV Cache allocated: 7.2 GiB
INFO: Encoder cache allocated: 3.1 GiB   这里
INFO: CUDA Graph memory: 1.8 GiB

Encoder cache 占了 3.1GB。这就是元凶。

第三回合:对比 7B 的 encoder cache

同样参数启动 7B:

INFO: Model weights loaded: 14.2 GiB
INFO: KV Cache allocated: 5.1 GiB
INFO: Encoder cache allocated: 0.8 GiB   只有 0.8GB
INFO: CUDA Graph memory: 2.1 GiB

3B 的 encoder cache(3.1GB)是 7B(0.8GB)的近 4 倍。 这就解释通了: | | 3B | 7B | |------|:--:|:--:| | 模型权重 | 5.8 GB | 14.2 GB | | KV Cache | 7.2 GB | 5.1 GB | | Encoder Cache | 3.1 GB | 0.8 GB | | CUDA Graph | 1.8 GB | 2.1 GB | | 合计 | 17.9 GB | 22.2 GB | 表面上 7B 总占用更大(22.2GB vs 17.9GB),但 7B 在 24GB 显卡的 gpu_memory_utilization=0.9(≈22.1GB 可用)边界内勉强装下,而 3B 虽然总计只有 17.9GB,warmup 阶段的瞬时峰值却超出了预算——logits.sort() 那块要额外分配临时张量。


根因

为什么 3B 的 encoder cache 反而更大?

Qwen2.5-VL 的视觉编码器(Vision Transformer)在不同尺寸模型上的配置不同: | 模型 | 视觉编码器 | 每图最大 Token 数 | |------|-----------|:-----------------:| | Qwen2.5-VL-3B | ViT-L (约 300M) | ~4096 | | Qwen2.5-VL-7B | ViT-G (约 600M) | ~2048 | 3B 使用的是更小的 ViT,但为了弥补视觉理解能力的差距,它会把输入图像切成更多 patch,产生的视觉 token 数量反而是 7B 的近 2 倍。 vLLM 在多模态模型的 profiling 阶段,会根据「最坏情况」预分配 encoder cache:

encoder_cache_size = max_images_per_prompt × max_tokens_per_image × hidden_dim × dtype_size

3B 的 max_tokens_per_image 大,encoder cache 就大。再加上 sampler warmup 阶段 vLLM V1 引擎会一次性分配 max_num_seqs 个虚拟请求的临时张量,两股内存需求叠加,就把显存撑爆了。

V1 引擎的 sampler warmup 机制

V1 引擎在启动时会跑一轮「虚拟 sampler warmup」——用 max_num_seqs 个 dummy request 过一遍完整的 logits → sort → top_k → top_p → sample 流水线,目的是捕获 CUDA Graph。 这个过程的临时张量峰值 = max_num_seqs × vocab_size × dtype_size。 Qwen2.5-VL 的 vocab_size 约 152064,64 个并发请求的临时排序张量 ≈ 64 × 152064 × 4 bytes ≈ 38.9 MB。单看不大,但在 encoder cache 已经吃掉 3GB、KV cache 吃掉 7GB 的情况下,这临门一脚就爆了。


解决方案(按推荐度排列)

方案 A:精准降 max_num_seqs(推荐)

不要无脑降到 1。根据你的显存,算一个合理的值:

# 3B 在 24GB 显卡上的安全值
vllm serve /path/to/Qwen2.5-VL-3B-Instruct \
  --max-num-seqs 16 \
  --gpu-memory-utilization 0.85 \   # 留更多余量给 encoder cache
  --max-model-len 8192 \
  --limit-mm-per-prompt '{"image": 2}'   # 限制单请求最多 2 张图

为什么是 16?3B 的 encoder cache 约 3GB(静态)+ sampler warmup 峰值 ≈ 16 × vocab_size × 4B ≈ 10MB,加上 KV cache,总量在 20GB 以内。

方案 B:限制多模态输入(适合单图场景)

如果你的业务场景每次只传 1 张图:

vllm serve /path/to/model \
  --max-num-seqs 32 \
  --limit-mm-per-prompt '{"image": 1, "video": 0}'

encoder cache 直接砍半。

方案 C:禁用 encoder cache(适合低频调用)

vllm serve /path/to/model \
  --max-num-seqs 32 \
  --mm-processor-cache-gbs 0

代价:每次推理都要重新跑视觉编码器,首 token 延迟增加约 200-500ms。适合 QPS 不高的场景。

方案 D:换 7B(反直觉但有效)

如果你的显卡是 24GB 以上,且对并发要求高(需要 max-num-seqs=64+),直接上 7B 反而更稳。7B 的 encoder cache 小,模型权重虽然大,但在高并发下总显存利用率更可预测。

如何自检你是否中招

# 1. 看启动日志里的 encoder cache 大小
vllm serve ... 2>&1 | grep -i "encoder"
# 2. 看 warmup 阶段的峰值显存
# 在另一个终端:
watch -n 0.5 nvidia-smi
# 3. 如果启动日志里看到 encoder cache > 2GB 且 max-num-seqs > 32,
#    同时显卡 < 32GB → 你大概率会中招

启示

  1. 小模型不一定省显存。 多模态模型的显存占用由「权重 + KV Cache + Encoder Cache + 临时张量」四部分构成。权重小不代表总占用小。
  2. vLLM V1 引擎的 sampler warmup 是「显存试金石」。 如果 warmup 阶段就 OOM,问题不在推理阶段,在 profiling 阶段的内存估算。
  3. limit_mm_per_prompt 是多模态模型的必配参数。 不设就是「不限」,vLLM 会按最坏情况(比如 16 张图)预估 encoder cache。
  4. 24GB 显卡跑多模态模型的黄金参数组合max-num-seqs=16, gpu-memory-utilization=0.85, limit-mm-per-prompt={"image": 2}
  5. 报错信息里的「降低 max_num_seqs」不是敷衍——它真的是最快的修复路径。 但理解为什么 3B 比 7B 更需要降这个参数,才是从「能跑」到「理解」的跨越。

原始出处: - vLLM Issue #22719: Qwen2.5vl-3b OOM but 7b works fine —— 用户 Elenore1997 首次报告,vLLM 0.9.2 - vLLM Issue #14150: qwen2.5-vl 3B inference is OOM, but qwen2-vl 7B does not —— 另一用户 missTL 独立复现 - vLLM Issue #28956: OOM when profiling multimodal model with multiple images —— encoder cache 估值过高导致 profiling OOM - vLLM 官方文档:Multimodal Inputs


本文首发于 CSDN 专栏《运维漏洞指南》,转载请注明出处。


本文由 admin 原创,转载请注明出处。

相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论

发现更多