Bootstrap

【GPU】

在这里插入图片描述

不断修订中。。。



背景

显卡(Video card,Graphics card)全称显示接口卡,又称显示适配器,是计算机最基本配置、最重要的配件之一。

显卡接在电脑主板上,它将电脑的数字信号转换成模拟信号让显示器显示出来。

GPU(Graphic Processing Unit)是显卡上的一块芯片,就像 CPU 是主板上的一块芯片

CUDA (Compute Unified Device Architecture),通用并行计算架构,是一种运算平台

你只要使用一种类似于 C 语言的 CUDA C 语言,就可以开发 CUDA 程序,从而可以更加方便的利用 GPU 强大的计算能力,而不是像以前那样先将计算任务包装成图形渲染任务,再交由 GPU 处理

CPU负责逻辑性强的事物处理和串行计算,GPU 则专注于执行高度线程化的并行处理任务(大规模计算任务)

附1:独立显卡和集成显卡的区别。

所谓集成,是指显卡集成在主板上,不能随意更换。而独立显卡是作为一个独立的器件插在主板的AGP接口上的,可以随时更换升级。

另外,集成显卡使用物理内存,而独立显卡有自己的显存。一般而言,同期推出的独立显卡的性能和速度要比集成显卡好、快。

值得一提的是,集成显卡和独立显卡都是有 GPU的。

前言

日常炼丹时,nvidia-smi 可以看到如下界面

在这里插入图片描述

watch nvidia-smi 可以看到动态的,页面中显存占用和 GPU 利用率有直接的联系吗?本博客就此展开讨论

原文 科普帖:深度学习中GPU和显存分析

扩展版 浅谈深度学习:如何计算模型以及中间变量的显存占用大小

结论:显存占用GPU 利用率的关系可以类似看成内存CPU 的关系


1 显存占用

显存可以看成是空间,类似于内存。

显存用于存放模型,数据

显存越大,所能运行的网络也就越大

1.1 参数的显存占用

参数占用显存 = 参数数目 ×n

在这里插入图片描述

eg: Float 32 时,n = 4

在 PyTorch 中,当你执行完 model=MyGreatModel().cuda() 之后就会占用相应的显存,占用的显存大小基本与上述分析的显存差不多(会稍大一些,因为其它开销)。

在这里插入图片描述
综述:PyTorch显存机制分析

1.2 梯度与动量的显存占用

在这里插入图片描述

SGD + momentum(把上一次的梯度更新当成惯性) 的讲解可以参考 随机梯度下降与动量详解

1.3 输入输出的显存占用

计算出每一层输出的 Tensor 的形状,然后就能计算出相应的显存占用。

模型输出的显存占用,总结如下:

  • 需要计算每一层的 feature map 的形状(多维数组的形状),模型输出的显存占用与 batch size 成正比
  • 需要保存输出对应的梯度用以反向传播(链式法则的中间结果),模型输出不需要存储相应的动量信息(因为不需要执行优化)

举个例子,下面是一个计算图,输入 x,经过中间结果 z,然后得到最终变量 L
在这里插入图片描述
我们在 backward 的时候要求 L 对 x 的梯度,这个时候就需要在计算链 L 和 x 中间的 z:

在这里插入图片描述
dz / dx 这个中间值当然要保留下来以用于计算


另外需要注意

  • 输入(数据,图片)一般不需要计算梯度
  • 神经网络的每一层输入输出都需要保存下来,用来反向传播,但是在某些特殊的情况下,我们可以不要保存输入。比如 ReLU,在 PyTorch 中,使用 nn.ReLU(inplace = True) 能将激活函数 ReLU 的输出直接覆盖保存于模型的输入之中,节省不少显存。感兴趣的读者可以思考一下,这时候是如何反向传播的(提示:y=relu(x) -> dx = dy.copy(); dx[y<=0]=0)

1.4 总结

总结一下,我们在总体的训练中,占用显存大概分以下几类:

  • 模型中的参数(卷积层或其他有参数的层,参数指的是 trainable 的参数)
  • 模型在计算时产生的中间参数(也就是输入图像在计算时每一层产生的输入和输出)
  • backward 的时候产生的额外的中间参数(链式传播)
  • 优化器在优化时产生的额外的模型参数

但其实,我们占用的显存空间为什么比我们理论计算的还要大,原因大概是因为深度学习框架一些额外的开销吧,不过如果通过上面公式,理论计算出来的显存和实际不会差太多的。

1.5 优化

优化除了算法层的优化,最基本的优化无非也就一下几点:

  • 减少输入图像的尺寸
  • 减少 batch,减少每次的输入图像数量
  • 多使用下采样,池化层
  • 一些神经网络层可以进行小优化,利用 relu 层中设置 inplace
  • 购买显存更大的显卡
  • 从深度学习框架上面进行优化
  • 对于不需要 bp 的 forward,如 validation 请使用 torch.no_grad , 注意model.eval() 不等于 torch.no_grad() 请看如下讨论(Pytorch有什么节省显存的小技巧? - 郑哲东的回答 - 知乎)。

来自 ‘model.eval()’ vs ‘with torch.no_grad()’

在这里插入图片描述

在这里插入图片描述

测试时

with torch.no_grad():
	测试代码
	...
	...

1.6 显存监控

在这里插入图片描述

在这里插入图片描述

来自:综述:PyTorch显存机制分析

1.7 显卡设置(指定用哪张卡)

单卡

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

多卡

import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'

命令行指定

CUDA_VISIBLE_DEVICES=0,1 python train.py

2 GPU 利用率

GPU 计算单元类似于 CPU 中的核,用来进行数值计算。

衡量计算量的单位是 flop: the number of floating-point multiplication-adds,浮点数先乘后加算一个 flop。计算能力越强大,速度越快。衡量计算能力的单位是 flops: 每秒能执行的 flop 数量

在这里插入图片描述

3 显存释放

参考 深度学习训练已经停止了,可GPU内存还在占用着,怎么办?

watch nvidia-smi 能看到 PID 号那就比较好说了,直接 kill -9 PID 就行

watch nvidia-smi看不到,重启,哈哈哈,当然可以

不过下面这条指令可以查看到

fuser -v /dev/nvidia*

然后 kill 掉对应的 PID,可能不止一个


pytorch 清除显存 torch.cuda.empty_cache()

或者

命令行 nvidia-smi --gpu-reset -i [gpu_id]

4 影响训练速度的因素

学习摘抄来自 不止GPU!这些硬件也影响着深度学习训练速度

CPU、内存、硬盘、GPU

(1) 数据流动路径

假设我们现在有一批图片集放在硬盘当中,待读取进内存送入GPU运算,那么一般会经历以下流程:

  • CPU发出读取指令,从硬盘中找到图片数据,并存到内存中;

  • CPU从内存中取出一批数据,转化为numpy array,并作数据预处理/增强操作,如翻转、平移、颜色变换等。处理完毕后送回内存。

  • CPU内存(后面简称内存)和GPU内存(后面简称显存)各开辟一块缓冲区,内存中的一个batch的数据通过PCIe通道传输到显存当中。

  • GPU核心从显存中获取数据进行并行计算,计算结果返回至显存中。

  • 计算好的结果将从显存经过PCIe通道返回到内存。

这5个步骤涉及到几个影响数据传输速度的环节:

  • 硬盘读取速度;(影响因素最大)

  • PCIe传输速度;

  • 内存读写速度;

  • cpu频率。

(2)硬盘
在这里插入图片描述
(3)PCIe

内存中的数据通过PCIe总线传输到GPU显存当中,如果是单显卡的机器,大部分都能工作在PCIe 3.0 x16带宽下,此时的带宽为15.754GB/s。

在这里插入图片描述
当一个batch的数据(假设tensor shape: (32, 224, 224, 3))通过PCIe (3.0 x16)传输至GPU时,理论上所需时间为:2.44ms。(float 32的tensor的size 32×224×224×3×8≈0.0385GB, 0.0385 / 15.754 ∗ 1000 0.0385/15.754*1000 0.0385/15.7541000 ≈ 2.44)。

但目前市售的桌面级cpu(以英特尔酷睿系列的i7 cpu为例),CPU直连的PCIe通道一般只有16条,如果插了双GPU,那么只能工作在PCIe 3.0 x8带宽上(如果你的主板支持的话),那么理论延迟时间加倍。因此一般多卡机器如4卡、8卡、10卡,均最低使用双路工作站或服务器级别的CPU,如英特尔至强系列,此系列单U最低都能提供40条PCIe总线。

但这些延迟时间相比起GPU计算、IO等这些时间来说,其实影响甚微,加上还有软件层面的速度优化,这个延迟可以忽略不计,因此装机时不必太多纠结于PCIe通道数上。

(4)CPU频率
在这里插入图片描述
实际上,由于所有数据预处理的操作都在CPU上执行,因此CPU频率越高,生成图片的速度就越快,这样的差距也在情理之中。当机器拥有多显卡时,CPU喂数据的速度可能会成为整个系统的瓶颈。

(5)内存频率
在这里插入图片描述

在这里插入图片描述

(6)GPU

  • CUDA核心数
    核心数越多意味着执行并行计算的量就更多,基本确定了一张显卡的算力

  • 内存颗粒
    在这里插入图片描述

  • TensorCore
    TensorCore最早出现在Nvidia的Volta架构,用于加速深度学习经常需要的矩阵运算。当使用TensorCore时,我们需要从以往的单精度运算(float32)转换成为混合精度(float16+float32)计算,而TensorCore正是能加速这些混合精度运算。

  • 浮点算力
    浮点算力是最能反映显卡性能的指标,对于深度学习来说,单精度(float32)的运算最常见,因此最受关注的是float32的算力。而近来混合精度计算越来越受欢迎,float16的算力也越来越重要。至于int8一般用在要求极端速度的推理任务上。而float64一般在HPC应用中常用,所以基本可以忽略这一项。我们来看看几款Volta、Turing架构的显卡详细参数表:
    在这里插入图片描述

5 动态图 vs 静态图

深度学习中的动态图与静态图

在边的层面,二者区别更明显。对于动态图,只有在调用 layer 的 forward 时,才知道当前 layer 的输入来自哪个 layer。当forward 调用结束,这个拓扑关系就没了。然后下一次运行调用 forward 的时候再建立一次。而静态图,在模型定义的时候,就已经把图的全局拓扑确定了。

TensorFlow and PyTorch, our two chosen frameworks, handle this computational graph differently. In TensorFlow, the graph is static. That means that we create and connect all the variables at the beginning, and initialize them into a static (unchanging) session. This session and graph persists and is reused: it is not rebuilt after each iteration of training, making it efficient. However, with a static graph, variable sizes have to be defined at the beginning, which can be non-convenient for some applications, such as NLP with variable length inputs.

On the contrary, PyTorch uses a dynamic graph. That means that the computational graph is built up dynamically, immediately after we declare variables. This graph is thus rebuilt after each iteration of training. Dynamic graphs are flexible and allow us modify and inspect the internals of the graph at any time. The main drawback is that it can take time to rebuild the graph. Either PyTorch or TensorFlow can be more efficient depending on the specific application and implementation.

A 附录 Use GPU to speed up training

测试下矩阵运算GPU与CPU的差别

A.1 导入tensorflow模块

import tensorflow as tf
import time

A.2 建立和执行计算图

默认用GPU跑
tf.reduce_sum 计算矩阵的和

#建立计算图
size=500
W = tf.random_normal([size, size],name='W') # 500,500
X = tf.random_normal([size, size],name='X') # 500,500
mul = tf.matmul(W, X,name='mul')
sum_result = tf.reduce_sum(mul,name='sum')# 矩阵内的值加总

# 执行计算图
with tf.Session() as sess:
    result = sess.run(sum_result)
print('result=',result)    

output

result= -10656.27

A.3 测试GPU与CPU性能的差别

def performanceTest(device_name,size):
    with tf.device(device_name):
        W = tf.random_normal([size, size],name='W')
        X = tf.random_normal([size, size],name='X')
        mul = tf.matmul(W, X,name='mul')
        sum_result = tf.reduce_sum(mul,name='sum')

    startTime = time.time() 
    # 使用 tf.ConfigProto 建立 session 的配置设置 tfconfig,传入参数 log_device_placement设置为True
    tfconfig=tf.ConfigProto(log_device_placement=True)
    with tf.Session(config=tfconfig) as sess:
        result = sess.run(sum_result)
    takeTimes=time.time()  - startTime   
    print(device_name," size=",size,"Time:",takeTimes )
    return takeTimes

调用

gpu_set=[];cpu_set=[];i_set=[]
for i in range(0,5001,500):
    g=performanceTest("/gpu:0",i) # 0号GPU
    c=performanceTest("/cpu:0",i)
    gpu_set.append(g);cpu_set.append(c);i_set.append(i)
    print("--")

output

/gpu:0  size= 0 Time: 0.14670062065124512
/cpu:0  size= 0 Time: 0.16228890419006348
--
/gpu:0  size= 500 Time: 0.16929364204406738
/cpu:0  size= 500 Time: 0.18462872505187988
--
/gpu:0  size= 1000 Time: 0.13805508613586426
/cpu:0  size= 1000 Time: 0.13151001930236816
--
/gpu:0  size= 1500 Time: 0.1536424160003662
/cpu:0  size= 1500 Time: 0.2314302921295166
--
/gpu:0  size= 2000 Time: 0.21573805809020996
/cpu:0  size= 2000 Time: 0.4350099563598633
--
/gpu:0  size= 2500 Time: 0.37288379669189453
/cpu:0  size= 2500 Time: 0.6350183486938477
--
/gpu:0  size= 3000 Time: 0.5283641815185547
/cpu:0  size= 3000 Time: 0.9774112701416016
--
/gpu:0  size= 3500 Time: 0.7861192226409912
/cpu:0  size= 3500 Time: 1.4520719051361084
--
/gpu:0  size= 4000 Time: 1.1301662921905518
/cpu:0  size= 4000 Time: 2.030012845993042
--
/gpu:0  size= 4500 Time: 1.5385856628417969
/cpu:0  size= 4500 Time: 2.7173430919647217
--
/gpu:0  size= 5000 Time: 2.0486159324645996
/cpu:0  size= 5000 Time: 3.6564781665802
--

GPU的RAM不够就把size设置小一些,可视化一下结果

%matplotlib inline
import matplotlib.pyplot as plt
fig = plt.gcf()
fig.set_size_inches(6,4)
plt.plot(i_set, gpu_set, label = 'gpu')
plt.plot(i_set, cpu_set, label = 'cpu')
plt.legend()

output
这里写图片描述

B 附录

AI 计算框架 MindSpore

在这里插入图片描述
MindSpore(昇思 shēng) 是由华为于2019年8月推出的新一代全场景AI计算框架,2020年3月28日,华为宣布 MindSpore 正式开源

在这里插入图片描述
在这里插入图片描述

著名开源运动先驱人物 Eric Steven Raymond 曾经说过:Given enough eyeballs, all bugs are shallow

附录C——显卡参数

在这里插入图片描述
https://admin.pci-ids.ucw.cz/mods/PC/10de?action=help?help=pci

在这里插入图片描述

对话框中输入 1f50


10系列20系列30系列显卡的发布时间多少? - 尤娜Yuna的回答 - 知乎
https://www.zhihu.com/question/434191939/answer/1620590375

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2024.03

英伟达 GTC 开发者大会 GB200

请添加图片描述

因特尔 Gaudi3 碾压 H100

【NVIDIA GeForce GTX 1080 Ti】

GTX1080Ti是 NVIDIA 公司于2017年3月1日在旧金山发布的显卡产品 。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

【NVIDIA GeForce RTX 2080 Ti】

RTX 2080、RTX 2080Ti将会在2018年9月20日正式发售,售价分别为699美元(约合人民币4793元)和999美元(约合人民币6850元)。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

【NVIDIA GeForce RTX 3090】

NVIDIA GeForce RTX 3090是一款NVIDIA系列的显卡。 于2020年9月2日正式发布,并于2020年9月24日开始发货

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

【NVIDIA GeForce RTX 3080 Ti】

NVIDIA GeForce RTX 3080 Ti是NVIDIA发布的显卡,2021年6月3日上市。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

【NVIDIA TITAN Xp】

2016年10月
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

;