1. 显存基础与压测背景

1.1 GPU显存架构:从物理到逻辑

做服务压测这么多年,我见过太多人把GPU显存当成一个「大黑盒」——只管往里塞数据,塞爆了也不知道为什么。其实,搞清楚显存架构,是优化压测的第一步。

先说说显存的物理结构。现代GPU(比如NVIDIA的A100、H100)用的都是HBM(高带宽内存)或HBM2e。它不像CPU内存那样插在主板槽上,而是通过硅中介层(Interposer)和GPU核心堆叠在一起。说白了,就是「贴」在核心旁边,距离极近。

核心要点:显存由多个DRAM堆叠层(Stack)组成,每个Stack又分成多个Channel。GPU核心通过内存控制器(Memory Controller)访问这些Channel。A100有40个内存控制器,H100有80个。

我刚开始做压测时,总以为显存访问是「一条大马路随便跑」。后来发现完全不是——每个内存控制器只能管自己那一亩三分地。如果你把数据全塞到一个Channel里,其他Channel闲着,带宽就浪费了。这就是所谓的「内存通道失衡」。

从逻辑层面看,GPU显存分为几个区域:

  • 全局内存(Global Memory):最常用,所有线程都能访问。容量最大,但延迟也最高(几百个时钟周期)。
  • 共享内存(Shared Memory):每个线程块独享,延迟极低(几个时钟周期)。容量很小,通常几十KB。
  • 寄存器(Registers):每个线程私有,最快。但数量有限,一个SM(流多处理器)通常只有64K~256K个寄存器。
  • 常量内存(Constant Memory):只读,有缓存加速。适合存放模型权重等不变数据。
  • 纹理内存(Texture Memory):专门优化空间局部性访问,图像处理常用。

你想想看,压测时模型推理主要用的是全局内存和共享内存。模型参数、中间激活值、输入输出数据,全堆在全局内存里。共享内存则用来做算子内部的临时缓存。

个人经验:我曾在压测一个BERT-large模型时,发现显存占用比理论值高出30%。排查了半天,原来是PyTorch的默认分配策略会预留一部分显存作为缓存池。这个「隐藏开销」在压测场景下特别容易让人误判。

1.2 显存带宽与容量:两个不同的瓶颈

很多人把显存带宽和容量混为一谈。其实它们是两个完全不同的维度。

显存容量,说白了就是「能装多少东西」。模型参数、中间激活、优化器状态、输入输出数据,都得占地方。容量不够,模型就加载不进去,或者推理时OOM(显存溢出)。

显存带宽,则是「读写速度有多快」。它决定了GPU每秒能从显存里搬多少数据。带宽不够,GPU核心就会「饿肚子」——计算单元闲着等数据。

我举个例子你就明白了:

  • A100 80GB版本的显存带宽是2TB/s
  • H100的显存带宽是3.35TB/s
  • RTX 4090的显存带宽是1TB/s左右

为什么带宽这么重要?因为大模型推理时,每个token生成都需要把整个模型参数从显存搬到计算单元。模型越大,参数越多,带宽需求就越高。

计算公式:推理一个token所需的最小带宽 = 模型参数量 × 参数精度(字节) / 计算时间

比如一个70B的模型,用FP16(2字节),想在1秒内生成30个token,那带宽至少需要:70B × 2B × 30 / 1s = 4.2TB/s。H100的3.35TB/s都扛不住,得用多卡并行。

我在压测一个LLaMA-2 70B模型时就踩过这个坑。单卡A100 80GB,理论容量够(模型约140GB,但可以用量化到4bit降到35GB),可实际推理速度只有5 token/s。为什么?带宽不够!模型参数从显存搬到计算单元的速度,成了整个流程的瓶颈。

避坑指南:我曾经以为显存容量够就万事大吉,结果压测时发现QPS(每秒查询数)上不去。后来用nvidia-smi dmon一查,显存带宽利用率飙到95%以上,计算单元利用率只有30%。这就是典型的「带宽瓶颈」——GPU核心在等数据,而不是在算数据。

1.3 压测场景下的显存瓶颈分析

做服务压测,不能光看显存用了多少。你得学会「诊断」瓶颈到底在哪。我个人习惯用三个维度来分析:

维度一:容量瓶颈

最直观的——显存不够用了。表现就是OOM,或者频繁的显存交换(如果启用了统一内存)。

  • 症状:压测刚开始正常,跑了几百个请求后突然报错CUDA out of memory。
  • 原因:推理框架没有及时释放中间激活值,或者请求并发太高,每个请求都占用了独立的显存空间。
  • 排查工具:nvidia-smi看显存占用趋势,或者用PyTorch的torch.cuda.memory_summary()。

维度二:带宽瓶颈

这个更隐蔽。显存没用满,但性能就是上不去。

  • 症状:GPU计算单元利用率低(<50%),但显存带宽利用率高(>80%)。
  • 原因:模型太大,或者batch size太小,导致计算量不足以掩盖数据搬运的延迟。
  • 排查工具:nvidia-smi dmon看mclk(显存时钟)和pcie带宽,或者用Nsight Systems看内存事务。

维度三:内存碎片化

这个坑我踩过好几次。显存总量够,但分配不出连续的大块内存。

  • 症状:压测过程中偶尔报OOM,但重启后又正常了。
  • 原因:频繁的显存分配和释放导致碎片化,大块连续内存被拆成小块。
  • 解决方案:使用显存池(如CUDA的cudaMallocAsync),或者预分配好所有需要的显存。

下面这张图是我自己总结的显存瓶颈分析流程,你可以参考:

显存瓶颈分析流程 压测性能不达标 显存是否OOM? 是 容量瓶颈 检查模型大小/并发数 否 带宽利用率高? (>80%) 是 带宽瓶颈 增大batch/使用量化 否 检查计算瓶颈/框架开销

嗯,这里要注意:实际压测中,瓶颈往往是混合的。比如容量快满了,同时带宽也吃紧。这时候你得先解决容量问题——因为OOM直接让服务挂掉,带宽问题只是性能差一点。

我的习惯:压测前先用一个简单的脚本跑一下「显存压力测试」——逐步增加batch size,观察显存占用和推理延迟的变化曲线。如果延迟在某个batch size后突然飙升,那大概率是带宽瓶颈了。

最后说一个容易被忽略的点:显存带宽和PCIe带宽是两码事。显存带宽是GPU核心和显存之间的速度,PCIe带宽是GPU和CPU之间的速度。压测时如果数据频繁在CPU和GPU之间拷贝(比如每次请求都重新加载模型),那瓶颈可能在PCIe上,而不是显存本身。

我曾经压测一个实时语音识别服务,发现延迟波动很大。查了半天,原来是每次请求都要把音频数据从CPU拷贝到GPU,PCIe 3.0 x16的带宽只有16GB/s,成了瓶颈。后来改成异步传输+预分配显存池,延迟直接降了40%。

避坑指南:我曾经天真地以为「显存够大就能扛住高并发」。结果压测时发现,每个请求都独立分配显存,导致内存碎片化严重,最终OOM。后来改用vLLM这类支持显存管理的推理框架,才解决了问题。

好了,显存基础就聊到这儿。记住三个关键词:容量、带宽、碎片化。压测时盯着这三个指标,基本能定位90%的问题。