聊聊 GPU 的计算能力上限

点击上方机器学习与生成对抗网络”,关注"星标"node

获取有趣、好玩的前沿干货!程序员

来自 | 知乎   做者丨卜居
算法

连接丨https://zhuanlan.zhihu.com/p/231302709数据库

文仅交流,如侵删编程

   一、前言

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 架构一探究竟。

   二、GPU 架构演变

图形处理器(GPU, Graphics Processing Unit),用来加速计算机图形实时绘制,俗称显卡,常常用于打游戏。自 NVIDIA 于 1999 年发明第一款 GPU GeForce 256,尔来二十有一年矣。
GeForce 256, 1999
从图片看到 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 内部架构以下图:
G80/G92 架构图
每一个 SM 内部有 8 个 SP(流处理器,Streaming Processor,后改称 CUDA Core),这是真正干活的单元,能够完成基本数学计算。8 个 SP 须要听口号统一行动,互相之间经过 shared memory 传递信息。
G80 架构比较简单,奠基了通用计算 GPU 的基础。接下来的 14 年,NVIDIA GPU 以大约每两年一代的速度逐步升级硬件架构,配套软件和库也不断丰富起来,CUDA Toolkit 最新已到 11.0,生态系统已颇为健壮,涵盖石油探测、气象预报、医疗成像、智能安防等各行各业, GPU 现已成为世界顶级超算中心的标配计算器件。
下表展现了从 2006 年至今支持 CUDA 计算的 GPU。有没有看到你手中的那一款?

架构起名是有讲究的,都是科学史上著名的物理学家、数学家(同时也是理工科同窗的梦魇,多少次由于写错了计量单位被扣分): 特斯拉、费米、开普勒、麦克斯韦、帕斯卡、伏打、图灵、安培。 (那么接下来是?
限于篇幅,咱们再也不深刻探讨每种架构细节,直接跳跃到最新 Ampere 架构,看看世界顶级计算能力是如何炼成的。对历史感兴趣的读者能够继续研读扩展材料【6】。

   三、Ampere 架构详解

从 Ampere 白皮书【1】看到 GA100 的整体架构图以下:
GA100 整体架构图
整体布局比较中正,八个 GPC 与 L2 Cache 坐落于核心地段,左右为外部存储接口,12 道显存控制器负责与 6 块 HBM2 存储器数据交互,顶部为 PCIe 4.0 控制器负责与主机通讯,底部又有 12 条高速 NVLink 通道与其余 GPU 连为一体。
GA100 以及基于 GA100 GPU 实现的 A100 Tensor Core GPU 内部资源以下表所示:
名词解释:
  • GPC —— 图形处理簇,Graphics Processing Clusters
  • TPC —— 纹理处理簇,Texture Processing Clusters
  • SM —— 流多处理器,Stream Multiprocessors
  • HBM2 —— 高带宽存储器二代,High Bandwidth Memory Gen 2
实际上到手的 A100 GPU 是阉割版,相比完整版 GA100 少了一组 GPC 和一组 HBM2。至于为何,要考虑这个芯片巨大的面积和工艺水平,以及整板功耗。因为少了这一组 GPC,致使后面一些奇奇怪怪的数字出现,等到了合适的时机再解释。
A100 SM 的架构细节以下图所示:
GA100 SM 架构图
GA100 的 SM 架构相比 G80 复杂了不少,占地面积也更大。每一个 SM 包括 4 个区块,每一个区块有独立的 L0 指令缓存、Warp 调度器、分发单元,以及 16384 个 32 位寄存器,这使得每一个 SM 能够并行执行 4 组不一样指令序列。4 个区块共享 L1 指令缓存和数据缓存、shared memory、纹理单元。
图中能看出 INT32 计算单元数量与 FP32 一致,而 FP64 计算单元数量是 FP32 的一半,这在后面峰值计算能力中会有体现。
每一个 SM 除了 INT3二、FP3二、FP64 计算单元以外,还有额外 4 个身宽体胖的 Tensor Core,这是加速 Deep Learning 计算的重磅武器,已发展到第三代,每一个时钟周期可作 1024 次 FP16 乘加运算,与 Volta 和 Turing 相比,每一个 SM 的吞吐翻倍,支持的数据类型也更为丰富,包括 FP6四、TF3二、FP1六、BF1六、INT八、INT四、INT1(另外还有 BF16),不一样类型指令吞吐见下表【2】所示:
Volta/Turing/Ampere 单个 SM 不一样数值类型指令吞吐
利用这张表咱们能够计算出 GPU 峰值计算能力,公式以下:

其中   为 GPU 核心的运行频率,   为 GPU SM 数量,   为特定数据类型的指令吞吐,后面乘 2 是由于乘加视做两次浮点运算。
例如 A100 FP32 CUDA Core 指令吞吐   ,核心运行频率为   ,总共 SM 数量   ,那么

对照 NVIDIA Ampere 白皮书【1】 中有关 FP32 峰值计算能力的数字 19.5 TFLOPS,基本一致。
将剩下的指令吞吐数字代入公式中,能够获得 A100 其余数据类型的峰值计算能力,包括使人震惊的 TF32 和使人迷惑的 FP16 性能。
理论峰值计算能力只是一个上限,咱们还关心 GPU 计算能力实测值,能够利用以下公式:

其中   为某个任务所需的所有乘、加运算数量,例如矩阵乘

其中 A、B、C、D 均为矩阵,各自尺寸如下标做为标识。完成上述公式计算所需总乘加次数为:

则 GEMM 实测计算能力为:

从前面两张图看到 Volta/Turing 架构 CUDA Core FP16 计算吞吐为 FP32 的 2 倍,而到了 Ampere 架构发生了阶跃,直接变 4 倍(256 vs 64,78 TFLOPS vs 19.5 TFLOPS),咱们拿到物理卡后第一时间进行了不一样精度 GEMM 评测,发现 FP16 性能相比 FP32 并不是 4 倍,而是和 Turing 同样 2 倍左右,感受更像是文档出现了谬误。
CUTLASS 实测性能
等待后续 NV 的软件或文档更新来释疑。

   四、不一样型号 GPU 峰值计算能力对比

咱们能够经过翻阅 GPU 数据手册、白皮书得到不一样型号 GPU 峰值计算能力,但这仅停留在纸面,对于管控系统而言须要借助工具来获取这些数值记录在设备数据库,以后调度器可根据计算需求以及库存状况进行计算能力分配。本节将提供这样一个工具来自动计算 GPU 峰值计算能力,基于 CUDA Runtime API 编写,对具体 CUDA 版本没有特殊要求。A100 上运行输出以下:
由此获得的 A100 理论峰值计算能力与上节 CUTLASS 实测结果能对号入座。
利用该工具,你能够更深刻了解本身手头 GPU 的计算能力上限,买新卡时会作出更理性判断。下面展现 2016-2020 主流 GPU 型号及其理论峰值计算力:
P4, 2016, Pascal
Tesla P4 峰值计算能力, P4 实际能够超频到 1.531 GHz,官方并未对超频性能作出承诺,用户需根据业务特色进行合理设置
P40, 2016, Pascal
Tesla P40 峰值计算能力
P100, 2016, Pascal
Tesla P100(PCIe 版) 峰值计算能力, NVLink 版比这个结果要高一点
GTX 1080, 2016, Pascal
GTX 1080 峰值计算能力
Tesla V100, 2017, Volta
Tesla V100 峰值计算能力,忽略最后一行(系早期工具 bug)
T4, 2018, Turing
Tesla T4 峰值计算能力, 实测 T4 正常工做频率约为峰值的 70%
RTX 2080 Ti, 2018, Turing
RTX 2080 Ti 峰值计算能力
Jetson Nano, 2019, Maxwell
Jetson Nano 峰值计算能力
Jetson TX2, 2016, Pascal
Jetson TX2 峰值计算能力
Jetson Xavier, 2018, Volta
Jetson Xavier 峰值计算能力
若是上面结果中没有发现你的 GPU 装备,欢迎运行下面代码并将结果发在评论区。

   五、本文代码

calc_peak_gflops.cpp
  
  
   
   
            
   
   
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <cuda_runtime.h>


#define CHECK_CUDA(x, str) \
if((x) != cudaSuccess) \
{ \
fprintf(stderr, str); \
exit(EXIT_FAILURE); \
}

int cc2cores(int major, int minor)
{
typedef struct
{
int SM;
int Cores;
} sSMtoCores;

sSMtoCores nGpuArchCoresPerSM[] =
{
{0x30, 192},
{0x32, 192},
{0x35, 192},
{0x37, 192},
{0x50, 128},
{0x52, 128},
{0x53, 128},
{0x60, 64},
{0x61, 128},
{0x62, 128},
{0x70, 64},
{0x72, 64},
{0x75, 64},
{0x80, 64},
{-1, -1}
};

int index = 0;

while (nGpuArchCoresPerSM[index].SM != -1)
{
if (nGpuArchCoresPerSM[index].SM == ((major << 4) + minor))
{
return nGpuArchCoresPerSM[index].Cores;
}

index++;
}

printf(
"MapSMtoCores for SM %d.%d is undefined."
" Default to use %d Cores/SM\n",
major, minor, nGpuArchCoresPerSM[index - 1].Cores);
return nGpuArchCoresPerSM[index - 1].Cores;
}

bool has_fp16(int major, int minor)
{
int cc = major * 10 + minor;
return ((cc == 60) || (cc == 62) || (cc == 70) || (cc == 75) || (cc == 80));
}
bool has_int8(int major, int minor)
{
int cc = major * 10 + minor;
return ((cc == 61) || (cc == 70) || (cc == 75) || (cc == 80));
}
bool has_tensor_core_v1(int major, int minor)
{
int cc = major * 10 + minor;
return ((cc == 70) || (cc == 72) );
}
bool has_tensor_core_v2(int major, int minor)
{
int cc = major * 10 + minor;
return (cc == 75);
}
bool has_tensor_core_v3(int major, int minor)
{
int cc = major * 10 + minor;
return (cc == 80);
}

int main(int argc, char **argv)
{
cudaDeviceProp prop;
int dc;
CHECK_CUDA(cudaGetDeviceCount(&dc), "cudaGetDeviceCount error!");
printf("GPU count = %d\n", dc);

for(int i = 0; i < dc; i++)
{
printf("=================GPU #%d=================\n", i);
CHECK_CUDA(cudaGetDeviceProperties(&prop, i), "cudaGetDeviceProperties error");
printf("GPU Name = %s\n", prop.name);
printf("Compute Capability = %d.%d\n", prop.major, prop.minor);
printf("GPU SMs = %d\n", prop.multiProcessorCount);
printf("GPU CUDA cores = %d\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount);
printf("GPU SM clock rate = %.3f GHz\n", prop.clockRate/1e6);
printf("GPU Mem clock rate = %.3f GHz\n", prop.memoryClockRate/1e6);
printf("FP32 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2);
if(has_fp16(prop.major, prop.minor))
{
printf("FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 2);
}
if(has_int8(prop.major, prop.minor))
{
printf("INT8 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 4);
}
if(has_tensor_core_v1(prop.major, prop.minor))
{
printf("Tensor Core FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 8);
}
if(has_tensor_core_v2(prop.major, prop.minor))
{
printf("Tensor Core FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 8);
printf("Tensor Core INT8 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 16);
}
if(has_tensor_core_v3(prop.major, prop.minor))
{
printf("Tensor Core TF32 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 8);
printf("Tensor Core FP16 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 16);
printf("Tensor Core INT8 Peak Performance = %.3f GFLOPS\n", cc2cores(prop.major, prop.minor) * prop.multiProcessorCount * (prop.clockRate / 1e6) * 2 * 32);
}
}
return 0;
}
编译:
  
  
   
   
            
   
   
nvcc -I/usr/local/cuda/include -L/usr/local/cuda/lib64 -lcudart -o calc_peak_gflops calc_peak_gflops.cpp
若是提示 nvcc 命令未找到,请先安装 CUDA 并设置 PATH 环境变量包含 nvcc 所在目录(Linux 默认为 /usr/local/cuda/bin)。
  
  
   
   
            
   
   
export PATH=/usr/local/cuda/bin:$PATH
运行:
  
  
   
   
            
   
   
./calc_peak_gflops


   六、后记

经过获取 GPU 峰值计算能力,能够加深对手头的硬件资源了解程度,不被过分宣传的文章洗脑,多快好省地完成工做。

参考文献
[1] https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/nvidia-ampere-architecture-whitepaper.pdfwww.nvidia.com
[2] GPU Performance Background User Guidedocs.nvidia.com
[3] https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/tesla-product-literature/NVIDIA-Kepler-GK110-GK210-Architecture-Whitepaper.pdfwww.nvidia.com
[4] https://images.nvidia.com/content/pdf/tesla/whitepaper/pascal-architecture-whitepaper.pdfimages.nvidia.com
[5] https://images.nvidia.com/content/volta-architecture/pdf/volta-architecture-whitepaper.pdfimages.nvidia.com
[6] NVIDIA GPU架构的变迁史


猜您喜欢:缓存


超100篇!CVPR 2020最全GAN论文梳理汇总!微信

附下载 | 《Python进阶》中文版网络

附下载 | 经典《Think Python》中文版架构

附下载 | 《Pytorch模型训练实用教程》并发

附下载 | 最新2020李沐《动手学深度学习》

附下载 | 《可解释的机器学习》中文版

附下载 |《TensorFlow 2.0 深度学习算法实战》

附下载 | 超100篇!CVPR 2020最全GAN论文梳理汇总!

附下载 |《计算机视觉中的数学方法》分享


本文分享自微信公众号 - 机器学习与生成对抗网络(AI_bryant8)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。