1、背景介绍
2020 年 5 月 14日,NVIDIA 创始人兼首席执行官黄仁勋在自家厨房直播带货,哦不对应该是 NVIDIA GTC 2020 主题演讲中热情洋溢地介绍了新鲜出炉的基于最新 Ampere 架构的 NVIDIA A100 GPU,号称史上最豪华的烧烤。
NVIDIA A100 Tensor Core GPU 基于最新的 Ampere 架构,其核心为基于台积电 7nm 工艺制造的 GA100,内有 542 亿晶体管,裸片尺寸为 826mm^2,而前代 GV100 裸片尺寸 815mm^2,内有 211 亿晶体管,短短 3 年时间,得益于新工艺,芯片集成度翻了不止一倍!
从 NVIDIA 发布会内容以及白皮书中能看到一些令人震惊的数字,今天我们来解密这些数字是怎么计算得到的。为此我们需要深入 GPU 架构一探究竟。
2、Ampere GPU 架构介绍
图形处理器(GPU, Graphics Processing Unit),用来加速计算机图形实时绘制,俗称显卡,经常用于打游戏。自 NVIDIA 于 1999 年发明第一款 GPU GeForce 256,尔来二十有一年矣。
从图片看到 GeForce 256 衣着相当简朴,完全看不到 RTX 3090 的贵族气质,显示输出口仅支持 VGA,显存 32 MB,另外和主机的接口是早已不见踪影的 AGP,支持的图形 API 为 DirectX 7.0、OpenGL 1.2,目前主流游戏都跑不动,放到现在只能当摆设。
那时显卡还只是纯粹的显卡,硬件架构还是固定的渲染流水线,如下图所示。
渲染流水线
渲染流水线中可被程序员控制的部分有两处:Geometry Processing 和 Pixel Processing,前者处理几何坐标变换,涉及矩阵乘计算;后者处理图像像素,涉及插值计算。有一些对科学有执着追求的人们试图用渲染流水线做一些除了打游戏之外更为正经的工作。于是,他们把计算输入数据伪造成顶点坐标或纹理素材,把计算机程序模拟为渲染过程,发挥异于常人的聪明才智,使用 OpenGL/DirectX/Cg 实现各类数值算法,将显卡这个为游戏做出突出贡献的可造之材打造为通用并行计算的利器,此时的 GPU 被赋能了更多工作内容,称作 GPGPU(General Purpose GPU)。
从事 GPGPU 编程的程序员十分苦逼,既要懂图形 API、GPU 架构,还要把各个领域算法摸清楚翻译为顶点坐标、纹理、渲染器这些底层实现,十分难以维护,今天一气呵成的代码,明天就形同陌路。程序如有 bug,调试工具奇缺,只能靠运气和瞪眼法。
为了彻底解放生产力,提高编程效率,NVIDIA 在 2006 年引入统一图形和计算架构以及 CUDA 工具,从此 GPU 就可以直接用高级语言编程,由程序员控制众多 CUDA 核心完成海量数值计算,GPGPU 业已成为历史。
GeForce 8800 是第一款支持 CUDA 计算的 GPU,核心为 G80,首次将渲染流水线中分离的顶点处理器与像素处理器替换为统一的计算单元,可用于执行顶点/几何/像素/通用计算等程序。G80 首次引入 SIMT(Single-Instruction Multiple-Thread) 执行模型,多个线程在不同计算单元上并发执行同一条指令,引入 barrier 和 shared memory实现线程间同步与通信。G80 架构图如下:
G80/G92 架构完全相同,G92 相比 G80 仅为工艺升级(90nm -> 65nm)。在 G80 中有 8 个 TPC(纹理处理簇,Texture Processing Clusters),每个 TPC 有 2 个 SM(流多处理器,Stream Multiprocessors),共计 16 个 SM。每个 SM 内部架构如下图:
每个 SM 内部有 8 个 SP(流处理器,Streaming Processor,后改称 CUDA Core),这是真正干活的单元,可以完成基本数学计算。另外 2 个 绿色方块 SFU 为特殊功能单元,可以快速计算常用数学函数如 sin cos 等。8 个 SP 需要听口号统一行动,互相之间通过 shared memory 传递信息。
G80 架构比较简单,奠定了通用计算 GPU 的基础。接下来的 14 年,NVIDIA GPU 以大约每两年一代的速度逐步升级,配套软件和库也不断丰富起来,CUDA Toolkit 最新已到 11.0。如今 CUDA 生态系统已颇为健壮,涵盖石油探测、气象预报、医疗成像、智能安防等各行各业, GPU 现已成为世界顶级超算中心的标配计算器件。
Nvidia 通用计算 GPU 架构历程如下表所示
发布时间 | 架构名 | 版本号 | 制造工艺 | 代表产品 |
2006 | Tesla(G80) | 1.0 | TSMC 90nm | GeForce 8800 |
2008 | Tesla 2.0(GT200) | 1.3 | TSMC 65nm | GeForce GTX 280 |
2010 | Fermi | 2.0 | TSMC 40nm | Geforce GTX 480 |
2012 | Kepler | 3.0、3.5、3.7 | TSMC 28nm | Geforce GTX 680, Tesla K20/K40/K80 |
2014 | Maxwell | 5.0、5.2 | TSMC 28nm | GeForce GTX 750/980, Tesla M40 |
2016 | Pascal | 6.0、6.1、6.2 | TSMC 16nm | GeForce GTX 1080/1070/1060, Tesla P100/P40/P4 |
2017 | Volta | 7.0 | TSMC 12nm | Tesla V100, Titan V |
2018 | Turing | 7.5 | TSMC 12nm | GeForce RTX 2080Ti, Quadro RTX 6000/8000, Tesla T4 |
2020 | Ampere | 8.0 | TSMC 7nmSamsung 8nm | Tesla A100RTX 3090/3080/3070 |
NV 起名是有讲究的,都是历史上著名物理学家、数学家,特斯拉、费米、开普勒、麦克斯韦、帕斯卡、伏打、图灵、安培,再后面会是牛顿还是爱因斯坦?
回顾完 NVIDIA GPU 架构发展史,让我们的目光回到最新 Ampere 架构。
从 Ampere 白皮书【1】看到 GA100 的总体架构图如下
这是一张很大的图,里面包含了丰富的细节,可能有些专用名词大家还不熟悉,逐一解释:
GPC —— 图形处理簇,Graphics Processing Clusters
TPC —— 纹理处理簇,Texture Processing Clusters
SM —— 流多处理器,Stream Multiprocessors
HBM2 —— 高带宽存储器二代,High Bandwidth Memory Gen 2
总体布局比较中正,八个 GPC 与 L2 Cache 坐落于核心地段,左右为外部存储接口,12 道显存控制器负责与 6 块 HBM2 存储器数据交互,顶部为 PCIe 4.0 控制器负责与主机通信,底部又有 12 条高速 NVLink 通道与其他 GPU 连为一体。
GA100 以及基于 GA100 GPU 实现的 A100 Tensor Core GPU 内部资源如下表所示:
GA100(完整版) | A100 GPU(阉割版) | |
GPC | 8 | 7 |
TPC | 64 | 56 |
SM | 128 | 108 |
CUDA Core | 8192 | 6912 |
Tensor Core | 512 | 432 |
HBM2 | 6 | 5 |
显存控制器数目 | 12 | 10 |
显存位宽 | 6144 | 5120 |
实际上到手的 A100 GPU 是阉割版,相比完整版 GA100 少了一组 GPC 和一组 HBM2。至于为什么,要考虑这个芯片巨大的面积和工艺水平,以及功耗。
由于少了这一组 GPC,导致后面怪异的数字出现,且听下回分解。
A100 SM 的架构细节如下图所示:
GA100 的 SM 架构相比 G80 复杂了很多,占地面积也更大。每个 SM 包括 4 个区块,每个区块有独立的 L0 指令缓存、Warp 调度器、分发单元,以及 16384 个 32 位寄存器,这使得每个 SM 可以并行执行 4 组不同指令序列。4 个区块共享 L1 指令缓存和数据缓存、shared memory、纹理单元。
图中能看出 INT32 计算单元数量与 FP32 一致,而 FP64 计算单元数量是 FP32 的一半,这在后面峰值计算能力中会有体现。
每个 SM 除了 INT32、FP32、FP64 计算单元之外,还有额外 4 个身宽体胖的 Tensor Core,这是加速 Deep Learning 计算的重磅武器,已发展到第三代,每个时钟周期可做 1024 次 FP16 乘加运算,与 Volta 和 Turing 相比,每个 SM 的吞吐翻倍,支持的数据类型也更为丰富,包括 FP64、TF32、FP16、BF16、INT8、INT4、INT1。
3、GPU 峰值计算能力
Volta、Turing、Ampere 这三代 GPU 不同类型指令吞吐见下表【2】所示:
注:图中未显示 BF16 吞吐,根据 Ampere 白皮书【1】,Tensor Core 的 BF16 吞吐与 FP16 一致,而 CUDA Core 的 BF16 吞吐为 FP16 的一半。
利用这张表我们可以计算出 GPU 峰值计算能力,公式如下:
其中为 GPU 核心的运行频率,为 GPU SM 数量,为特定数据类型的指令吞吐,后面乘 2 是因为乘加视作两次浮点运算。
例如 A100 FP32 CUDA Core 指令吞吐 ,核心运行频率为 ,总共 SM 数量
对照 NVIDIA Ampere 白皮书【1】 中有关 FP32 峰值计算能力的数字 19.5 TFLOPS,基本一致。
以此类推,可以分别计算得到 A100 不同数值精度、是否稀疏化、Tensor Core ON/OFF 的峰值计算能力。
通过该公式也可以计算得到 Volta、Turing 系列产品的不同配置下的峰值计算能力。
手动计算较为繁琐,我们也可以通过编写程序自动获取 等参数,经过简单计算得到相应 GPU 的峰值计算能力。关键代码段如下:
cudaDeviceProp prop;
CHECK_CUDA(cudaGetDeviceProperties(&prop, gpu_index), "cudaGetDeviceProperties error");
printf("GPU Name = %s\n", prop.name);
printf("Compute Capability = %d.%d\n", prop.major, prop.minor); // 获得 SM 版本
printf("GPU SMs = %d\n", prop.multiProcessorCount); // 获得 SM 数目
printf("GPU SM clock rate = %.3f GHz\n", prop.clockRate/1e6); // prop.clockRate 单位为 kHz,除以 1e6 之后单位为 GHz
printf("GPU Mem clock rate = %.3f GHz\n", prop.memoryClockRate/1e6); // 同上
if((prop.major == 8) && (prop.minor == 0)) // SM 8.0,即 A100
{
// 根据公式计算峰值吞吐,其中 64、32、256、128 等数字是从前文 SM 吞吐表中查到
printf("-----------CUDA Core Performance------------\n");
printf("FP32 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 64 * 2);
printf("FP64 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 32 * 2);
printf("FP16 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 256 * 2);
printf("BF16 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 128 * 2);
printf("INT8 Peak Performance = %.3f GOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 256 * 2);
printf("-----------Tensor Core Dense Performance------------\n");
printf("TF32 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 512 * 2);
printf("FP64 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 64 * 2);
printf("FP16 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 1024 * 2);
printf("BF16 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 1024 * 2);
printf("INT8 Peak Performance = %.3f GOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 2048 * 2);
printf("INT4 Peak Performance = %.3f GOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 4096 * 2);
printf("INT1 Peak Performance = %.3f GOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 16384 * 2);
printf("-----------Tensor Core Sparse Performance------------\n");
printf("TF32 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 1024 * 2);
printf("FP16 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 2048 * 2);
printf("BF16 Peak Performance = %.3f GFLOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 2048 * 2);
printf("INT8 Peak Performance = %.3f GOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 4096 * 2);
printf("INT4 Peak Performance = %.3f GOPS.\n", prop.multiProcessorCount * (prop.clockRate/1e6) * 8192 * 2);
}
在 滴滴云 A100 机器上运行结果如下图所示:
上述代码稍作修改可用于获取其他型号 GPU 的峰值计算能力。