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 综合追踪 |
| #21532 | token_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,再等上游修。