显存管理基础:GPU显存层次、CUDA显存分配机制、显存碎片定义与分类
在深入多卡vLLM的显存碎片治理之前,必须先建立一套统一的“显存世界观”。很多性能问题表面上是OOM(Out of Memory)或吞吐低下,本质上是对显存层次理解错位、对CUDA分配器行为误判、以及对碎片类型缺乏分类能力所致。本节将依次拆解GPU显存的物理与逻辑层次、CUDA运行时分配器的真实工作方式,以及显存碎片的精确定义与分类。
2.1 GPU显存层次:从物理带宽到逻辑池
现代GPU(以NVIDIA Hopper/Ampere架构为例)的显存体系可以划分为三个关键层次,每一层对vLLM的KVCache和权重驻留策略都有直接影响:
- 物理显存(HBM):高带宽显存,带宽可达2~3TB/s,容量为40GB/80GB(A100/H100)。这是所有张量数据的最终归宿,也是碎片产生的物理载体。
- L2缓存:片上缓存,容量约40~50MB,带宽远高于HBM。vLLM的算子若能在L2中命中(如重复读取同一Attention分块),可显著降低HBM压力,但L2无法被软件直接管理,只能通过访问模式优化。
- CUDA虚拟地址空间:每个进程拥有独立的虚拟显存地址空间,CUDA驱动负责将虚拟页映射到物理HBM页。碎片问题本质上是虚拟地址空间中的“空洞”无法被有效复用。
2.2 CUDA显存分配机制:cudaMalloc的“缓存池”陷阱
许多开发者误以为cudaMalloc每次调用都会直接向驱动申请物理显存,实际上CUDA Runtime维护了一个显存缓存池。当调用cudaFree释放显存时,内存块并不会立即归还给驱动,而是被放入缓存池中以供后续复用。这种设计减少了与驱动的交互开销,但也带来了两个关键问题:
- 缓存池碎片化:频繁分配/释放不同大小的张量(如不同batch size下的KVCache),缓存池中会出现大量不连续的空闲块,即使总空闲容量充足,也无法满足大块连续分配请求。
- 显存占用虚高:
cudaFree后显存占用(通过nvidia-smi查看)可能不会下降,因为缓存池仍持有该内存。这会导致多卡场景下其他进程无法使用该显存。
torch.cuda.memory_summary()观察显存,会发现“reserved”远大于“allocated”。这是PyTorch缓存分配器(基于CUDA缓存池)的正常行为,但若reserved持续增长且不回落,说明缓存池中存在大量碎片或未释放的block。
CUDA分配器的工作流程可以概括为:
cudaMalloc(ptr, size)
→ 检查缓存池中是否有 ≥ size 的空闲块
→ 有: 从池中取出并返回(可能产生内部碎片)
→ 无: 向驱动申请新物理页 → 加入缓存池 → 返回
cudaFree(ptr)
→ 将块标记为空闲 → 放入缓存池
→ 不立即归还驱动(除非超过阈值或显式调用 cudaDeviceReset)
2.3 显存碎片定义与分类:内部碎片与外部碎片
显存碎片是指显存空间中无法被有效利用的“空洞”或“浪费”。在vLLM多卡部署中,碎片直接影响KV Cache的可用容量和吞吐上限。碎片分为两大类:
| 碎片类型 | 定义 | 产生原因 | vLLM中的典型表现 |
|---|---|---|---|
| 内部碎片 | 分配块内部未被使用的空间 | 分配粒度(如256B对齐)、请求大小与分配块大小不匹配 | KV Cache block大小为16 token,但实际序列长度不足16的整数倍时,最后一个block部分浪费 |
| 外部碎片 | 空闲块之间被已分配块隔断,无法合并成大块 | 不同大小的张量交替分配/释放,缓存池无法合并相邻空闲块 | 多卡并行时,各卡显存分配不均,某卡出现“总空闲>需求但无法分配” |
--gpu-memory-utilization 0.9,即可用显存为72GB。若KV Cache block大小为16 token(约2KB),而实际请求的平均序列长度为1000 token(62.5个block),则每个序列最后一个block平均浪费约50%空间。若同时有1000个并发序列,内部碎片浪费约1000 × 1KB = 1MB,看似不大,但若block大小设置为128 token(约16KB),浪费将放大8倍。
2.4 案例:多卡场景下的碎片放大效应
考虑一个4卡A100(80GB)的vLLM部署,使用张量并行(TP=4)加载一个70B模型。模型权重约140GB,每卡需35GB。剩余每卡45GB用于KV Cache。假设采用默认的cudaMalloc行为:
- 每卡独立运行PyTorch缓存分配器,缓存池互不共享。
- 当请求到达时,每卡为KV Cache分配相同大小的block(因为TP需要保持各卡张量形状一致)。
- 若某卡的缓存池因之前处理不同长度请求而产生外部碎片,该卡可能无法分配新的KV block,导致整个请求失败(即使其他卡仍有空闲)。
这种“木桶效应”在多卡场景中被放大:任何一张卡的碎片问题都会导致整体请求失败。因此,vLLM引入了--kv-cache-dtype、--block-size等参数来精细控制分配粒度,并通过PagedAttention的页表机制减少外部碎片的影响。
torch.cuda.memory.set_per_process_memory_fraction限制每卡缓存池上限,强制释放;③ 在vLLM中启用--enable-prefix-caching复用KV Cache块,减少分配/释放频率。
2.5 误区与复盘:显存碎片治理的常见错误
torch.cuda.memory_snapshot()分析块分布,区分“已分配”与“保留未用”。
--gpu-memory-utilization。该参数只是设置可用显存上限,并不会减少碎片。若碎片严重,即使利用率设到0.95,仍可能OOM。
nvidia-smi的“FB Memory Usage”和PyTorch的memory_summary(),而非只看全局均值。
复盘建议:在部署vLLM前,先使用torch.cuda.memory._dump_snapshot()生成显存快照,分析block大小分布和空闲块位置。若发现大量小空闲块(<2MB),说明外部碎片严重,应考虑调整block大小或启用内存池合并策略。若发现分配块内部浪费超过20%,则应减小block size或启用动态block大小调整。