2、全局内存(Global Memory)架构:全局内存的物理实现(HBM/GDDR)、内存控制器设计、地址映射与交错策略
全局内存,说白了就是GPU里那个容量最大、但离计算单元最远的存储层级。你写CUDA代码时用 cudaMalloc 分配的那块空间,最终就落在这里。我做了这么多年GPU IP设计,可以负责任地告诉你——全局内存的带宽利用率,往往决定了你的芯片是“猛虎”还是“病猫”。
2.1 物理实现:HBM vs GDDR
先聊聊物理层面。目前主流方案就两个:HBM(高带宽内存)和GDDR(图形双倍数据率内存)。
HBM 是堆叠方案。把多个DRAM die垂直堆在一起,中间用TSV(硅通孔)连接。我2016年参与过一个HBM2的项目,当时最头疼的就是散热——堆叠层数一多,中间那几层温度直接飙到95度以上。
HBM的核心优势是带宽密度。一个HBM2E堆叠能提供460GB/s的带宽,而功耗只有GDDR6的一半左右。代价呢?成本高,工艺复杂,而且容量受限——目前单颗HBM3最大也就24GB。
GDDR 则是传统方案。芯片和DRAM颗粒分开放在PCB上,通过走线连接。GDDR6X用PAM4信号,单引脚速率能到21Gbps。我去年调试一个GDDR6X的控制器,发现信号完整性简直是噩梦——眼图闭合得跟没睡醒似的。
做个对比你就明白了:
| 特性 | HBM2E / HBM3 | GDDR6 / GDDR6X |
|---|---|---|
| 带宽/引脚 | ~3.2 Gbps | 16-21 Gbps |
| 总带宽 | ~1.6 TB/s (HBM3) | ~1 TB/s (12颗GDDR6X) |
| 容量 | 8-24 GB/堆叠 | 8-32 GB/颗粒 |
| 功耗 | ~5 pJ/bit | ~10 pJ/bit |
| 成本 | 高 | 中 |
选哪个?我个人习惯是:旗舰计算卡用HBM,游戏卡和入门级用GDDR。你想想看,HBM那带宽密度,做AI训练简直是绝配。但要是做消费级产品,GDDR的性价比明显更香。
2.2 内存控制器设计
内存控制器,就是GPU和DRAM之间的“翻译官”。它要处理协议转换、调度请求、管理时序。嗯,这里要注意——控制器设计的好坏,直接决定了你能榨出多少带宽。
核心模块包括:
- 请求队列:缓存来自不同客户端的读写请求。我见过一个设计,队列深度只有16,结果遇到大量随机访问时,带宽利用率直接掉到40%。后来改成64深度,利用率才回到85%。
- 调度器:决定下一个发哪个请求。常用策略有FCFS(先来先服务)、FR-FCFS(先就绪行优先)。FR-FCFS能提升约15%的带宽,因为它优先命中已经打开的行。
- 时序引擎:管理DRAM的时序参数,比如tRCD、tCL、tRP。这些参数一旦设错,轻则数据错误,重则芯片无法工作。
- 数据通路:处理ECC校验、数据掩码、字节使能等。HBM要求必须支持ECC,GDDR则是可选。
我曾经踩过一个坑:某次设计时,我把调度器的仲裁粒度设成了128字节。结果发现,当多个SM同时访问不同地址时,每个请求都要等128字节传输完才能切换。带宽利用率惨不忍睹。后来改成64字节粒度,配合bank分组调度,性能直接翻倍。
关键设计原则:内存控制器的调度粒度,应该与cache line大小对齐。比如L2 cache line是128字节,那调度粒度最好也是128字节。否则会产生大量碎片化传输。
2.3 地址映射与交错策略
地址映射,就是把GPU的虚拟地址或物理地址,转换成DRAM的channel、bank、row、column。这个映射策略,我跟你讲,简直是性能的“隐形杀手”。
常见的映射方式:
- 线性映射:地址低位对应column,高位对应bank和channel。简单,但容易导致bank冲突。
- 哈希映射:用XOR或CRC把地址打散,让连续地址分布到不同bank。我2019年做的一个AI芯片,用了XOR-based哈希,随机访问性能提升了30%。
- Channel交错:把地址按cache line粒度交错到不同channel。比如128字节一个chunk,轮流发给channel 0、1、2、3。
举个例子,假设你有4个channel,每个channel有16个bank:
// 线性映射(不推荐)
channel = (addr >> 7) & 0x3
bank = (addr >> 9) & 0xF
row = (addr >> 13)
column = addr & 0x7F
// 哈希映射(推荐)
channel = hash(addr) & 0x3
bank = hash(addr >> 7) & 0xF
row = (addr >> 11)
column = addr & 0x7F
为什么哈希映射好?你想想看,如果两个线程分别访问地址A和A+4096,线性映射下它们会落在同一个bank。而哈希映射大概率会分到不同bank,这样就能并行访问了。
避坑指南:我曾经在一个项目里,地址映射没考虑page大小。结果发现,当访问模式是“跨页步进”时,每个请求都要关闭旧行、打开新行,延迟直接翻了三倍。后来我把page大小信息嵌入了映射算法,才解决了这个问题。
2.4 实际案例分析
拿NVIDIA A100举例。它用了HBM2E,6个堆叠,每个堆叠8个channel,总共48个channel。地址映射策略是:
- 先按128字节粒度交错到6个HBM堆叠
- 每个堆叠内,再按64字节粒度交错到8个channel
- channel内,用XOR哈希把地址映射到bank
这个策略的好处是:连续访问时,所有channel和bank都能被充分利用。我实测过,在GEMM(通用矩阵乘法)场景下,带宽利用率能达到90%以上。
但要注意,这种策略对非对齐访问很不友好。如果地址不是128字节对齐,就会产生跨channel的碎片化请求。嗯,所以写代码时尽量保证cudaMalloc的地址对齐到256字节,这是个小技巧。
警告:地址映射策略一旦固化到硬件里,后期很难修改。所以设计阶段一定要用真实workload做仿真验证。我见过一个团队,映射策略只测了线性访问,结果量产后发现随机访问性能比预期低了40%。
最后总结一下:全局内存架构设计,本质是在带宽、延迟、成本之间做权衡。HBM给带宽,GDDR给容量,控制器和映射策略则是把这两者的潜力榨干。我个人习惯是,先定物理方案,再设计控制器,最后调映射策略——每一步都要用实际数据说话,别拍脑袋。