
2026年6月,我们在腾讯云GPU GN7机型(T4 16GB) 上部署基于vLLM 0.6.3 + Qwen2.5-72B-AWQ 的Agent推理网关。服务启动12小时后,监控显示:
$ nvidia-smi
+-----------------------------------------------------------------------------+
| GPU-Util Memory-Usage |
| 100% 15890MiB / 16384MiB | # 显存几乎耗尽
+-----------------------------------------------------------------------------+此时新请求全部阻塞,vLLM日志疯狂输出:
WARNING 12:34:56 scheduler.py:xxx] Block manager failed to allocate memory.
CUDA out of memory. Tried to allocate 256.00 MiB. 但诡异的是,我们通过torch查看实际激活张量占用:
import torch
print(torch.cuda.memory_allocated() / 1024**2) # 输出: 5120.5 MB
print(torch.cuda.memory_reserved() / 1024**2) # 输出: 15360.1 MB结论:allocated仅5GB,但reserved高达15GB,中间凭空消失了近10GB显存。这不是OOM,是显存碎片化 + 分配器死锁。
cudaMalloc),切成不同大小的桶(2MB、4MB、8MB...20MB等),复用给后续Tensor。但一旦某个桶被占用,即使内部有碎片,整个桶也不会归还给CUDA。--block-size=16,即16个token)。在高并发场景下,vLLM会频繁向PyTorch申请/释放不规则的中间激活张量(如logits、hidden_states)。冲突点:vLLM释放了中间张量,PyTorch将其回收到对应的桶中等待复用。但由于max_num_seqs和max_model_len动态变化,请求张量大小跨度极大(从1KB到2GB),导致桶内外部碎片急剧增加。最终reserved达到16GB上限,但可用的连续显存块却不足256MB。
torch.cuda.memory_snapshot()我们编写以下诊断脚本,dump出显存块分布:
import pickle
import torch
# 在OOM发生时执行
snapshot = torch.cuda.memory_snapshot()
with open("oom_snapshot.pickle", "wb") as f:
pickle.dump(snapshot, f)使用PyTorch官方memory_viz.py工具可视化后,发现Free segments数量超过2000个,最大连续空闲块仅128MB,碎片率高达92%。
网上90%的帖子会教你在OOM时执行:
torch.cuda.empty_cache()实测在vLLM场景下,empty_cache()只会释放完全空闲的大块,对碎片毫无作用。执行后reserved依然坚挺在15GB。
expandable_segments从PyTorch 2.2开始,官方引入了expandable_segments特性(参考PyTorch Issue #104193)。它改变了底层cudaMalloc的策略,允许CachingAllocator在申请新大块时,尝试扩展(expand)已有块,而不是重新向驱动申请。这能显著减少碎片。
在vLLM启动脚本中添加环境变量:
# 启动vLLM服务时强制注入
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
# 同时建议开启后端异步分配,减少阻塞
export PYTORCH_CUDA_ALLOC_CONF=backend:cudaMallocAsync,expandable_segments:True
python -m vllm.entrypoints.openai.api_server \
--model /data/models/Qwen2.5-72B-AWQ \
--served-model-name Qwen2.5 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.85 \
--max-num-seqs 32 \
--max-model-len 4096 \
--block-size 16关键改动说明:
gpu-memory-utilization从默认0.9降至0.85,为碎片预留缓冲。max-num-seqs根据压测调整为32,防止过度抢占导致Context切换爆炸。我们使用wrk模拟30并发持续请求Agent(附带4K上下文),对比优化前后的显存稳定性:
指标 | 优化前(12h后) | 优化后(连续运行72h) |
|---|---|---|
显存碎片率 (1 - max_free_block/reserved) | 92.3% | 8.7% |
有效吞吐量 (tok/s) | 从1200降至350(抖动剧烈) | 稳定在1150 ± 50 |
请求P99延迟 | 无法获取(大量超时) | 320ms |
OOM崩溃次数 | 每8小时1次 | 0次 |
py-spy录制)优化前Scheduler loop中大量时间消耗在cudaFree和cudaMalloc的系统调用(占比45%);优化后该占比降至3%,CPU时间全在模型Forward计算上。
需要注意:expandable_segments并非银弹。
NCCL_P2P_DISABLE=1规避通信死锁(篇幅所限,后续另文详解)。生产环境AI Agent的稳定性,往往卡在这些CUDA生态底层细节上。希望这篇纯粹的“踩坑+调优”实录能帮到正在被显存问题折磨的同仁。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。