3、显存泄漏概念:定义、常见类型、与内存泄漏的区别
在vLLM这类大模型推理框架中,显存泄漏(VRAM Leak)是最隐蔽、最难以排查的故障之一。它的表象往往是“服务跑着跑着就OOM了”,但真正的原因并非流量突增,而是显存被某段逻辑持续占用且永不释放。要理解这个问题,我们首先需要给出一个精确的定义。
3.1 显存泄漏的定义与本质
显存泄漏是指:程序在运行过程中,通过CUDA、ROCm等接口向GPU显存申请了存储空间,但在使用完毕后,由于逻辑错误、引用未解除或异常分支未处理,导致这些显存块无法被回收或复用,从而造成可用显存持续下降的现象。与CPU内存泄漏类似,显存泄漏的本质是“分配与释放的不对称”——每一次分配都对应一个确切的释放时机,但程序在某个路径上丢失了这个时机。
在vLLM中,显存泄漏的严重性被进一步放大:因为vLLM使用PagedAttention管理KV Cache,显存被划分为固定大小的块(block),一旦某个block被标记为“已占用”但实际不再被任何序列引用,它就会成为“僵尸块”。更危险的是,vLLM的显存池(memory pool)通常预分配了绝大部分显存,泄漏并不表现为“分配失败”,而是表现为可用KV Cache块数量逐渐减少,最终导致新请求无法获得足够的KV块而触发OOM或请求排队超时。
关键判断标准:如果重启服务后显存恢复、运行一段时间后显存再次被占满,且占用增长与请求量不成线性关系,那么几乎可以断定存在显存泄漏。
3.2 显存泄漏的常见类型
在vLLM及类似的大模型推理系统中,显存泄漏并非单一形态,而是分布在多个层面。根据我们的工程实践,可以归纳为以下四种典型类型:
| 泄漏类型 | 发生位置 | 典型特征 | 检测难度 |
|---|---|---|---|
| KV Cache 块泄漏 | PagedAttention 管理器 | block_table 中残留已终止序列的块引用;可用块数持续下降 | 中 |
| CUDA 张量泄漏 | 模型前向/后向传播 | 中间激活张量未释放;显存碎片化加剧 | 高 |
| Python 对象引用泄漏 | 请求处理循环 | 请求对象被全局缓存或闭包引用,导致其持有的GPU张量无法释放 | 极高 |
| CUDA Context 泄漏 | 多线程/多进程 | 每次创建新的CUDA context 未销毁;显存占用随线程数线性增长 | 低 |
3.3 与内存泄漏的区别
虽然显存泄漏与CPU内存泄漏在“分配-释放不匹配”这一本质上相同,但在vLLM场景下,两者存在几个关键差异,这些差异直接决定了排查手段的不同:
- 分配方式不同:CPU内存通过
malloc/free或GC管理;显存通过cudaMalloc/cudaFree或缓存池(如vLLM的block allocator)管理。vLLM的显存池预分配机制使得泄漏初期不报错,而是表现为可用块减少。 - 回收机制不同:Python的CPU内存有引用计数和垃圾回收(GC)兜底,即使开发者忘记释放,只要引用解除,内存最终会被回收;而显存没有自动GC,一旦
cudaMalloc分配的内存丢失指针,就永久泄漏,直到进程结束。 - 可观测性不同:CPU内存泄漏可以通过
tracemalloc、objgraph等工具追踪对象引用;显存泄漏则需要借助nvidia-smi、torch.cuda.memory_summary()或CUDA原生工具,且显存碎片化会干扰判断。 - 影响范围不同:CPU内存泄漏通常只影响当前进程;显存泄漏在共享GPU的多人多卡环境中,会波及其他租户,甚至导致整卡OOM,影响同卡上的其他任务。
一个容易混淆的案例:某团队报告vLLM服务“显存泄漏”,但排查后发现是CPU内存泄漏导致Python进程RSS持续增长,进而触发了CUDA OOM。因为CPU内存不足时,PyTorch的缓存分配器会尝试扩展显存缓存,反而加剧了显存压力。这说明在排查时,必须同时监控CPU和GPU内存,不能只看
nvidia-smi。
3.4 典型泄漏场景复盘
我们以一个真实的vLLM生产事故为例,复盘显存泄漏的完整链路:
- 现象:服务运行约6小时后,新请求的TTFT(首token延迟)从200ms飙升至5s,随后报
CUDA out of memory。 - 初步排查:
nvidia-smi显示显存占用率99%,但vllm.engine.metrics中的num_free_kv_blocks从初始的 12000 降至 0。 - 根因定位:通过
torch.cuda.memory_snapshot()发现大量allocator标记为active但无对应tensor引用的内存段。进一步追踪发现,请求处理函数中有一个try...except分支:当生成结果包含特殊token时,提前return,跳过了seq_group.free()调用,导致该序列的KV块未被释放。 - 修复:在
finally块中确保seq_group.free()必然执行,并增加block_table的完整性校验。
误区提醒:不要认为“显存泄漏一定是框架bug”。在vLLM中,自定义模型(如新增一个attention变体)或自定义采样逻辑是最常见的泄漏来源。框架本身对标准模型做了充分测试,但你的扩展代码可能破坏了内存管理的不变量。排查时,优先审查自己新增的代码路径。