专栏名称: GiantPandaCV
专注于机器学习、深度学习、计算机视觉、图像处理等多个方向技术分享。团队由一群热爱技术且热衷于分享的小伙伴组成。我们坚持原创,每天一到两篇原创技术分享。希望在传播知识、分享知识的同时能够启发你,大家一起共同进步(・ω<)☆
目录
相关文章推荐
GiantPandaCV  ·  基于o1-preview解读 ... ·  3 天前  
GiantPandaCV  ·  【翻译】在 GPU 上如何加速 GPTQ ... ·  4 天前  
GiantPandaCV  ·  加速矩阵计算:英伟达TensorCore架构 ... ·  6 天前  
GiantPandaCV  ·  CUDA-MODE课程笔记 ... ·  1 周前  
GiantPandaCV  ·  使用Nsight ... ·  1 周前  
51好读  ›  专栏  ›  GiantPandaCV

通过微基准测试和指令级分析(Instruction-level Analysis)揭秘英伟达Ampere架构

GiantPandaCV  · 公众号  · 3D  · 2024-08-17 22:22

正文

通过微基准测试(Microbenchmarking)和指令级分析(Instruction-level Analysis)揭秘英伟达Ampere架构

这是CUDA-MODE课程笔记 第8课: CUDA性能检查清单里面提到的2篇Paper的其中一篇,讲的是通过微基准测试(Microbenchmarking)和指令级分析(Instruction-level Analysis)揭秘英伟达Ampere架构,这里做一个解读做为CUDA-MODE第8课的知识补充。

题目&作者

  • 论文链接:https://arxiv.org/pdf/2208.11174

论文题目为"通过微基准测试(Microbenchmarking)和指令级分析(Instruction-level Analysis)揭秘英伟达Ampere架构"。下面列出了4位作者的相关信息,他们分别来自新墨西哥州立大学(New Mexico State University)和洛斯阿拉莫斯国家实验室(Los Alamos National Laboratory)。

这篇paper里面做micro benchmark的代码也开源了一部分:https://www.stuffedcow.net/research/cudabmk?q=research/cudabmk 和 WMMA 指令相关的 micro benchmark 测试代码没有公开。此外,这里的代码似乎使用了比较早的cuda版本,有一些API和新版本的cuda不太兼容。

摘要

GPU已经成为加速AI、数据分析、HPC等通用目的工作负载的主要硬件。近十年来,研究人员一直致力于揭秘和评估各种GPU架构的微架构特性,这对于更好地理解硬件并构建更高效的工作负载和应用非常必要。许多工作已经研究了最近的英伟达架构,如Volta和Turing,并与它们的前代Ampere进行了比较。然而,Ampere架构的一些微架构特性,如不同指令的时钟周期,还没有被广泛研究。Paper使用微基准测试研究了在英伟达GPU的指令集架构(ISA)中发现的各种数据类型的每条指令(PTX ISA指令)的时钟周期,以及它们的SASS ISA指令对应物。作者进一步计算了访问每个内存单元所需的时钟周期。作者还使用WMMA API揭示了Ampere架构中新的Tensor Core, 并测量了不同数据类型和输入形状的每条指令的时钟周期和吞吐量。研究的结果应该为软件开发人员和硬件架构师提供指导。此外,这里的每条指令的时钟周期被性能建模模拟器和工具广泛使用,以模拟和预测硬件的性能。

介绍

GPU已经成为加速各种通用应用的主要硬件,从神经网络到科学计算。基于英伟达Ampere架构的超级计算机已经成为世界上最强大的超级计算机。

英伟达每两年提供一次新的架构, 但几乎没有微架构特性的信息,这使得很难量化这些特性的效果。这引发了研究人员研究新特性对应用性能影响的需求。英伟达推出了Tensor Core(TC)单元来加速深度神经网络, 它是Volta版本开始引入的。该TC版本在FP16和FP32精度操作上运行。Ampere架构增加了TC的稀疏特性和Int8、Int4、FP64、bf16和TF32等新的数据精度。通常,除了供应商在白皮书中选择披露的信息外,很少有关于这些特性的信息。因此,研究人员试图解释和揭示每一代GPU的新特性。然而,有些领域仍未在文献中得到全面覆盖。在这项工作中,我们专注于揭示指令集架构(ISA)中指令粒度的时钟周期延迟。之前已经有类似的工作被提出。例如,作者在[9]中改编了一些微基准来揭示一些硬件单元如内存、Tensor Core和算术单元的延迟。然而,他们只计算了整个warp和block的指令的内存延迟,而不是单个指令的延迟。在另一项工作[10]中,作者计算了各种Nvidia架构的仅内存层次结构的延迟。

Paper提供了对Nvidia Ampere GPU架构的微基准分析[11]。Paper中提供的微基准是基于并行线程执行(PTX)[12]的。PTX是高级语言(CUDA)和汇编语言(SASS)之间的中间表示。因此,它可在不同架构之间移植。PTX是开源的,有据可查的。然而,它的指令不直接在硬件上执行。相反,它必须转换为特定于架构的ISA--SASS。在这种情况下,SASS是封闭源代码的,只与每个架构系列兼容。Paper展示了如何将每条PTX指令映射到SASS指令,同时测量两种ISA的时钟周期。此外,我们还展示了访问每个内存单元所需的时钟周期。这些微基准测试是基于Arafa等人之前的工作[13],该工作计算了不同Nvidia架构上各种指令的时钟周期延迟。然而,Ampere架构还没有这样的研究。我们还展示了在不同数据类型上Tensor Core指令的时钟周期和吞吐量。

[18]表明,通过为GPU指令采用正确的延迟,他们的性能模型可以提高其预测准确性,与实际硬件相比。此外,Andersch等人[10]证明了延迟与性能之间的关键关系。这项工作是准确建模Ampere GPU架构的第一步。

背景

与多核CPU不同,GPU由数十个简单的处理器组成,可以同时执行许多简单的操作。这对于需要大量并行工作的应用非常有用,如人工智能和科学计算等。

英伟达GPU由数十个SM(Streaming Multiprocessors)组成。不同GPU架构(如Kepler、Volta和Ampere)的SM数量各不相同,新架构的SM数量更多(如Ampere有124个SM)。SM内部包含不同的计算资源和缓存层次结构。GPU有不同类型的内存单元,全局内存和L2缓存与所有SM共享。此外,每个SM有自己的专用L1缓存,可以通过共享内存进行通信。

在许多领域,GPU面临着提供更好性能的压力。英伟达在过去几年里提供了新的GPU架构。新架构不仅有新的硬件单元,而且可能包含新的ISA,从而提高性能。例如,在过去两年中,英伟达在Tensor Core方面进行了显著改进,使其在更大的矩阵上运行得更快。此外,它还引入了新的L2 Cache驻留控制功能,可以自动管理数据保存到L2 Cache。

尽管这些特性在白皮书和在线评论网站上有详细记录,但在微架构和指令集增强方面,几乎没有关于Ampere架构的信息。因此,本文通过提供Ampere GPU指令集架构(ISA)的详细指令级表征来填补这一空白。

相关工作

这里就最后几句话需要写一下,Paper声明他们的工作是首次研究每个WMMA Tensor Core指令的时钟周期延迟,并且他们的工作可以轻松扩展到未来的架构。

方法

在本节中,我们介绍微基准测试设计的细节。我们的工作基于扩展[13]中提出的微基准测试,以计算Nvidia Ampere (A100) GPU的每条指令的时钟周期。我们修改了微基准测试,以计算相关和无关指令的延迟。此外,我们扩展了代码,以计算不同类型内存单元和Tensor Core指令的时钟周期延迟。

微基准测试直接用PTX编写,PTX是一种伪汇编中间语言,也是适用于所有Nvidia架构的独立指令集架构(ISA)。然而,直接用PTX ISA编写可能会很棘手,因为编译器会将PTX代码翻译成另一种架构相关的ISA,即SASS。关于编译器如何从PTX映射到SASS的信息不多。例如,编译器可以将多个PTX指令优化为一条SASS指令。为了克服这些限制并确保执行的指令是我们所需要的,我们在每个PTX微基准测试运行时动态读取SASS指令Trace。我们使用PPT-GPU[17]中的Tracing Tool来实现这一点。然后,我们通过反复试验来调整PTX微基准测试,以得到正确的SASS结果。

A. 指令时钟周期延迟

我们每个块只使用一个线程来测量指令延迟。我们有两个步骤。首先,我们运行一段代码,计算所研究的指定数据类型指令的时钟周期。例如,图1中的代码计算了操作数为32位寄存器的add指令的延迟。通常,测量延迟可以通过在指令执行前后读取时钟来完成,如图1的第13行和第17行所示。然后,我们将两次时钟读数相减(第18行)来计算差值或所需的延迟。我们执行三个独立的add指令(第14-16行)。我们还使用了相关指令,发现相比独立指令,延迟增加了。最后,我们将延迟值返回到主CUDA函数,并除以3来计算每条指令的周期数。我们使用3条指令来克服首次启动开销。我们发现只执行一条指令会导致意外的高周期数。表1显示了add.u32指令的示例。第一条指令大约需要5个周期。然而,当我们使用超过3条指令时,每条指令的平均周期数(CPI)为2。

第二,我们使用PPT-GPU [18]的动态Tracing Tool检查sass指令,以确保从PTX到SASS的映射是正确的,且编译器在运行时没有添加额外的开销或指令。图4(a)中显示的PTX代码在将时钟存储到32位寄存器时,为add指令提供了不准确的延迟。动态SASS指令跟踪显示在两次时钟读取之间有一个Barrier,如SASS部分的第二条指令所示。这个Barrier导致结果发生显著变化(在这种情况下增加了约33个周期)。克服这个Barrier的一种方法是使用64位寄存器来存储时钟,这样可以消除Barrier并提供准确的测量,如图4(b)所示。第一种和第二种情况的CPI分别为13和2个周期。最后,我们使用两个连续的时钟读取指令计算时钟开销,发现它等于2个周期。

这里的测试代码实现对应:https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/cuda-mode/cudabmk/clock.cu ,这个地方的代码比较简单这里就不分析了。

B. 内存单元访问延迟

为了计算全局内存、L2缓存和L1缓存的延迟,我们使用指针追逐技术,其中每个数组元素依赖于前一个元素。这种技术强制读取操作串行化,以正确计算延迟。否则,许多读取操作可能会同时发出,这会使延迟测量不准确。图2显示了用于内存延迟计算的PTX微基准测试。第1行将数组地址移动到%r19寄存器。然后,我们用%r40寄存器中的零值开始一个计数器。这个计数器用于遍历元素数组。第3行和第9-11行表示循环指令。第4到7行用于存储数组元素,其中每个元素依赖于前一个元素。存储结果后,我们使用第14到24行的指令来读取时钟,同时读取数组中的每个元素。从第16到19行,我们有4条加载指令来加载4个值,这些指令重复执行以读取所有数组元素。

ld指令可以与许多操作符一起使用,如cvcacg,每个操作符都有其用途。cg用于缓存所有可用级别(全局-L1-L2),而cg仅缓存L2。另一方面,我们使用cv因为它绕过缓存,这是我们计算全局内存延迟时所需要的。我们使用4条指令是因为我们发现在许多情况下,当我们检查一些Cuda应用程序的动态跟踪时,编译器会将循环展开4次。全局内存代码和L2缓存代码之间的区别在于ld指令使用的操作符以及数组中元素的数量。对于L2缓存,我们使用cg操作符,数组元素的总大小必须小于L2大小,而对于全局内存代码,它必须大于L2缓存以避免L2缓存驻留。同样,我们对L1缓存延迟重复相同的方法,使用ca操作符。

对于共享内存,我们在读取时钟之间发射load和save指令,如图3的第3-12行所示。然而,我们需要添加另一条依赖于ld或st指令的指令,以防止编译器在完成之前执行时钟读取指令,如第4-13行所示。

C. Tensor Core指令延迟和吞吐量

Tensor Core(TC)单元是加速机器学习应用的一个非常重要的单元。在Ampere架构中,每个流多处理器(SM)包含4个Tensor Core,可以对3个矩阵执行多重加法操作,形式为D=A*B+C,其中A、B是输入,C、D是累加器。与仅支持fp16精度输入的Volta不同,Ampere架构支持多种类型,如FP16bf16tf32f64u8u4和单比特。通用算术指令使用一个线程执行,只能通过全局或共享内存通信。另一方面,TC指令使用专用线程束中的所有32条指令。为了解密Ampere架构的TC指令及其新数据类型,我们设计了一个用Cuda编程语言编写的特殊微基准测试。这些微基准测试受到Jia等人[7]关于Volta架构研究的启发。

Ampere架构引入的一些新数据类型仍处于实验阶段,如PTX和CUDA文档[28]所述。此外,由于每种数据类型都有其特定的形状、步幅和布局,我们使用不同的函数来计算每种类型的延迟。图5展示了用于计算U8数据类型TC指令延迟的代码。第5至7行创建了fragment,其中寄存器被准备用于存储矩阵元素。我们创建4个fragment,但不全部写入以使形状更小。然后,我们从内存加载数据(第10-12行),其它fragment也是如此。如前所述,我们在TC WMMA执行前后读取时钟(第15至22行),并在打印前进行减法,如第28-29行所示。从第16到21行,我们多次运行4个TC指令(每个TC一个)。我们使用4条指令是因为我们发现用一个TC的一条指令计算延迟会导致不准确的测量。

例如,图6显示了在一个TC上运行一条指令的动态SASS指令。NOP指令指的是PTX中的线程束同步,我们发现这里的延迟与白皮书中提到的不同。当我们多次运行一条指令时也会出现这种情况。最后,我们计算每条指令的延迟并通过第28-29行打印出来。类似的方法用于计算TC的WMMA吞吐量。

结果

在本节中,我们呈现详细的设置和结果。首先,我们展示指令时钟周期延迟。接着,我们解释内存访问延迟。最后,我们展示Tensor Core延迟和吞吐量。我们在Nvidia Tesla A1100 GPU上运行了所有的微基准测试。

指令延迟

我们发现依赖关系直接影响指令时钟周期延迟。因此,我们使用依赖指令序列(如图1所示)重新运行微基准测试,并将依赖序列替换为另一个独立指令序列。表II显示了一些指令的依赖和独立序列的CPI(每指令周期数)。例如,单精度和双精度浮点指令分别显示4和2个周期。我们还发现,在没有依赖关系的情况下,图1中提到的3个add.u32指令被映射到同一个sass指令(IADD),如图4(a)所示。然而,当我们使用三个依赖指令时,PTX指令可能会被转换为不同的指令。例如,add.u32 PTX指令可能会被映射到IADD3或IMAD.IADD,具体取决于依赖关系。

表V描述了各种PTX-SASS指令及其测量的时钟周期延迟。我们为表中的每个字段都有一个单独的微基准测试。

指令的测试可以在 https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/cuda-mode/cudabmk/instructions.h & https://github.com/BBuf/how-to-optim-algorithm-in-cuda/blob/master/cuda-mode/cudabmk/pipeline.cu 这里看到。

接下来,我们讨论在生成结果时发现的一些额外见解:

1: mad指令在浮点流水线上运行,而不是整数流水线,即使它使用整数值。这可以通过以下方式证明:- 表V中的PTX指令mad.lo.u32被映射到SASS FFMA(浮点乘加)。- 我们创建了一个特殊代码,使用一个线程运行两个add指令和两个mad指令,发现总周期数约为4个周期。这意味着每个指令大约需要1个周期。这证明这两种类型在不同的流水线上同时执行。

显示mad指令使用另一个流水线解释了为什么在某些情况下,依赖的PTX add.u32指令被映射到SASS (IMAD.IADD)指令。编译器试图使用浮点流水线,同时等待整数流水线完成。

2: 除了bfindminmax指令外,使用有符号或无符号指令时,在时钟周期数或PTX到SASS的映射上没有区别。例如,add.u64和add.s64提供相同的映射和相同的延迟。

3: 通常,movadd指令用于在使用寄存器作为输入操作数之前,用一个值初始化该寄存器,这是我们需要计算其延迟的指令。然而,在某些情况下,我们发现时钟周期和PTX-SASS映射会根据输入的初始化方式而改变。例如,当我们使用add指令初始化输入时,PTX neg.f32被映射到SASS FADD。另一方面,当我们使用mov进行初始化时,它将movneg指令合并为一个SASS指令(IMAD.MOV.u32)。abs.f32指令也发生同样的情况。

4: 尽管许多PTX指令有1对1的映射到SASS指令,但其他如div、rem、sinfcosf被转换为多个不同的SASS指令。

5: 不是所有具有相同数据类型的指令都有相同的延迟。更具体地说,mad.lo.u64被映射到一个IMAD SASS指令,仅需2个周期。然而,双精度addsubfma指令每个都需要4个周期。

6: 对于testp指令,延迟取决于状态。

内存访问延迟

不同类型内存的观察到的延迟如表IV所示。全局内存延迟约为290个周期。这个值不包括缓存未命中的延迟,因为我们在所有级别上阻止了缓存。与Turing架构的434个周期相比,这个数字有所改善[13]。L2访问延迟为200个周期,而Turing架构为188个周期。此外,对于Ampere和Turing架构,L1缓存命中的延迟分别为33和32个周期。对于共享内存,我们发现存储访问延迟比加载指令的值更小,加载和存储分别为23和19个周期。

Tensor Core 延迟和吞吐

Ampere架构的指令集架构(ISA)提供了各种在Tensor Core上运行的SASS指令,支持新增的数据类型。Volta架构的ISA只有HMMA.884 SASS指令处理所有Tensor Core操作(单精度和混合精度操作)。对于Turing,存在两种HMMA SASS指令,适用于不同的输入形状,即HMMA.1688HMMA.884 [26]。

表III描述了Ampere架构的Tensor Core指令。具体来说,  DMMA.884IMMA.16816IMMA.8832被添加以分别处理FP64U8U4数据类型。每种数据类型的每条PTX指令被转换为不同数量的SASS指令。对于FP16BF16U8输入,PTX被转换为2条指令。TensorFloat-32 (TF32)精度被映射到4条SASS指令,而FP64和U4则只映射到1条指令。这些差异与支持的PTX形状和SASS可以处理的形状之间的差异有关。例如,在表III的第一行中,PTX指令可以使用多种形状如16x16x16,但SASS只能处理16x8x16。因此,需要2条SASS指令来迭代PTX形状。然而,物理Tensor Core实现可以执行848 [21]。虽然之前在[25]中提到Turing的Tensor Core延迟依赖于形状,但我们发现对于Ampere架构,同一数据类型的不同形状不影响计算的延迟。它可能在Ampere架构中因类型而异。

我们对表中显示的Tensor Core吞吐量和延迟的观察与白皮书[11]中提到的行为一致。最后,我们注意到对于所有半精度浮点(fp16和bf16)输入,SASS指令MOVM.16.MT88用于将矩阵加载到Tensor Core。通常,MOVM SASS指令用于移动带有转置的矩阵。发出的MOVM指令数量取决于矩阵形状和布局(行主序或列主序)。例如,如果我们在PTX代码中使用A和B矩阵作为行主序,那么MOVM指令用于转置B矩阵,以便将A的每一行与B的每一列相乘。然而,当我们都使用列主序时,MOVM指令用于A和C矩阵。它在执行前转置A和C,并在执行后转置C。最后,如果A是行主序而B是列主序,则跟踪中不存在MOVM指令。我们使用上述相同方法计算延迟来计算内存吞吐量。观察结果与白皮书中提到的吞吐量值非常相似。

结论

内容本身很短,就不总结了。