1. 显存基础与压测概述
大家好,我是你们这堂课的主讲。做了这么多年GPU性能优化,我最大的感触就是——显存这东西,看着简单,用起来全是坑。今天咱们就从最基础的开始聊,把显存的底裤扒干净。
1.1 GPU显存架构:HBM vs GDDR
先说说显存类型。目前主流就两种:HBM(高带宽显存)和GDDR(图形双倍数据率显存)。
HBM,说白了就是堆叠起来的。像盖楼一样,把多个DRAM die堆在一起,中间用TSV(硅通孔)连接。这样做的好处是——带宽极高,功耗还低。我最早接触HBM是在NVIDIA的P100上,当时看到那带宽数据,说实话,惊到了。
GDDR呢,就是传统的平铺设计。芯片和显存颗粒分布在PCB上,走线长,延迟相对高一些。但胜在容量大、成本低。RTX 3090用的就是GDDR6X,24GB,打游戏爽,做训练也够用。
来,我画个图帮你们理解:
核心区别一句话总结:HBM是带宽怪兽,适合计算密集型;GDDR是容量担当,适合数据密集型。
1.2 显存带宽与延迟
带宽和延迟,这两个参数决定了你的模型能跑多快。
带宽,就是单位时间内能读写多少数据。公式很简单:
带宽 = 显存频率 × 位宽 × 2(DDR双倍速率)
举个例子:A100的HBM2e,频率1215 MHz,位宽5120 bit,算下来:
1215 MHz × 5120 bit × 2 = 12.44 Gbps × 5120 bit = 约 1.6 TB/s
嗯,这个数字很漂亮。但实际用的时候,你几乎跑不到这个理论值。为什么?因为还有延迟。
延迟,就是从发出请求到拿到数据的时间。HBM的延迟大概在100-200纳秒,GDDR稍微好一点,80-150纳秒。你想想看,GPU核心一个时钟周期才0.5纳秒,等一次显存访问要等几百个周期——这差距,够CPU执行上千条指令了。
| 显存类型 | 典型带宽 | 典型延迟 | 常见于 |
|---|---|---|---|
| HBM2e | 1.6 - 2.0 TB/s | 100 - 200 ns | A100, MI250 |
| HBM3 | 3.0 - 3.5 TB/s | 80 - 150 ns | H100, B200 |
| GDDR6 | 500 - 700 GB/s | 80 - 120 ns | RTX 3080, A4000 |
| GDDR6X | 800 - 1000 GB/s | 70 - 100 ns | RTX 3090, RTX 4090 |
我的经验:做压测时,别只看带宽。我曾经遇到一个场景,带宽利用率只有40%,但延迟已经飙到300ns了。后来发现是显存访问模式太随机,导致缓存命中率极低。说白了,带宽是上限,延迟才是日常。
1.3 吞吐压测的目标与指标
吞吐压测,说白了就是看你的系统能扛多大并发。两个核心指标:
- QPS(Queries Per Second):每秒能处理多少个查询请求。适合搜索、推荐这类场景。
- TPS(Transactions Per Second):每秒能完成多少个完整事务。适合交易、计费这类场景。
我个人习惯把QPS和TPS统称为「吞吐量」。但要注意,这两个指标不能混用。你想想看,一个搜索请求可能只查一次显存,但一个交易事务可能要读写十几次显存——同样的QPS和TPS,对显存的压力天差地别。
压测的核心公式:
吞吐量 = 并发数 / 平均延迟
这个公式看着简单,但实际压测时,并发数一上去,延迟就会飙升。为什么?因为显存带宽是共享的,大家都在抢。
1.4 显存瓶颈识别
怎么判断你的系统是不是卡在显存上?我教你们几个土办法:
- 看带宽利用率:用nvidia-smi或者Nsight Systems,如果带宽利用率超过80%,基本就是显存瓶颈了。
- 看延迟分布:如果大部分请求的延迟都集中在显存访问阶段,而不是计算阶段,那问题就在显存。
- 看缓存命中率:L1/L2缓存命中率低于50%的话,说明数据局部性很差,显存压力会很大。
我曾经踩过的坑:有一次压测,QPS死活上不去,我盯着带宽利用率看了半天,才60%。后来用Nsight一分析,发现是显存分配和释放太频繁,导致大量时间花在cudaMalloc/cudaFree上。嗯,从那以后,我养成了「显存复用」的习惯——提前分配好,压测过程中不释放。
最后,给你们看一个典型的显存瓶颈识别流程:
# 用nvidia-smi监控显存
nvidia-smi --query-gpu=memory.used,memory.total,utilization.memory --format=csv -l 1
# 用Nsight Systems做profiling
nsys profile --stats=true ./your_benchmark
# 重点关注:
# - Memory Throughput(显存吞吐)
# - Memcpy Bandwidth(拷贝带宽)
# - Kernel Launch Overhead(内核启动开销)
好了,这一章的基础就到这里。显存这东西,你越了解它,越知道怎么「骗」它——让数据尽量留在缓存里,让访问尽量连续,让分配尽量提前。后面的章节,咱们会一步步把这些技巧拆开来讲。
一句话记住:显存不是无限的,带宽不是无限的,但你的优化思路可以是无限的。