聊聊 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索