13.4. 硬件¶ 在 SageMaker Studio Lab 中打开 Notebook
要构建性能卓越的系统,需要很好地理解算法和模型,以捕获问题的统计学方面。同时,对底层硬件有起码的了解也是必不可少的。本节不能替代关于硬件和系统设计的正式课程。相反,它可以作为一个起点,帮助我们理解为什么某些算法比其他算法更有效,以及如何实现良好的吞吐量。一个好的设计可以轻松地带来一个数量级的差异,而这反过来又可能决定了你是否能够训练一个网络(例如,在一周内完成)还是完全无法完成(在3个月内,从而错过截止日期)。我们将首先看看计算机。然后我们将深入研究CPU和GPU。最后,我们将视角拉远,回顾多台计算机如何在服务器中心或云中连接。

图 13.4.1 每个程序员都应该知道的延迟数。¶
没有耐心的读者可以参考 图 13.4.1。它取自 Colin Scott 的交互式博文,该文很好地概述了过去十年的进展。最初的数字来自 Jeff Dean 在 2010 年斯坦福大学的演讲。下面的讨论解释了这些数字背后的一些原理,以及它们如何指导我们设计算法。下面的讨论是高度概括和粗略的。它显然不能替代一门正式的课程,而只是为了给统计建模者提供足够的信息以做出合适的设计决策。要深入了解计算机体系结构,我们建议读者参考 (Hennessy and Patterson, 2011) 或最近的相关课程,例如 Arste Asanovic 的课程。
13.4.1. 计算机¶
大多数深度学习研究人员和从业者都拥有一台配备了相当数量的内存、计算资源、某种形式的加速器(如GPU)或多个加速器的计算机。计算机由以下关键组件组成:
一个处理器(也称为CPU),能够执行我们给定的程序(除了运行操作系统和许多其他事情),通常由8个或更多核心组成。
内存(RAM)用于存储和检索计算结果,如权重向量和激活值,以及训练数据。
一个以太网网络连接(有时是多个),速度从 1 GB/s 到 100 GB/s 不等。在高端服务器上可以找到更高级的互连方式。
一个高速扩展总线(PCIe),用于将系统连接到一个或多个GPU。服务器最多有8个加速器,通常以高级拓扑结构连接,而桌面系统则有1或2个,具体取决于用户的预算和电源的大小。
持久存储设备,如机械硬盘(HDD)或固态硬盘(SSD),在许多情况下使用PCIe总线连接。它为系统提供高效的训练数据传输,并根据需要存储中间检查点。
图 13.4.2 计算机组件的连接。¶
如 图 13.4.2 所示,大多数组件(网络、GPU 和存储)都通过 PCIe 总线连接到 CPU。它由多条直接连接到 CPU 的通道组成。例如,AMD 的 Threadripper 3 有 64 条 PCIe 4.0 通道,每条通道都能双向传输 16 Gbit/s 的数据。内存直接连接到 CPU,总带宽高达 100 GB/s。
当我们在计算机上运行代码时,我们需要将数据传输到处理器(CPU 或 GPU),执行计算,然后将结果从处理器移回内存和持久存储。因此,为了获得良好的性能,我们需要确保这个过程无缝进行,没有任何一个系统成为主要瓶颈。例如,如果我们无法足够快地加载图像,处理器将无事可做。同样,如果我们无法足够快地将矩阵移动到 CPU(或 GPU),其处理单元将处于空闲状态。最后,如果我们要通过网络同步多台计算机,网络不应该减慢计算速度。一种选择是将通信和计算交错进行。让我们更详细地看看各个组件。
13.4.2. 内存¶
最基本的功能,内存用于存储需要随时访问的数据。目前 CPU RAM 通常是 DDR4 类型,每个模块提供 20-25 GB/s 的带宽。每个模块都有一个 64 位宽的总线。通常成对使用内存模块以支持多个通道。CPU 拥有 2 到 4 个内存通道,这意味着它们的峰值内存带宽在 40 GB/s 到 100 GB/s 之间。每个通道通常有两个内存库(bank)。例如,AMD 的 Zen 3 Threadripper 有 8 个插槽。
虽然这些数字确实令人印象深刻,但它们只讲述了故事的一部分。当我们想从内存中读取一部分数据时,我们首先需要告诉内存模块信息在哪里。也就是说,我们首先需要将*地址*发送到 RAM。完成此操作后,我们可以选择只读取一个 64 位记录,或一长串记录。后者称为*突发读取*。简而言之,向内存发送地址并设置传输大约需要 100 ns(具体细节取决于所用内存芯片的时序系数),而之后的每次传输仅需 0.2 ns。总之,第一次读取比后续读取贵 500 倍!请注意,我们每秒最多可以执行 10,000,000 次随机读取。这表明我们应尽可能避免随机内存访问,而应使用突发读取(和写入)。
考虑到我们有多个*内存库(banks)*,事情会变得更复杂一些。每个内存库基本上可以独立地读取内存。这意味着两件事。一方面,有效随机读取次数最多可提高 4 倍,前提是它们均匀分布在内存中。这也意味着进行随机读取仍然是一个坏主意,因为突发读取也快 4 倍。另一方面,由于内存按 64 位边界对齐,因此将任何数据结构与相同的边界对齐是个好主意。当设置适当的标志时,编译器几乎自动地完成这项工作。有兴趣的读者可以查看关于 DRAM 的讲座,例如 Zeshan Chishti 的讲座。
GPU 内存面临更高的带宽要求,因为它们拥有比 CPU 多得多的处理单元。总的来说,有两种方法来满足这些要求。第一种是显著加宽内存总线。例如,NVIDIA 的 RTX 2080 Ti 有一个 352 位宽的总线。这允许在同一时间传输更多的信息。第二种,GPU 使用特定的高性能内存。消费级设备,如 NVIDIA 的 RTX 和 Titan 系列,通常使用 GDDR6 芯片,总带宽超过 500 GB/s。另一种选择是使用 HBM(高带宽内存)模块。它们使用非常不同的接口,并直接在专用的硅晶片上与 GPU 连接。这使得它们非常昂贵,其使用通常仅限于高端服务器芯片,如 NVIDIA Volta V100 系列加速器。毫不奇怪,由于成本更高,GPU 内存通常比 CPU 内存小得多。就我们的目的而言,它们的性能特性大体相似,只是速度快得多。在本书中,我们可以安全地忽略这些细节。只有在为高吞吐量调整 GPU 内核时,这些细节才重要。
13.4.3. 存储¶
我们看到 RAM 的一些关键特性是*带宽*和*延迟*。存储设备也是如此,只是差异可能更加极端。
13.4.3.1. 机械硬盘¶
*机械硬盘*(HDD)已经使用了半个多世纪。简而言之,它们包含许多旋转的盘片,以及可以定位以在任何给定磁道上读取或写入的磁头。高端磁盘在 9 个盘片上最多可存储 16 TB。HDD 的一个主要好处是它们相对便宜。它们的许多缺点之一是其通常是灾难性的故障模式和相对较高的读取延迟。
要理解后者,请考虑 HDD 大约以 7200 RPM(每分钟转数)旋转的事实。如果它们转速快得多,它们会因盘片上的离心力而破碎。这在访问磁盘上特定扇区时有一个主要缺点:我们需要等到盘片旋转到位(我们可以移动磁头,但不能加速实际的磁盘)。因此,可能需要超过 8 毫秒才能获得请求的数据。一个常见的表达方式是,HDD 大约能以 100 IOPS(每秒输入/输出操作数)运行。这个数字在过去二十年里基本没有变化。更糟糕的是,增加带宽同样困难(大约在 100-200 MB/s 的数量级)。毕竟,每个磁头读取一个比特轨道,因此比特率只与信息密度的平方根成比例。因此,HDD 正在迅速被降级为存档存储和用于非常大型数据集的低级存储。
13.4.3.2. 固态硬盘¶
固态硬盘(SSD)使用闪存来持久存储信息。这使得访问存储的记录速度*快得多*。现代 SSD 可以达到 100,000 到 500,000 IOPS,即比 HDD 快 3 个数量级。此外,它们的带宽可以达到 1-3 GB/s,即比 HDD 快一个数量级。这些改进听起来好得令人难以置信。事实上,由于 SSD 的设计方式,它们也存在以下注意事项:
SSD 以块(256 KB 或更大)为单位存储信息。它们只能整体写入,这需要相当长的时间。因此,在 SSD 上进行按位的随机写入性能非常差。同样,通常写入数据需要很长时间,因为必须读取、擦除块,然后再用新信息重写。现在,SSD 控制器和固件已经开发出算法来缓解这个问题。尽管如此,写入速度可能要慢得多,特别是在 QLC(四层单元)SSD 上。提高性能的关键是维护一个操作*队列*,优先进行读取,并尽可能以大块写入。
SSD 中的内存单元磨损相对较快(通常在几千次写入后)。磨损均衡保护算法能够将退化分散到许多单元上。尽管如此,不建议使用 SSD 作为交换文件或用于大型日志文件的聚合。
最后,带宽的大幅增加迫使计算机设计者将 SSD 直接连接到 PCIe 总线。能够处理此功能的驱动器,称为 NVMe(非易失性内存增强),最多可以使用 4 条 PCIe 通道。这在 PCIe 4.0 上相当于高达 8 GB/s。
13.4.3.3. 云存储¶
云存储提供可配置的性能范围。也就是说,分配给虚拟机的存储是动态的,无论是在数量上还是在用户选择的速度上。我们建议用户在延迟过高时,例如在用许多小记录进行训练时,增加配置的 IOPS 数量。
13.4.4. CPU¶
中央处理单元(CPU)是任何计算机的核心。它们由几个关键组件组成:能够执行机器代码的*处理器核心*,连接它们的*总线*(具体拓扑在处理器型号、代次和供应商之间差异很大),以及*缓存*,以实现比从主内存读取更高的带宽和更低的延迟内存访问。最后,几乎所有现代CPU都包含*向量处理单元*,以辅助高性能线性代数和卷积,这在媒体处理和机器学习中很常见。
图 13.4.3 英特尔 Skylake 消费级四核 CPU。¶
图 13.4.3 描绘了一款英特尔Skylake消费级四核CPU。它有一个集成的GPU、缓存和一个连接四个核心的环形总线。外围设备,如以太网、WiFi、蓝牙、SSD控制器和USB,要么是芯片组的一部分,要么直接(通过PCIe)连接到CPU。
13.4.4.1. 微架构¶
每个处理器核心都由一套相当复杂的组件组成。虽然细节因代次和供应商而异,但基本功能几乎是标准的。前端加载指令并试图预测将采取哪个路径(例如,对于控制流)。然后,指令从汇编代码解码为微指令。汇编代码通常不是处理器执行的最低级代码。相反,复杂的指令可能会被解码为一组更低级的操作。然后由实际的执行核心处理这些操作。通常后者能够同时执行许多操作。例如,图 13.4.4 中的ARM Cortex A77核心能够同时执行多达8个操作。
图 13.4.4 ARM Cortex A77 微架构。¶
这意味着高效的程序可能每个时钟周期执行超过一个指令,前提是这些指令可以独立执行。并非所有单元都生而平等。一些专门用于整数指令,而另一些则为浮点性能进行了优化。为了提高吞吐量,处理器还可能在分支指令中同时跟踪多个代码路径,然后丢弃未被采纳的分支的结果。这就是为什么分支预测单元(在前端)很重要的原因,这样只有最有希望的路径才会被执行。
13.4.4.2. 向量化¶
深度学习对计算的需求极高。因此,为了使CPU适用于机器学习,需要在单个时钟周期内执行许多操作。这通过向量单元实现。它们有不同的名称:在ARM上它们被称为NEON,在x86上(一个近期的一代)它们被称为AVX2单元。一个共同的方面是它们能够执行SIMD(单指令多数据)操作。图 13.4.5展示了如何在ARM上一个时钟周期内将8个短整数相加。
图 13.4.5 128位 NEON 向量化。¶
根据架构选择,这样的寄存器最长可达 512 位,允许组合多达 64 对数字。例如,我们可能将两个数相乘,然后将结果加到第三个数上,这也被称为融合乘加。英特尔的 OpenVino 利用这些单元在服务器级 CPU 上为深度学习实现了可观的吞吐量。但请注意,这个数字完全被 GPU 的能力所掩盖。例如,NVIDIA 的 RTX 2080 Ti 拥有 4352 个 CUDA 核心,每个核心在任何时候都能够处理这样的操作。
13.4.4.3. 缓存¶
考虑以下情况:我们有一个如 图 13.4.3 所示的普通四核 CPU,运行频率为 2 GHz。此外,假设我们的 IPC(每时钟周期指令数)为 1,并且这些单元启用了 256 位宽度的 AVX2。再进一步假设,用于 AVX2 操作的寄存器中至少有一个需要从内存中检索。这意味着 CPU 每时钟周期消耗 \(4 \times 256 \textrm{ bit} = 128 \textrm{ bytes}\) 的数据。除非我们能够每秒向处理器传输 \(2 \times 10^9 \times 128 = 256 \times 10^9\) 字节,否则处理单元将会“饿死”。不幸的是,这种芯片的内存接口只支持 20-40 GB/s 的数据传输,即少了一个数量级。解决方法是尽可能避免从内存中加载*新*数据,而是将其本地缓存在 CPU 上。这就是缓存派上用场的地方。通常使用以下名称或概念:
寄存器严格来说不属于缓存。它们帮助暂存指令。也就是说,CPU寄存器是CPU可以以时钟速度访问而没有任何延迟惩罚的内存位置。CPU有几十个寄存器。高效地使用寄存器取决于编译器(或程序员)。例如,C编程语言有一个
register
关键字。L1 缓存是对抗高内存带宽要求的第一道防线。L1 缓存很小(典型大小可能是 32-64 KB),并且通常分为数据缓存和指令缓存。当在 L1 缓存中找到数据时,访问速度非常快。如果在那里找不到,搜索将沿着缓存层次结构向下进行。
L2 缓存是下一站。根据架构设计和处理器大小,它们可能是独占的。它们可能只能由给定的核心访问,或者在多个核心之间共享。L2 缓存比 L1 更大(通常每个核心 256-512 KB)也更慢。此外,要访问 L2 中的内容,我们首先需要检查并意识到数据不在 L1 中,这会增加少量额外的延迟。
L3 缓存在多个核心之间共享,并且可以非常大。AMD 的 Epyc 3 服务器 CPU 拥有高达 256 MB 的缓存,分布在多个芯片上。更典型的数字在 4-8 MB 范围内。
预测接下来需要哪些内存元素是芯片设计的关键优化参数之一。例如,建议以*向前*的方向遍历内存,因为大多数缓存算法会尝试*预读*而不是向后读。同样,保持内存访问模式的局部性是提高性能的好方法。
添加缓存是一把双刃剑。一方面,它们确保处理器核心不会因数据不足而停顿。但同时,它们也增加了芯片尺寸,占用了本可以用于增加处理能力的区域。此外,*缓存未命中*的代价可能很高。考虑最坏的情况,即*伪共享*,如 图 13.4.6 所示。当处理器1上的一个线程请求数据时,一个内存位置被缓存在处理器0上。为了获取它,处理器0需要停止正在做的事情,将信息写回主内存,然后让处理器1从内存中读取它。在此操作期间,两个处理器都在等待。很有可能,这样的代码在多处理器上运行得比高效的单处理器实现*更慢*。这是为什么缓存大小有实际限制的另一个原因(除了它们的物理尺寸)。
图 13.4.6 伪共享(图片由英特尔提供)。¶
13.4.5. GPU 和其他加速器¶
毫不夸张地说,没有GPU,深度学习就不会成功。同样,有理由认为GPU制造商的财富因深度学习而显著增加。硬件和算法的这种共同进化导致了一种情况,即无论好坏,深度学习都是首选的统计建模范式。因此,了解GPU和相关加速器(如TPU)的特定优势是值得的 (Jouppi et al., 2017)。
值得注意的是,在实践中经常会做一个区分:加速器要么针对训练进行优化,要么针对推理进行优化。对于后者,我们只需要计算网络中的前向传播。不需要为反向传播存储中间数据。此外,我们可能不需要非常精确的计算(FP16或INT8通常就足够了)。另一方面,在训练期间,所有中间结果都需要存储以计算梯度。此外,累积梯度需要更高的精度以避免数值下溢(或上溢)。这意味着FP16(或与FP32混合精度)是最低要求。所有这些都需要更快、更大的内存(HBM2 vs. GDDR6)和更强的处理能力。例如,NVIDIA的Turing T4 GPU针对推理进行了优化,而V100 GPU更适合训练。
回想一下 图 13.4.5 中所示的向量化。向处理器核心添加向量单元使我们能够显著提高吞吐量。例如,在 图 13.4.5 的示例中,我们能够同时执行 16 个操作。首先,如果我们添加的操作不仅优化了向量之间的操作,还优化了矩阵之间的操作呢?这个策略导致了张量核心(稍后将介绍)。其次,如果我们添加更多的核心呢?简而言之,这两个策略概括了 GPU 的设计决策。图 13.4.7 概述了一个基本的处理块。它包含 16 个整数单元和 16 个浮点单元。此外,两个张量核心加速了与深度学习相关的一小部分额外操作。每个流式多处理器由四个这样的块组成。

图 13.4.7 NVIDIA Turing 处理块(图片由 NVIDIA 提供)。¶
接下来,12个流式多处理器被分组到图形处理集群中,构成了高端TU102处理器。充足的内存通道和L2缓存完善了整个配置。图 13.4.8 提供了相关细节。设计这种设备的原因之一是,可以根据需要添加或移除单个块,以允许更紧凑的芯片并处理良率问题(有缺陷的模块可能不会被激活)。幸运的是,对于普通的深度学习研究者来说,对这类设备的编程被很好地隐藏在CUDA和框架代码的层次之下。特别是,只要有可用资源,GPU上很可能可以同时执行多个程序。尽管如此,了解设备的局限性是值得的,以避免选择不适合设备内存的模型。

图 13.4.8 NVIDIA Turing 架构(图片由 NVIDIA 提供)¶
最后一点值得更详细地提及的是*张量核心*。它们是最近趋势的一个例子,即添加更多专门为深度学习优化的电路。例如,TPU增加了一个用于快速矩阵乘法的脉动阵列(Kung, 1988)。那里的设计是为了支持极少数(第一代TPU只有一个)的大型操作。张量核心则处于另一端。它们针对涉及\(4 \times 4\)到\(16 \times 16\)矩阵之间的小型操作进行了优化,具体取决于其数值精度。图 13.4.9概述了这些优化。

图 13.4.9 NVIDIA Turing 中的张量核心(图片由 NVIDIA 提供)。¶
显然,当优化计算时,我们最终会做出某些妥协。其中之一是GPU在处理中断和稀疏数据方面表现不佳。虽然有一些显著的例外,例如Gunrock (Wang et al., 2016),但稀疏矩阵和向量的访问模式与GPU擅长的高带宽突发读取操作并不匹配。匹配这两个目标是一个活跃的研究领域。例如,请参见DGL,一个为图上的深度学习优化的库。
13.4.6. 网络和总线¶
当单个设备不足以进行优化时,我们需要向其传输数据或从其接收数据以同步处理。这就是网络和总线派上用场的地方。我们有许多设计参数:带宽、成本、距离和灵活性。一端是WiFi,它的覆盖范围相当好,使用非常方便(毕竟没有线缆),价格便宜,但提供的带宽和延迟相对平庸。没有一个头脑清醒的机器学习研究者会用它来构建服务器集群。接下来,我们重点关注适合深度学习的互连技术。
PCIe 是一种专用于非常高带宽点对点连接的总线(在16通道插槽的PCIe 4.0上高达32 GB/s)。延迟在个位数微秒级别(5 μs)。PCIe链接非常宝贵。处理器拥有的数量有限:AMD的EPYC 3有128条通道,英特尔的Xeon每个芯片最多48条通道;在桌面级CPU上,数量分别为20条(Ryzen 9)和16条(Core i9)。由于GPU通常有16条通道,这限制了能以全带宽连接到CPU的GPU数量。毕竟,它们需要与其他高带宽外设(如存储和以太网)共享链接。与RAM访问一样,由于减少了数据包开销,大型批量传输更受青睐。
以太网是连接计算机最常用的方式。虽然它比PCIe慢得多,但它安装非常便宜且有弹性,并且覆盖的距离更长。低端服务器的典型带宽是1 GBit/s。高端设备(例如云中的C5实例)提供10到100 GBit/s的带宽。与所有先前的情况一样,数据传输有显著的开销。请注意,我们几乎从不直接使用原始以太网,而是使用在物理互连之上执行的协议(如UDP或TCP/IP)。这增加了额外的开销。像PCIe一样,以太网设计用于连接两个设备,例如一台计算机和一个交换机。
交换机允许我们连接多个设备,使得任何一对设备都可以同时进行(通常是全带宽的)点对点连接。例如,以太网交换机可以以高横截面带宽连接40台服务器。请注意,交换机并不仅仅用于传统计算机网络。即使是PCIe通道也可以被交换。例如,这发生在将大量GPU连接到主机处理器的情况下,就像P2实例一样。
NVLink 是PCIe在极高带宽互连方面的一种替代方案。它每个链接提供高达300 Gbit/s的数据传输速率。服务器GPU(Volta V100)有六个链接,而消费级GPU(RTX 2080 Ti)只有一个链接,以降低的100 Gbit/s速率运行。我们建议使用NCCL来实现GPU之间的高效数据传输。
13.4.7. 更多延迟数据¶
表 13.4.1 和 表 13.4.2 中的摘要来自 Eliot Eshelman,他以 GitHub gist 的形式维护着这些数字的更新版本。
操作 |
时间 |
备注 |
---|---|---|
L1缓存引用/命中 |
1.5 ns |
4个周期 |
浮点加法/乘法/FMA |
1.5 ns |
4个周期 |
L2缓存引用/命中 |
5 ns |
12 ~ 17个周期 |
分支预测失误 |
6 ns |
15 ~ 20个周期 |
L3缓存命中(非共享缓存) |
16 ns |
42个周期 |
L3缓存命中(在另一个核心中共享) |
25 ns |
65个周期 |
互斥锁/解锁 |
25 ns |
|
L3缓存命中(在另一个核心中修改) |
29 ns |
75个周期 |
L3缓存命中(在远程CPU插槽上) |
40 ns |
100 ~ 300个周期 (40 ~ 116 ns) |
QPI跳到另一个CPU(每跳) |
40 ns |
|
64MB内存引用(本地CPU) |
46 ns |
TinyMemBench on Broadwell E5-2690v4 |
64MB内存引用(远程CPU) |
70 ns |
TinyMemBench on Broadwell E5-2690v4 |
256MB内存引用(本地CPU) |
75 ns |
TinyMemBench on Broadwell E5-2690v4 |
英特尔傲腾随机写入 |
94 ns |
UCSD非易失性系统实验室 |
256MB内存引用(远程CPU) |
120 ns |
TinyMemBench on Broadwell E5-2690v4 |
英特尔傲腾随机读取 |
305 ns |
UCSD非易失性系统实验室 |
通过100 Gbps HPC网络发送4KB |
1 μs |
MVAPICH2 over Intel Omni-Path |
使用Google Snappy压缩1KB |
3 μs |
|
通过10 Gbps以太网发送4KB |
10 μs |
|
向NVMe SSD随机写入4KB |
30 μs |
DC P3608 NVMe SSD (QOS 99% 为 500μs) |
向/从NVLink GPU传输1MB |
30 μs |
~33GB/s on NVIDIA 40GB NVLink |
向/从PCI-E GPU传输1MB |
80 μs |
~12GB/s on PCIe 3.0 x16 link |
从NVMe SSD随机读取4KB |
120 μs |
DC P3608 NVMe SSD (QOS 99%) |
从NVMe SSD顺序读取1MB |
208 μs |
~4.8GB/s DC P3608 NVMe SSD |
向SATA SSD随机写入4KB |
500 μs |
DC S3510 SATA SSD (QOS 99.9%) |
从SATA SSD随机读取4KB |
500 μs |
DC S3510 SATA SSD (QOS 99.9%) |
同一数据中心内的往返 |
500 μs |
单向 ping 约为 250μs |
从SATA SSD顺序读取1MB |
2 ms |
~550MB/s DC S3510 SATA SSD |
从磁盘顺序读取1MB |
5 ms |
~200MB/s 服务器 HDD |
随机磁盘访问(寻道+旋转) |
10 ms |
|
发送数据包 CA->荷兰->CA |
150 ms |
操作 |
时间 |
备注 |
---|---|---|
GPU共享内存访问 |
30 ns |
30~90个周期(bank冲突增加延迟) |
GPU全局内存访问 |
200 ns |
200~800个周期 |
在GPU上启动CUDA内核 |
10 μs |
主机CPU指示GPU启动内核 |
向/从NVLink GPU传输1MB |
30 μs |
~33GB/s on NVIDIA 40GB NVLink |
向/从PCI-E GPU传输1MB |
80 μs |
~12GB/s on PCI-Express x16 link |
13.4.8. 小结¶
设备的操作有开销。因此,目标应该是进行少量大传输而不是多次小传输。这适用于RAM、SSD、网络和GPU。
向量化是性能的关键。请确保了解您的加速器的特定能力。例如,一些英特尔至强CPU特别擅长INT8操作,NVIDIA Volta GPU在FP16矩阵-矩阵运算方面表现出色,而NVIDIA Turing在FP16、INT8和INT4操作方面表现优异。
由于小数据类型导致的数值溢出在训练期间(以及在较小程度上在推理期间)可能是一个问题。
混叠会显著降低性能。例如,在64位CPU上,内存对齐应相对于64位边界进行。在GPU上,将卷积大小对齐(例如,与张量核心对齐)是一个好主意。
将您的算法与硬件相匹配(例如,内存占用和带宽)。当参数能放入缓存时,可以实现巨大的加速(数量级)。
我们建议您在验证实验结果之前,在纸上大致估算新算法的性能。一个数量级或更大的差异是值得关注的原因。
使用性能分析器来调试性能瓶颈。
训练和推理硬件在价格和性能方面有不同的最佳点。
13.4.9. 练习¶
编写 C 代码测试相对于外部内存接口,内存对齐访问和不对齐访问之间是否存在速度差异。提示:注意缓存效应。
测试顺序访问内存与按给定步长访问内存之间的速度差异。
你如何测量CPU上的缓存大小?
为了获得最大带宽,你会如何在多个内存通道上布局数据?如果你有许多小线程,你会如何布局?
假设一个企业级 HDD 以 10,000 rpm 的速度旋转。在最坏的情况下,HDD 在读取数据之前需要花费的最短时间是多少(你可以假设磁头移动几乎是瞬时的)?为什么 2.5 英寸 HDD 在商用服务器中变得流行(相对于 3.5 英寸和 5.25 英寸驱动器)?
假设一个HDD制造商将存储密度从每平方英寸1 Tbit增加到每平方英寸5 Tbit。你可以在一个2.5英寸HDD的一个环上存储多少信息?内外磁道之间有区别吗?
从8位数据类型升级到16位数据类型,所需硅片面积大约增加四倍。为什么?为什么NVIDIA可能在其Turing GPU中增加了INT4操作?
向前读取内存比向后读取快多少?这个数字在不同的计算机和CPU供应商之间是否有所不同?为什么?编写C代码并进行实验。
你能测量你的磁盘缓存大小吗?典型的HDD缓存是多大?SSD需要缓存吗?
测量通过以太网发送消息时的数据包开销。查找UDP和TCP/IP连接之间的区别。
直接内存访问(DMA)允许除CPU以外的设备直接向内存写入(和从中读取)。为什么这是个好主意?
查看Turing T4 GPU的性能数据。为什么从FP16到INT8和INT4时,性能“仅仅”翻倍?
一个数据包在旧金山和阿姆斯特丹之间往返的最短时间应该是多少?提示:你可以假设距离为10,000公里。