AI-12计算性能
12.1. 编译器和解释器
命令式编程(imperative programming)和符号式编程(symbolic programming)
单线程的Python解释器使得,Python将很难让所有的GPU都保持忙碌。可以通过将Sequential
替换为HybridSequential
来解决代码中这个瓶颈。
编译模型的好处之一是我们可以将模型及其参数序列化(保存)到磁盘。这允许这些训练好的模型部署到其他设备上,并且还能方便地使用其他前端编程语言。同时,通常编译模型的代码执行速度也比命令式编程更快。
练习:
-
回顾前几章中感兴趣的模型,能提高它们的计算性能吗? 答:前几章在学习过程中都是用命令式编程便于理解模型架构,但是计算性能低,设计为符号式编程可以提高性能。
12.2. 异步计算
在诸多的深度学习框架中,MXNet和TensorFlow之类则采用了一种异步编程(asynchronous programming)模型来提高性能,而PyTorch则使用了Python自己的调度器来实现不同的性能权衡。对PyTorch来说GPU操作在默认情况下是异步的。
NumPy点积是在CPU上执行的,而PyTorch矩阵乘法是在GPU上执行的,后者的速度要快得多。
广义上说,PyTorch有一个用于与用户直接交互的前端(例如通过Python),还有一个由系统用来执行计算的后端。
Python前端线程和C++后端线程之间的简化交互可以概括如下:
-
前端命令后端将计算任务
y = x + 1
插入队列; -
然后后端从队列接收计算任务并执行;
-
然后后端将计算结果返回到前端。
练习:
-
在CPU上,对本节中相同的矩阵乘法操作进行基准测试,仍然可以通过后端观察异步吗?
在CPU上,两中矩阵计算方式仍存在差异
12.3. 自动并行
手动调度由此产生的并行程序将是相当痛苦的,后端可以通过自动化地并行计算和通信来提高性能。这就是基于图的计算后端进行优化的优势所在。
练习:
-
在本节定义的
run
函数中执行了八个操作,并且操作之间没有依赖关系。设计一个实验,看看深度学习框架是否会自动地并行地执行它们。 -
当单个操作符的工作量足够小,即使在单个CPU或GPU上,并行化也会有所帮助。设计一个实验来验证这一点。
(图中为40*40的矩阵运算),在只有一个GPU条件下并行化性能有所提升。
12.4. 硬件
大多数深度学习研究者和实践者都可以使用一台具有相当数量的内存、计算资源、某种形式的加速器(如一个或者多个GPU)的计算机。计算机由以下关键部件组成:
-
一个处理器(也被称为CPU),它除了能够运行操作系统和许多其他功能之外,还能够执行给定的程序。它通常由8个或更多个核心组成;
-
内存(随机访问存储,RAM)用于存储和检索计算结果,如权重向量和激活参数,以及训练数据;
-
一个或多个以太网连接,速度从1GB/s到100GB/s不等。在高端服务器上可能用到更高级的互连;
-
高速扩展总线(PCIe)用于系统连接一个或多个GPU。服务器最多有8个加速卡,通常以更高级的拓扑方式连接,而桌面系统则有1个或2个加速卡,具体取决于用户的预算和电源负载的大小;
-
持久性存储设备,如磁盘驱动器、固态驱动器,在许多情况下使用高速扩展总线连接。它为系统需要的训练数据和中间检查点需要的存储提供了足够的传输速度。
最基本的内存主要用于存储需要随时访问的数据。目前,CPU的内存通常为DDR4类型,每个模块提供20-25Gb/s的带宽。每个模块都有一条64位宽的总线。通常使用成对的内存模块来允许多个通道。
硬盘的主要优点之一是相对便宜,而它们的众多缺点之一是典型的灾难性故障模式和相对较高的读取延迟。
固态驱动器(solid state drives,SSD)使用闪存持久地存储信息。这允许更快地访问存储的记录。现代的固态驱动器的IOPs可以达到10万到50万,比硬盘驱动器快3个数量级
练习:
-
编写C语言来测试访问对齐的内存和未对齐的内存之间的速度是否有任何差异。(提示:小心缓存影响。)
答:对齐内存访问速度快,未对齐的内存会有多次访问下一级缓存的操作。
-
测试按顺序访问或按给定步幅访问内存时的速度差异。
答:顺序访问符合其邻近性,访问内存更迅速
-
如何测量CPU上的缓存大小?
答:在内存上定义一个较长的数组,并选取不同的长度区间随机读取。由于从内存中读取数据的时间远大与从cache中读取的时间,因此当数组sizeof大于缓存时,读取时间会明显增加。
12.5. 多GPU训练
使用nvidia-smi
命令列出计算机上所有可用的GPU,那么如何实现深度学习训练的并行化呢?
假设我们有多个GPU,我们希望以一种方式对训练进行拆分:
第一种方法,在多个GPU之间拆分网络(可以想象为GPU串行)。 也就是说,每个GPU将流入特定层的数据作为输入,跨多个后续层对数据进行处理,然后将数据发送到下一个GPU。但GPU之间的数据传输可能需要。
第二种方法,拆分层内的工作(可以想象为GPU并行工作)。 例如,将问题分散到4个GPU,每个GPU生成16个通道的数据,而不是在单个GPU上计算64个通道。
最后一种方法,跨多个GPU对数据进行拆分。 这种方法最简单,并可以应用于任何情况,同步只需要在每个小批量数据处理之后进行。 GPU的数量越多,小批量包含的数据量就越大,从而就能提高训练效率。在数据并行中,数据需要跨多个GPU拆分,其中每个GPU执行自己的前向传播和反向传播,随后所有的梯度被聚合为一,之后聚合结果向所有的GPU广播。
12.6. 参数服务器
多GPU服务器数据并行操作
分布式并行训练的发生:
-
在每台机器上读取一组(不同的)批量数据,在多个GPU之间分割数据并传输到GPU的显存中。基于每个GPU上的批量数据分别计算预测和梯度。
-
来自一台机器上的所有的本地GPU的梯度聚合在一个GPU上(或者在不同的GPU上聚合梯度的某些部分)。
-
每台机器的梯度被发送到其本地CPU中。
-
所有的CPU将梯度发送到中央参数服务器中,由该服务器聚合所有梯度。
-
然后使用聚合后的梯度来更新参数,并将更新后的参数广播回各个CPU中。
-
更新后的参数信息发送到本地一个(或多个)GPU中。
-
所有GPU上的参数更新完成。
练习:
-
请尝试进一步提高环同步的性能吗。(提示:可以双向发送消息。) 答:每个节点同时向两侧发送消息聚合梯度的速度将被加快。
-
在计算仍在进行中,可否允许执行异步通信?它将如何影响性能? 答:。。。
-
怎样处理在长时间运行的计算过程中丢失了一台服务器这种问题?
答:尝试设计一种容错机制来避免重启计算这种解决方案? 可以设置冗余的服务器,在接受梯度时若发现某服务器丢失,则使用冗余服务器代替进行计算。