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直接让服务挂掉,带宽问题只是性能差一点。
我的习惯:压测前先用一个简单的脚本跑一下「显存压力测试」——逐步增加batch size,观察显存占用和推理延迟的变化曲线。如果延迟在某个batch size后突然飙升,那大概率是带宽瓶颈了。
最后说一个容易被忽略的点:显存带宽和PCIe带宽是两码事。显存带宽是GPU核心和显存之间的速度,PCIe带宽是GPU和CPU之间的速度。压测时如果数据频繁在CPU和GPU之间拷贝(比如每次请求都重新加载模型),那瓶颈可能在PCIe上,而不是显存本身。
我曾经压测一个实时语音识别服务,发现延迟波动很大。查了半天,原来是每次请求都要把音频数据从CPU拷贝到GPU,PCIe 3.0 x16的带宽只有16GB/s,成了瓶颈。后来改成异步传输+预分配显存池,延迟直接降了40%。
避坑指南:我曾经天真地以为「显存够大就能扛住高并发」。结果压测时发现,每个请求都独立分配显存,导致内存碎片化严重,最终OOM。后来改用vLLM这类支持显存管理的推理框架,才解决了问题。
好了,显存基础就聊到这儿。记住三个关键词:容量、带宽、碎片化。压测时盯着这三个指标,基本能定位90%的问题。