SGLang VLM 多模态推理 GPU 显存泄漏——每次图片请求泄漏 20MB,直至 CUDA OOM

1 阅读1分钟

sleep 30

第二步:看显存是否回落

nvidia-smi --query-gpu=memory.used --format=csv -l 1

如果持续保持高位不回落 → 泄漏

如果回落到接近 baseline → 只是高负载缓存


泄漏的典型特征:空闲 30 秒后,显存**一动不动**。

### 2.2 定位泄漏来源

SGLang 的 `--enable-metrics` 会暴露 Prometheus 指标:

```bash
curl http://localhost:7860/metrics | grep -E "kv_cache|allocator|memory"

关注 sglang:kv_cache_usage_perc——如果这个指标没有增长但 nvidia-smi 的显存在涨,说明泄漏不在 KV cache 里,而是在图像处理管线

2.3 确认是否命中 fast image processor bug

SGLang v0.4.9.post6 及之前版本,fast image processor(默认启用)有已知的严重泄漏。检查方式:

# 看启动日志
grep "fast.*image.processor" server.log

# 如果有这行 → 命中了
# "Using fast image processor for Qwen2.5-VL"

三、根因分析

3.1 显存泄漏的三层机制

图片请求
  │
  ├─ 1. 图像预处理 (fast image processor)
  │     分配 GPU 临时 buffer 做 resize/normalize
  │     ❌ buffer 释放不完全,残留在 PyTorch caching allocator
  │
  ├─ 2. Vision Encoder 推理
  │    图像 token → KV cache 条目
  │    ❌ 请求结束后,vision token 的 KV cache 未标记为可回收
  │
  └─ 3. LLM 推理 (language model component)
        处理 visual tokens + text tokens
        ❌ 某些 attention backend (如 fa3/aiter) 分配的分页 KV pool
           在 warmup 后泄漏(每页 64 tokens ≈ 128KB)

核心矛盾:VL(Vision-Language)模型有两个 KV cache——文本部分按 radix tree 管理(可回收),但视觉 token 的 cache 条目在请求结束后没有正确释放。这些视觉 token 的 cache page 被标记为「在使用中」,但实际上对应的请求已经返回了。

3.2 为什么 fast image processor 加剧了泄漏

普通 processor:CPU 处理 → 结果传 GPU → CPU buffer 自动回收 ✅
fast processor:GPU 上直接处理 → 使用 cuDNN/cuBLAS 临时 buffer
                → PyTorch caching allocator 持有不归还 ❌

PyTorch 的 caching allocator 设计哲学是「不归还 GPU 内存给驱动」——它认为你还会再次分配。但 fast image processor 每次分配的 buffer 大小不完全相同,导致 allocator 不断分配新 segment,旧的又不释放。

3.3 为什么 LLM-only 模式也受影响

SGLang 官方在 #9365 tracking issue 中确认:纯文本请求同样有泄漏,只是幅度更小(每请求几 KB)。根因指向 token_to_kv_pool_allocator——每次 warmup 请求泄漏恰好 1 个 page(64 tokens)。

这说明泄漏的根源在 KV cache 管理器的 page 分配/回收逻辑,图像请求只是放大了这个 bug(因为视觉 token 多,分配的 page 多,泄漏的 page 也多)。

四、三种缓解方案

方案 A:禁用 fast image processor(推荐,影响最小)

python -m sglang.launch_server \
  --model-path Qwen/Qwen3-VL-8B-Instruct-FP8 \
  --disable-fast-image-processor               # ← 关键参数

效果:将图像处理从 GPU 移到 CPU,杜绝 PyTorch allocator 的碎片化泄漏。

代价:图像预处理增加 ~50ms 延迟(对端到端推理影响约 5-10%)。

方案 B:降低 mem_fraction_static + 定时重启

# 留出更多显存余量,让泄漏慢一点触发 OOM
python -m sglang.launch_server \
  --model-path Qwen/Qwen3-VL-8B-Instruct-FP8 \
  --mem-fraction-static 0.5                    # 从 0.7 降到 0.5

# 配合 cron 定时重启
# 每天凌晨 3 点 kill -15 并重启服务

效果:把 OOM 从「20 分钟必挂」推迟到「可以撑几小时」。

代价:服务中断的定时重启窗口。

方案 C:升级到修复版本

SGLang 团队正在 v0.5.x 中修复此问题。关注以下 PR:

PR修复内容
#9365 (tracking)VLM/LLM OOM 综合追踪
#21532token_to_kv_pool_allocator 泄漏修复
#18010--language-only 模式不应为 vision encoder 预留内存
# 尝试最新 dev 版本
pip install --upgrade "sglang[all] @ git+https://github.com/sgl-project/sglang.git@main"

代价:dev 版本可能引入新 bug。

五、vLLM 有没有同样的问题?

有,但表现不同。vLLM 的 VLM 推理也有类似的内存增长,但更多集中在「多图并行」场景:

场景:并发 10 个图片请求
vLLM:一次性分配大块 KV cache,请求结束后**部分回收**
SGLang:每次请求分配小 page,请求结束后**不回收**

两者根因相似——视觉 token 的 KV cache 生命周期管理不完善。但目前 SGLang 的泄漏更严重、更可复现。

六、生产环境 checklist

# 1. 监控脚本:检测显存泄漏趋势
#!/bin/bash
while true; do
    mem=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits)
    echo "$(date -Iseconds) $mem" >> /var/log/gpu_memory.log
    sleep 60
done

# 2. 告警阈值:显存超过 85% 且仍在增长 → 重启服务
python3 -c "
import subprocess, sys
mem = int(subprocess.check_output(
    'nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits', 
    shell=True))
if mem > 13_600:  # 16GB * 0.85
    print('ALERT: GPU memory >85%, triggering restart')
    sys.exit(1)
"

# 3. 健康检查端点
curl -X POST http://localhost:7860/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"default","messages":[{"role":"user","content":"ping"}],"max_tokens":1}'

七、跟已发文章的关联

本专栏之前写过:

  • THP 透明大页延迟飙升——内存管理 bug 的另一面
  • MemAvailable 29GB 却报内存压力——内核谎报内存的排障
  • kswapd0 100% CPU——内存回收机制的滥用

这篇是GPU 维度的内存泄漏,补齐了「内存问题」在整个技术栈的覆盖:内核 THP → 系统 kswapd → 应用层 GPU allocator。

总结

层级理解
现象SGLang 服务 VLM 时显存只涨不降,最终 OOM
根因fast image processor 的 PyTorch allocator 碎片化 + vision token KV cache page 未正确回收
缓解--disable-fast-image-processor(推荐) 或 降低 mem_fraction_static + 定时重启
根治等待 SGLang v0.5.x 的 KV cache page 回收重构

记忆锚点:VLM 显存泄漏 = fast image processor 的 PyTorch allocator 不归还 + vision token 的 KV cache page 不回收。先关 fast processor,再等上游修。