|

财经

逆向英伟达GPU,解码芯片龙头的成功奥秘

来源:半导体行业观察

2025-05-14 09:53:51

(原标题:逆向英伟达GPU,解码芯片龙头的成功奥秘)

如果您希望可以时常见面,欢迎标星收藏哦~

GPU 是加速高性能计算(HPC)工作负载的热门平台,广泛应用于人工智能和科学模拟等领域。然而,学术界的大多数微架构研究仍基于 15 年前的 GPU 核心流水线设计。

本文通过逆向工程剖析了现代英伟达 GPU 核心,揭示了其设计的关键方面,并解释了 GPU 如何利用硬件-编译器协同技术,在执行过程中由编译器引导硬件工作。具体而言,研究揭示了指令发射逻辑的工作机制,包括发射调度器的策略、寄存器文件及其相关缓存的结构,以及内存流水线的多个特性。此外,分析了基于流缓冲区的简单指令预取器如何与现代英伟达 GPU 相适配并可能被采用。进一步研究了寄存器文件缓存和寄存器文件读端口数量对模拟准确性和性能的影响。

通过对这些新发现的微架构细节进行建模,与之前的先进模拟器相比,我们的模型在执行周期的平均绝对百分比误差(MAPE)降低了18.24%,与实际硬件(英伟达RTX A6000)相比,平均绝对百分比误差为13.98%。此外,我们证明了该新模型适用于其他英伟达架构,如图灵架构。

最后,研究表明,现代英伟达 GPU 中基于软件的依赖管理机制在性能和面积方面优于传统的基于计分板的硬件机制。

引言

近年来,GPU 除了在图形处理领域应用广泛外,在执行通用工作负载方面也备受青睐。GPU 的架构提供了大规模并行处理能力,许多现代应用,如生物信息学、物理学和化学等领域,都可以利用这一特性。如今,GPU 已成为加速现代机器学习工作负载的主要选择,这些工作负载对内存带宽和计算能力有着极高的要求。近年来,GPU 的微架构、互连技术(NVLink)和通信框架(NCCL)都取得了重大创新。这些进展推动了大型语言模型的推理和训练,而这往往需要配备数千个 GPU 的集群。

然而,关于现代商用 GPU 微架构设计的公开信息较少,目前学术界的研究大多以 2006 年推出的特斯拉微架构为基准。但自特斯拉架构以来,GPU 架构已经发生了显著变化,因此基于该架构的模型可能会导致研究结果出现偏差。本研究旨在揭示现代英伟达 GPU 架构中各个组件的不同特性和细节,以提高学术微架构模型的准确性。本文所解释的模型和细节有助于研究人员更好地识别改进未来 GPU 的挑战和机遇。总之,本文做出了以下贡献:


  • 描述了指令发射阶段的操作,包括依赖处理、线程束(warp)的就绪条件以及发射调度器的策略。

  • 描述了取指阶段及其调度器的合理操作,该调度器与发射阶段协同工作。

  • 提供了寄存器文件的重要细节,并解释了寄存器文件缓存的行为。此外,研究表明现代英伟达 GPU 不使用操作数收集阶段或收集单元。

  • 揭示了内存流水线组件的多个细节。

  • 重新设计了 Accel-sim 模拟器中使用的 SM / 核心模型,并将本文揭示的所有细节整合到该模型中。

  • 使用实际硬件对新模型进行验证,并与 Accel-sim 模拟器进行比较。我们的新模型在与英伟达 RTX A6000(安培架构)实际硬件对比时,执行周期的平均绝对百分比误差(MAPE)为 13.98%,比之前的模拟器模型提高了 18.24%。

  • 证明了基于简单流缓冲区的指令预取器在性能准确性方面表现出色,其性能与完美指令缓存相近。

  • 展示了寄存器文件缓存和寄存器文件读端口数量对模拟准确性和性能的影响。

  • 比较了本文揭示的依赖管理系统与传统计分板在性能、面积和模拟准确性方面的差异。结果表明,这种新颖的软硬件协同设计比传统计分板在处理依赖关系方面更高效。

  • 展示了该模型对其他英伟达架构(如图灵架构)的适用性。


本文的其余部分组织如下:第 2 节介绍了这项工作的背景和动机;第 3 节解释了我们采用的逆向工程方法;第 4 节描述了现代英伟达 GPU 架构中的控制位及其详细行为;第 5 节介绍了这些 GPU 的核心微架构;第 6 节描述了我们在模拟器中建模的特性;第 7 节评估了我们的模型与实际硬件相比的准确性,并将其与 Accel-sim 框架模拟器进行比较,分析了指令预取流缓冲区的影响,研究了寄存器文件缓存和寄存器文件读端口数量的影响,比较了不同的依赖管理机制,并讨论了该模型对其他英伟达架构的适用性;第 8 节回顾了先前的相关工作;最后,第 9 节总结了这项工作的主要成果。

背景和动机

学术界的大多数 GPU 微架构研究依赖于 GPGPU-Sim 模拟器采用的微架构。最近,该模拟器进行了更新,纳入了从 Volta 架构开始的子核心(英伟达术语中的处理块)方法。图 1 展示了该模拟器中建模的架构框图。可以看到,它由四个子核心和一些共享组件组成,如 L1 指令缓存、L1 数据缓存、共享内存和纹理单元。


在这个 GPU 流水线的取指阶段,循环调度器会选择下一条指令位于 L1 指令缓存中且指令缓冲区有空闲槽位的线程束。这些缓冲区专用于每个线程束,用于存储线程束中取指和解码后的连续指令。指令会一直留在这个缓冲区中,直到准备好并被选中发射。

在发射阶段, GTO调度器会选择一个线程束进行指令发射,条件是该线程束不在等待屏障,并且其最老的指令与流水线中其他正在执行的指令没有数据依赖关系。先前的研究假设每个线程束有两个计分板来检查数据依赖。第一个计分板标记对寄存器的待写操作,以跟踪写后写(WAW)和写后读(RAW)依赖。只有当指令的所有操作数在这个计分板中都被清除时,该指令才能被发射。第二个计分板统计寄存器的活跃消费者数量,以防止读后写(WAR)危害。第二个计分板是必要的,因为尽管指令按顺序发射,但它们的操作数可能会乱序获取,这种情况会发生在可变延迟指令(如内存指令)上。这些指令在发射后会被排队,并且可能在较年轻的算术指令写入结果后读取其源操作数,如果前者的源操作数与后者的目的操作数相同,就会导致读后写危害。

一旦指令被发射,它会被放置在一个收集单元(CU)中,等待直到获取到所有源寄存器操作数。每个子核心都有一个私有的寄存器文件,包含多个存储体,每个存储体有几个端口,这样可以在单个周期内以较低成本进行多次访问。仲裁器负责处理对同一存储体的多个请求可能产生的冲突。当指令的所有源操作数都在收集单元中时,该指令会移动到分发阶段,在这个阶段,它会被分发到合适的执行单元(如内存单元、单精度单元、特殊功能单元),执行单元的延迟取决于单元类型和指令。一旦指令到达写回阶段,结果会被写入寄存器文件。

Accel-sim 中建模的 GPU 微架构类似于基于 2006 年发布的特斯拉架构的英伟达 GPU,并更新了一些现代特性,主要是子核心模型和类似于 Volta 架构的采用 IPOLY 索引的扇区缓存。然而,它缺少现代英伟达 GPU 中存在的一些重要组件,如 L0 指令缓存和统一寄存器文件。此外,子核心的一些主要组件,如发射逻辑、寄存器文件或寄存器文件缓存等,并没有更新以反映当前的设计。

这项工作旨在对现代英伟达 GPU 核心的微架构进行逆向工程,并更新 Accel-sim 模拟器以融入新发现的特性。这将使更新后的 Accel-sim 模拟器的用户能够以更接近行业在商业设计中证明成功的基线开始工作,从而使他们的工作更具相关性。

逆向工程方法

本节解释了我们用于发现英伟达安培架构 GPU 核心(SMs)微架构的研究方法。

我们的方法基于编写包含少量指令的小型微基准测试,并测量特定短指令序列的执行时间。通过在代码区域周围使用指令将 GPU 的时钟计数器保存到寄存器中,并将其存储在主内存中以供后续后处理,从而获得经过的周期数。评估的指令序列通常由手写的 SASS 指令(及其控制位)组成。根据测试的不同,我们会可视化记录的周期数,以确认或反驳关于控制位语义或微架构中特定特性的假设。

下面给出两个示例来说明这种方法:


虽然英伟达没有官方工具直接编写 SASS 代码(英伟达汇编语言),但各种第三方工具允许程序员重新排列和修改汇编指令(包括控制位)。例如,当编译器生成的代码不是最优时,这些工具可用于优化关键内核的性能。MaxAS 是第一个用于修改 SASS 二进制文件的工具,随后,针对 Kepler 架构开发了其他工具,如 KeplerAS。然后,TuringAS 和 CUAssembler 出现,以支持更新的架构。由于其灵活性、可扩展性以及对最新硬件的支持,我们决定使用 CUAssembler。

现代英伟达 GPU 架构中的控制位

现代英伟达 GPU 架构的指令集架构(ISA)包含控制位和编译器提供的用于确保正确性的信息。与先前工作中通过在运行时跟踪寄存器读写来检查数据依赖的 GPU 架构不同,这些 GPU 架构依赖编译器来处理寄存器数据依赖。为此,所有汇编指令都包含一些控制位,除了提高性能和降低能耗外,还用于正确管理依赖关系。

下面描述每个指令中包含的这些控制位的行为。解释基于一些文档,但这些文档通常含糊不清或不完整,因此我们使用第 3 节中描述的方法来揭示这些控制位的语义,并验证它们是否如以下所述那样工作。

子核心每个周期可以发射一条指令。默认情况下,如果线程束程序顺序中最老的指令已准备好,发射调度器会尝试发射该线程束的指令。编译器使用控制位指示指令何时准备好发射。如果前一个周期发射指令的线程束中最老的指令未准备好,发射逻辑会根据 5.1 小节中描述的策略从另一个线程束中选择一条指令。

为了处理固定延迟指令的生产者-消费者依赖关系,每个线程束都有一个计数器,称为停顿计数器(Stall counter)。如果这个计数器不为零,该线程束就不是发射指令的候选者。编译器会根据产生指令的延迟减去生产者和第一个消费者之间的指令数量来设置这个计数器。所有这些每个线程束的停顿计数器每个周期减 1,直到达到 0。发射逻辑只会检查这个计数器,并且在其值为 0 之前不会考虑发射同一线程束的另一条指令。

例如,一条延迟为 4 个周期且其第一个消费者是下一条指令的加法指令,会在停顿计数器中编码为 4。使用第 3 节中解释的方法,我们已经验证,如果停顿计数器设置不正确,程序的结果将是错误的,因为硬件不会检查 RAW 危害,而是简单地依赖这些由编译器设置的计数器。此外,这种机制在面积和能量布线方面有优势。要知道,与传统计分板方法不同,固定延迟单元到依赖处理组件的布线是不需要的。

另一个控制位称为 Yield,用于指示硬件在下一个周期不应发射同一线程束的指令。如果子核心的其他线程束在下一个周期都未准备好,则不会发射任何指令。

每个指令都会设置停顿计数器和 Yield 位。如果停顿计数器大于 1,线程束将至少停顿一个周期,在这种情况下,无论 Yield 位是否设置都会停顿。

另一方面,一些指令(如内存指令、特殊功能指令)具有可变延迟,编译器不知道它们的执行时间。因此,编译器无法通过停顿计数器来处理这些危害。这些危害通过依赖计数器位来解决。每个线程束有六个特殊寄存器来存储这些计数器,称为 SBx,其中 x 的取值范围为 0 - 5。每个计数器最多可以计数到 63。

当一个线程束开始时,这些计数器被初始化为 0。为了处理生产者 - 消费者依赖,生产者在发射后增加一个特定的计数器,并在写回时减少它。消费者指令被指示等待,直到这个计数器为 0。

对于 WAR 危害,机制类似,唯一的区别是计数器在指令读取其源操作数后减少,而不是在写回时减少。

在每个指令中,有一些控制位用于指示最多两个在发射时增加的计数器。其中一个计数器将在写回时减少(用于处理 RAW 和 WAW 依赖),另一个在寄存器读取时减少(用于处理 WAR 依赖)。为此,每个指令有两个 3 位的字段来指示这两个计数器。此外,每个指令有一个 6 位的掩码,用于指示它必须检查哪些依赖计数器以确定是否准备好发射。注意,一条指令最多可以检查所有六个计数器。

考虑到如果一条指令有多个源操作数,且其生产者具有可变延迟,所有这些生产者可以使用相同的依赖计数器而不会损失任何并行性。需要注意的是,在有超过六个具有不同可变延迟生产者的消费者指令的场景中,这种机制可能会遇到并行性限制。在这种情况下,编译器必须在两种选择中做出决定来管理这种情况:1)将更多指令分组在同一个依赖计数器下;2)以不同方式重新排序指令。

依赖计数器的增加是在发射生产者指令后的周期进行的,因此直到一个周期后才会生效。因此,如果消费者是下一条指令,生产者必须将停顿计数器设置为 2,以避免在下一个周期发射消费者指令。

处理具有可变延迟生产者的依赖关系的示例可以在图 2 中找到。这段代码展示了四条指令(三条加载指令和一条加法指令)及其相关编码。由于加法指令与加载指令(可变延迟指令)存在依赖关系,因此使用依赖计数器来防止数据危害。地址为 PC 0x80 的指令与地址为 0x50 和 0x60 的指令存在 RAW 依赖关系。因此,SB3 在指令 0x50 和 0x60 发射时增加,并在写回时减少。另一方面,加法指令与地址为 0x60 和 0x70 的指令存在 WAR 依赖关系。因此,SB0 在指令 0x60 和 0x70 发射时增加,并在读取它们各自的寄存器源操作数后减少。最后,加法指令的依赖计数器掩码编码表示在发射之前,SB0 和 SB3 必须为 0。注意,指令 0x70 还使用 SB4 来控制与未来指令的 RAW/WAR 危害,但指令 0x80 不需要等待这个依赖计数器,因为它与该加载指令没有任何依赖关系。在读取源操作数后清除 WAR 依赖是一项重要的优化,因为源操作数有时会比结果产生的时间早得多被读取,特别是对于内存指令。例如,在这个例子中,指令 0x80 会等待直到指令 0x70 读取 R2 以清除这个 WAR 依赖,而不是等待直到指令 0x70 执行写回操作,后者可能会在数百个周期后发生。

检查这些计数器是否就绪的另一种方法是通过 DEPBAR.LE 指令。例如,DEPBAR.LE SB1, 0x3, {4,3,2} 要求依赖计数器 SB1 的值小于或等于 3 才能继续执行。最后一个参数(, {4,3,2})是可选的,如果使用,该指令在指定 ID(在这个例子中为 4、3、2)的依赖计数器值等于 0 之前不能发射。

DEPBAR.LE 在某些特定场景中特别有用。例如,当一个消费者需要等待一系列 N 个按顺序写回的可变延迟指令(例如带有 STRONG.SM 修饰符的内存指令)中的前 M 条指令时,它允许对这一系列指令使用相同的依赖计数器。使用 DEPBAR.LE 并将其参数设置为 N-M 会使这条指令等待序列中的前 M 条指令。另一个例子是重用相同的依赖计数器来保护 RAW/WAW 和 WAR 危害。如果一条指令对两种类型的危害都使用相同的依赖计数器,由于 WAR 危害比 RAW/WAW 危害解决得更早,后续的 DEPBAR.LE SBx, 0x1 会等待直到 WAR 危害得到解决,然后允许线程束继续执行。后续使用其结果的指令需要等待直到这个依赖计数器变为 0,这意味着结果已经被写入。

此外,GPU采用寄存器文件缓存(register file cache)来降低能耗并减少寄存器文件读端口的争用。该结构通过软件管理:每条指令的源操作数包含一个控制位(称为reuse位),用于指示硬件是否缓存该寄存器的内容。关于寄存器文件缓存组织架构的更多细节详见第5.3.1节。

最后,需要说明的是,虽然本文聚焦NVIDIA架构,但通过研究AMD GPU指令集文档可以发现,AMD同样采用硬件-软件协同设计来管理依赖关系并提升性能。与NVIDIA的DEPBAR.LE指令类似,AMD使用waitcnt指令——根据架构版本不同,每个wavefront(相当于warp)配备3至4个专用计数器,每个计数器对应特定指令类型,用于防范相关指令造成的数据冲突。AMD不允许常规指令通过控制位直接等待计数器归零,而必须显式插入waitcnt指令,这会增加指令总数。这种设计虽然降低了解码开销,却导致整体指令数量上升。相比之下,NVIDIA的方案具有两大优势:1) 每个warp可用的计数器数量多出2个(共6个);2) 计数器不与特定指令类型绑定,从而支持同一指令类型内更并行的依赖链管理。值得注意的是,AMD在RDNA 3/3.5架构中引入了DELAY_ALU指令来缓解ALU指令依赖导致的流水线停顿,其优势在于无需编译器介入即可避免ALU指令的数据冲突。而NVIDIA则依赖编译器为固定延迟指令正确设置Stall计数器,这种方式虽减少了指令数量,但增加了解码开销。

GPU 核心微架构

在本节中,我们使用第 3 节中解释的方法,描述我们对现代商用英伟达 GPU 核心微架构的研究发现。图 3 展示了 GPU 核心微架构的主要组件。下面,我们将详细描述指令发射调度器、前端、寄存器文件和内存流水线的微架构。

1

指令发射调度器

在本小节中,我们剖析现代英伟达 GPU 的指令发射调度器。首先,在(1)小节中描述每个周期中哪些线程束被视为指令发射的候选者。然后,在(2)小节中介绍选择策略。

(1)线程束就绪条件

在给定周期内,若满足某些条件,线程束会被视为发射其最老指令的候选者。其中一些条件取决于同一线程束的先前指令,而另一些则依赖于核心的全局状态。

一个显而易见的条件是指令缓冲区中存在有效指令。另一个条件是线程束的最老指令与同一线程束中尚未完成的较老指令之间不能有任何数据依赖风险。指令之间的依赖关系通过第 4 节中描述的控制位,借助软件支持来处理。

此外,对于固定延迟指令,只有在确保指令发射后执行所需的所有资源都可用的情况下,线程束才会被视为在给定周期内发射其最老指令的候选者。

这些资源之一是执行单元。执行单元有一个输入锁存器,当指令到达执行阶段时,该锁存器必须为空。如果执行单元的宽度仅为半个线程束,此锁存器会被占用两个周期;若为完整线程束的宽度,则占用一个周期。

对于在常量缓存中有源操作数的指令,在发射阶段进行标签查找。当所选线程束的最老指令需要从常量缓存中获取操作数,而该操作数不在缓存中时,调度器会暂停指令发射,直到缓存缺失问题得到解决。然而,如果在四个周期后缓存缺失仍未得到处理,调度器会切换到另一个线程束(具有就绪指令的最年轻线程束)。



至于寄存器文件读端口的可用性,指令发射调度器并不知道被评估的指令在后续周期中是否有足够的端口用于读取而不会出现停顿。我们在观察到如果删除清单 1 中最后一条 FFMA 指令和最后一条 CLOCK 指令之间的 NOP 指令,代码中的冲突不会导致第二条 CLOCK 指令的发射停顿后,得出了这一结论。我们进行了大量实验来揭示指令发射和执行之间的流水线结构,但未能找到一个完美符合所有实验结果的模型。不过,下面描述的模型几乎适用于所有情况,所以我们采用这个模型。在这个模型中,固定延迟指令在指令发射阶段和读取源操作数的阶段之间有两个中间阶段。第一个阶段称为控制阶段,固定延迟指令和可变延迟指令都会经过这个阶段,其职责是增加依赖计数器,或者在需要时读取时钟计数器的值。我们的实验证实,这导致增加依赖计数器的指令和等待该依赖计数器归零的指令之间至少需要一个周期,才能使增加的计数生效,所以两条连续的指令不能使用依赖计数器来避免数据依赖风险,除非第一条指令设置了 Yield 位或停顿计数器大于 1。

第二个阶段仅存在于固定延迟指令中。在这个阶段,检查寄存器文件读端口的可用性,指令会在这个阶段停顿,直到确保其可以继续执行而不会发生寄存器文件端口冲突。我们将这个阶段称为分配阶段。有关寄存器文件读写流水线及其缓存的更多详细信息,请参见 5.3 小节。可变延迟指令(例如内存指令)在经过控制阶段后(不经过分配阶段)会直接进入一个队列。当队列中的指令确保不会发生任何冲突时,它们才被允许进入寄存器文件读取流水线。固定延迟指令在分配寄存器文件端口时优先于可变延迟指令,因为如上文所述,依赖关系由软件处理,固定延迟指令需要在发射后的固定周期数内完成,以保证代码的正确性。

(2)调度策略

为了探究指令发射调度器的策略,我们设计了许多涉及多个线程束的不同测试用例,并记录每个周期中指令发射调度器选择哪个线程束进行指令发射。这些信息通过能够保存 GPU 当前时钟周期的指令收集而来。然而,由于硬件不允许连续发射两条这样的指令,我们在它们之间使用了一定数量的其他指令(通常是 NOP 指令)。我们还改变了 Yield 和停顿计数器控制位的具体值。

实验结果使我们得出结论,线程束调度器采用GTO策略,如果同一线程束中的指令满足上述资格标准,就选择该线程束的指令。当切换到不同线程束时,会选择满足资格标准的最年轻线程束。

图 4 通过我们的一些实验示例说明了这种指令发射调度器策略。该图描绘了在同一子核心中执行四个线程束时,三种不同情况下的指令发射情况。每个线程束执行相同的代码,该代码由 32 条可在每个周期发射一条的独立指令组成。

在第一种情况(图 4a)中,所有停顿计数器、依赖掩码和 Yield 位都设置为零。调度器从最年轻的线程束 W3 开始发射指令,直到它在指令缓存(Icache)中发生缺失。由于缓存缺失,W3 没有有效指令,因此调度器切换到从 W2 发射指令。W2 在 Icache 中命中,因为它重用了 W3 带来的指令,当 W2 到达 W3 发生缺失的位置时,缺失已经得到处理,并且所有剩余指令都在 Icache 中找到,所以GTO调度器发射该线程束的指令直到结束。之后,调度器继续从 W3(最年轻的线程束)发射指令直到结束,因为此时所有指令都已存在于 Icache 中。然后,调度器切换到从 W1 从头到尾发射指令,最后对 W0(最老的线程束)执行相同操作。


图 4b 展示了每个线程束的第二条指令将其停顿计数器设置为 4 时的指令发射时间线。可以观察到,调度器在两个周期后从 W3 切换到 W2,再过两个周期后切换到 W1,然后又过两个周期后回到 W3(因为 W3 的停顿计数器变为零)。一旦 W3、W2 和 W1 执行完毕,调度器开始从 W0 发射指令。在发射 W0 的第二条指令后,调度器产生四个空闲周期,因为没有其他线程束可以掩盖停顿计数器带来的延迟。

图 4c 展示了每个线程束的第二条指令设置了 Yield 位时调度器的行为。可以看到,在发射每个线程束的第二条指令后,调度器会切换到其余线程束中最年轻的线程束。例如,W3 切换到 W2,W2 又切换回 W3。我们还测试了设置 Yield 位且没有更多可用线程束的场景(图中未显示),观察到调度器会产生一个周期的空闲。

我们将这种指令发射调度器策略称为CGGTY,因为编译器通过控制位(停顿计数器、Yield 位和依赖计数器)协助调度器工作。

不过,我们仅在同一线程块(CTA)内的线程束中证实了这种行为,因为我们尚未设计出可靠的方法来分析不同 CTA 中线程束之间的交互。

2

前端

根据英伟达的多篇文档中的图表,流多处理器(SM)有四个不同的子核心,线程束以循环方式均匀分布在子核心之间(即 warp ID %4)。每个子核心都有一个私有的 L0 指令缓存,它与一个为 SM 中所有四个子核心共享的 L1 指令缓存相连。我们假设存在一个仲裁器来处理不同子核心的多个请求。

每个 L0 指令缓存都有一个指令预取器。我们的实验证实了 Cao 等人先前的研究,该研究表明指令预取在 GPU 中是有效的。虽然我们无法确定英伟达 GPU 中使用的具体设计,但我们怀疑它是一种简单的方案,如流缓冲区,在发生缓存缺失时预取连续的内存块。根据我们的分析,我们假设流缓冲区的大小为 16,下文将对此进行详细说明。

我们通过实验无法确定确切的指令获取策略,但它肯定与指令发射策略类似;否则,在指令缓冲区中找不到有效指令的情况会相对频繁地发生,而我们在实验中并未观察到这种情况。基于此,我们假设每个子核心每个周期可以获取并解码一条指令。取指调度器会尝试从先前周期(或最近发射指令的周期)发射指令的同一线程束中获取指令,除非它检测到指令缓冲区中已有的指令数量加上正在获取的指令数量等于指令缓冲区的大小。

在这种情况下,它会切换到指令缓冲区中有空闲条目的最年轻线程束。我们假设每个线程束的指令缓冲区有三个条目,因为考虑到从取指到发射有两个流水线阶段,这样足以支持贪心策略。如果指令缓冲区的大小为两个条目,指令发射调度器的贪心策略将会失败。例如,假设指令缓冲区大小为两个,所有请求都在 Icache 中命中,所有线程束的指令缓冲区都已满,在第 1 个周期,一个子核心从线程束 W1 发射指令并从 W0 取指。在第 2 个周期,W1 的第二条指令将被发射,第三条指令将被获取。在第 3 个周期,W1 的指令缓冲区中将没有指令,因为指令 3 仍在解码中。

因此,其贪心行为将失败,它将不得不切换到从另一个线程束发射指令。正如我们的实验所证实的,当指令缓冲区有三个条目时,这种情况不会发生。值得注意的是,文献中的大多数先前设计通常假设取指和解码宽度为两条指令,且每个线程束的指令缓冲区有两个条目。此外,这些设计仅在指令缓冲区为空时才取指。因此,贪心选择的线程束总是至少在连续两条指令之后就会改变,这与我们的实验观察结果不符。

3

寄存器文件

我们通过运行不同组合的 SASS 汇编指令进行了大量实验,以揭示寄存器文件的组织结构。例如,我们编写了对寄存器文件端口施加不同压力的代码,包括使用和不使用寄存器文件缓存的情况。

现代英伟达 GPU 拥有多种寄存器文件:


  • 常规寄存器文件:近期的英伟达架构每个 SM 有 65536 个 32 位寄存器,用于存储线程操作的值。这些寄存器以 32 个为一组进行排列,每组对应一个线程束中 32 个线程的寄存器,因此共有 2048 个线程束寄存器。这些寄存器在子核心之间平均分配,每个子核心中的寄存器被组织成两个存储体。特定线程束使用的寄存器数量可以在 1 到 256 之间变化,这在编译时确定。每个线程束使用的寄存器越多,SM 中能够并行运行的线程束就越少。

  • 统一寄存器文件:每个线程束有 64 个专用的 32 位寄存器,用于存储该线程束中所有线程共享的值。

  • 谓词寄存器文件:每个线程束有八个 32 位寄存器,每个位由线程束中的不同线程使用。这些谓词用于线程束指令,以指示哪些线程必须执行该指令,在分支情况下,还用于指示哪些线程必须执行分支,哪些不执行。

  • 统一谓词寄存器文件:每个线程束有八个 1 位寄存器,用于存储线程束中所有线程共享的谓词。

  • SB 寄存器:如第 4 节所述,每个线程束有六个寄存器,称为依赖计数器,用于跟踪可变延迟依赖关系。

  • B 寄存器:每个线程束至少有 16 个 B 寄存器,用于管理控制流重新收敛。

  • 特殊寄存器:还有各种其他寄存器,用于存储特殊值,如线程 ID 或块 ID。

与先前的工作不同,现代英伟达 GPU 并未使用操作数收集器来处理寄存器文件端口的冲突。操作数收集单元会在指令发射和写回之间的时间间隔引入可变性,这使得英伟达指令集架构(ISA)无法实现固定延迟指令,因为如第 4 节所述,为了正确处理依赖关系,固定延迟指令的延迟必须在编译时确定。我们通过检查特定的生产者 - 消费者指令序列在改变进入同一存储体的操作数数量时的正确性,证实了操作数收集器的不存在。我们观察到,无论寄存器文件端口冲突的数量如何,指令中为避免数据风险所需的停顿计数字段的值以及执行指令所需的时间都保持不变。

我们的实验表明,每个寄存器文件存储体都有一个 1024 位的专用写端口。此外,当加载指令和固定延迟指令在同一周期完成时,延迟一个周期的是加载指令。另一方面,当两个固定延迟指令之间存在冲突时,例如一条 IADD3 指令后面跟着一条使用相同目标存储体的 IMAD 指令,它们都不会延迟。这意味着使用了类似于 Fermi 架构中引入的结果队列来处理固定延迟指令。这些指令的消费者不会被延迟,这意味着在结果写入寄存器文件之前,使用了旁路技术将结果转发给消费者。

关于读取,我们观察到每个存储体的带宽为 1024 位。这些测量结果是通过各种测试获得的,这些测试记录了连续的 FADD、FMUL 和 FFMA 指令的执行时间。例如,两个源操作数都在同一存储体中的 FMUL 指令会产生一个周期的空闲,而如果两个操作数在不同存储体中,则不会产生空闲。三个源操作数都在同一存储体中的 FFMA 指令会产生两个周期的空闲。

遗憾的是,我们未能找到一种适用于所有研究情况的读取策略,因为我们观察到空闲周期的产生取决于指令的类型以及指令中每个操作数的任务。我们发现,与几乎所有测试实验都最匹配的近似方案是,在固定延迟指令的指令发射和操作数读取之间设置两个中间阶段,我们称之为控制阶段和分配阶段。前者已在前文中解释过。后者负责保留寄存器文件读端口。寄存器文件的每个存储体都有一个 1024 位的读端口,通过使用寄存器文件缓存来缓解读取冲突(稍后会详细介绍)。

我们的实验表明,所有固定延迟指令读取源操作数都需要三个周期,即使在某些周期指令处于空闲状态(例如当只有两个源操作数时),因为 FADD 和 FMUL 指令与 FFMA 指令具有相同的延迟,尽管它们少一个操作数,而且 FFMA 指令无论其三个操作数是否在同一存储体中,延迟始终相同。如果处于分配阶段的指令意识到它无法在接下来的三个周期内读取所有操作数,它将停留在这个阶段(使流水线向上游停顿)并产生空闲周期,直到它能够在接下来的三个周期内保留读取源操作数所需的所有端口。

(1)寄存器文件缓存

在 GPU 中使用寄存器文件缓存(RFC)是为了缓解寄存器文件端口的竞争并节省能源。

通过实验,我们观察到英伟达的设计与 Gebhart 等人的工作类似。与该设计一致,RFC 由编译器控制,并且仅用于在常规寄存器文件中有操作数的指令。关于最后结果文件结构,我们所说的结果队列的行为类似。然而,与上述论文不同,这里没有使用两级指令发射调度器,如前文所述。

关于 RFC 的组织,我们的实验表明,它在每个子核心的两个寄存器文件存储体中各有一个条目。每个条目存储三个 1024 位的值,每个值对应指令可能拥有的三个常规寄存器源操作数之一。总体而言,RFC 的总容量为六个 1024 位操作数的值(子条目)。需要注意的是,有些指令的某些操作数需要两个连续的寄存器(例如张量核心指令)。在这种情况下,这两个寄存器分别来自不同的存储体,并被缓存在各自相应的条目中。

编译器管理分配策略。当一条指令被发射并读取其操作数时,如果编译器为某个操作数设置了重用位,该操作数就会被存储在 RFC 中。如果后续指令来自同一线程束,其寄存器 ID 与 RFC 中存储的 ID 一致,并且操作数在指令中的位置与触发缓存的指令中的位置相同,那么这条后续指令将从 RFC 中获取其寄存器源操作数。无论 RFC 中是否命中,当对同一存储体和操作数位置的读取请求到达时,缓存的值将不可用。清单 2 中的示例 2 说明了这一点;为了让第三条指令在缓存中找到 R2,第二条指令必须设置 R2 的重用位,尽管 R2 已经在缓存中为第二条指令所用。清单 2 还展示了另外三个示例来说明 RFC 的行为。


4

内存流水线

在现代NVIDIA GPU中,内存流水线的初始阶段由各个子核心(sub-core)独立处理,而执行实际内存访问的最后阶段则由四个子核心共享,这是因为数据缓存(data cache)和共享内存(shared memory)是所有子核心共用的资源20,28。本节我们将揭示每个子核心中加载/存储队列(load/store queues)的大小、子核心向共享内存结构发送请求的速率,以及不同类型内存指令的延迟特性。

需要说明的是,GPU内存访问主要分为两种类型:一种是访问共享内存(即SM本地内存,由线程块内所有线程共享),另一种是访问全局内存(即GPU主存)。

为探究队列大小和内存带宽,我们设计了一系列实验:每个子核心要么执行一个warp的指令,要么处于空闲状态。每个warp执行一系列独立的加载或存储指令,这些指令总能命中数据缓存或共享内存,并使用常规寄存器。表1展示了这些实验结果,其中第一列显示代码中的指令序号,后续四列则分别展示四种不同激活子核心数量场景下,各子核心发射对应指令的时钟周期。

通过观察Ampere架构的表现可以发现:每个子核心能够连续五个周期每周期发射一条内存指令,但从第6条内存指令开始会出现延迟,延迟周期数取决于激活的子核心数量。由此我们可以推断:1) 每个子核心最多能缓冲5条连续指令而不产生停顿;2) 全局共享结构每两个周期可以接收来自任意子核心的一个内存请求。例如,在多个子核心同时激活的场景下,每个子核心的第6条及后续指令会间隔两个周期才被发射。

我们还发现:每个子核心的地址计算吞吐量为每4个周期完成一条指令。这一结论源自单激活子核心场景下的测试数据——当仅有一个子核心激活时,第6条指令发射后会出现4个周期的间隔。当两个子核心激活时,由于共享结构每两个周期能处理一条指令,因此每个子核心可以保持每4个周期发射一条内存指令的速率。当更多子核心激活时,共享结构将成为瓶颈。例如四个子核心同时工作时,由于共享结构的最大吞吐量为每两个周期处理一条指令,每个子核心只能每8个周期发射一条指令。


关于子核心内存队列的大小,我们估算其容量为4条指令,尽管每个子核心能够缓冲5条连续指令。指令在进入处理单元时占用队列槽位,离开时释放该槽位。

在缓存命中且单线程执行的场景下,我们测量了各类内存指令的两种延迟指标:第一种是加载指令从发射到其消费者指令(或覆写同一目标寄存器的指令)能够发射的最短时间间隔,我们称之为RAW/WAW延迟(需注意存储指令不会产生寄存器RAW/WAW依赖)。第二种是加载/存储指令从发射到能够发射写入其源寄存器的指令的最短时间间隔,称为WAR延迟。具体测量结果如表2所示。


研究发现:使用统一寄存器(uniform registers)计算地址的全局内存访问速度优于常规寄存器。这种差异源于地址计算效率的提升——统一寄存器由warp内所有线程共享,因此只需计算单个内存地址;而常规寄存器要求每个线程可能计算不同的内存地址。

此外,共享内存加载的延迟普遍低于全局内存,且其WAR延迟在常规与统一寄存器场景下保持一致,而RAW/WAW延迟在使用统一寄存器时会减少1个周期。WAR延迟的一致性表明共享内存的地址计算是在共享结构中完成的(而非子核心本地结构),因此一旦读取完源寄存器即可解除WAR依赖。

延迟特性还与读写数据的尺寸相关:对于WAR依赖,加载指令的延迟不受数据尺寸影响,因为其源操作数仅用于地址计算;而存储指令的WAR延迟会随写入内存数据尺寸的增加而上升,因为这些数据作为源操作数需要从寄存器文件读取。对于RAW/WAW依赖(仅适用于加载指令),延迟随读取数据尺寸增大而增加,因为需要从内存向寄存器文件传输更多数据。实测数据显示该传输带宽为512比特/周期。

我们还观察到,常量缓存的WAR延迟显著高于全局内存加载操作,而其RAW/WAW延迟却略低。目前尚未能确认这一现象的具体成因。但通过实验发现:固定延迟指令对常量内存的访问与LDC(加载常量)指令使用了不同层级的缓存。我们通过LDC指令预加载特定地址到常量缓存并等待其完成后,再使用相同地址发出固定延迟指令,实测出现了79个周期的延迟(对应缓存未命中状态),而非预期的零延迟(命中状态)。这表明固定延迟指令访问常量地址空间时使用L0 FL(固定延迟)常量缓存,而LDC指令则使用L0 VL(可变延迟)常量缓存。

最后分析LDGSTS指令,该指令专为降低寄存器文件压力并提升数据传输效率而设计38,可直接将全局内存数据存入共享内存而无需经过寄存器文件,从而节省指令和寄存器资源。测试表明其延迟与数据粒度无关:WAR依赖延迟恒定,因为地址计算完成后即可解除依赖;而RAW/WAW依赖则在完成读取阶段后解除,同样不受数据粒度影响。

建模实现

我们从零开始设计了 Accel - sim 框架模拟器的流多处理器(SM)/ 核心模型,通过修改流水线来实现前文所阐述以及图 3 中所展示的所有细节。下面概述主要的新增组件。

首先,我们为每个子核心添加了一个带有流缓冲区预取器的 L0 指令缓存。L0 指令缓存和常量缓存通过参数化的延迟与 L1 指令 / 常量缓存相连。

我们根据之前的测量结果或贾等人所描述的安培架构来选择缓存的大小、层次结构和延迟。我们修改了指令发射阶段,以支持控制位、对新添加的固定延迟指令的 L0 常量缓存进行标签查找,以及实现新的编译器引导的贪心优先且最年轻优先(CGGTY)指令发射调度器。我们纳入了控制阶段(在该阶段指令会增加依赖计数器)和分配阶段(在该阶段固定延迟指令会检查访问寄存器文件和寄存器文件缓存时是否存在冲突)。

对于内存指令,我们为每个子核心建模了一个新单元,并为子核心之间建模了一个共享单元,其延迟如前一节所述。

此外,由于阿卜杜勒哈利克等人已经证明张量核心指令的延迟取决于其操作数的数值类型和大小,因此我们调整了模型,以便为每种操作数类型和大小使用正确的延迟。

我们建模的其他细节包括:在每个子核心中没有专用双精度执行单元的架构中,为所有子核心共享的双精度指令执行流水线。此外,我们精确地对使用多个寄存器的操作数的读写时序进行建模,而之前只是为每个操作数使用一个寄存器来进行近似处理。此外,我们修正了之前一项工作中报告的指令地址的一些不准确之处。

除了在模拟器中实现新的 SM / 核心模型外,我们还扩展了跟踪工具。该工具已扩展到可以转储所有类型操作数(常规寄存器、统一寄存器、谓词寄存器、立即数等)的 ID。另一个重要的扩展是能够获取所有指令的控制位,因为 NVBit 无法提供对这些控制位的访问。这是通过在编译时使用 CUDA 二进制实用工具获取 SASS(英伟达汇编语言)代码来实现的。这意味着要修改应用程序的编译方式,以便在编译时生成依赖于微架构的代码,而不是使用即时编译方法。不幸的是,对于少数内核(它们都属于 Deepbench),英伟达工具无法提供 SASS 代码,这就导致无法获取这些指令的控制位。为了模拟这些应用程序,我们对依赖关系采用混合模式,即在没有 SASS 代码的内核中采用传统的计分板方法;否则,就使用控制位。


我们扩展了该工具,使其能够通过描述符捕获对常量缓存或全局内存的访问。尽管有人声称后一种类型的内存访问是在 Hopper 架构中引入的,但我们注意到 Ampere 架构已经在使用它们了。描述符是一种对内存引用进行编码的新方式,它使用两个操作数。第一个操作数是一个统一寄存器,用于对内存指令的语义进行编码,而第二个操作数则对地址进行编码。我们扩展了跟踪器以捕获地址。遗憾的是,统一寄存器中编码的行为仍未被跟踪到。

我们计划公开对 Accel-sim 框架所做的所有模拟器和跟踪器更改。

验证

在本节中,我们评估所提出的 GPU 核心微架构的准确性。首先,在 7.1 小节中描述所采用的方法。然后,在 7.2 小节中对设计进行验证。接着,在 7.4 小节中研究寄存器文件缓存和寄存器文件读端口数量对准确性和性能的影响。随后,在 7.3 小节中探讨设计中两个不同组件(如指令预取器)的影响,并在 7.5 小节中分析依赖检查机制。最后,在 7.6 小节中讨论该模型如何无缝适配除安培架构之外的其他英伟达架构。

1

方法

我们通过将模拟器的结果与在真实 GPU 上获得的硬件计数器指标进行比较,来验证所提出的 GPU 核心的准确性。我们使用了四种不同的安培架构GPU,其规格如表 4 所示。所有 GPU 均使用 CUDA 11.4 和 NVBit 1.5.5。我们还将我们的模型 / 模拟器与原始的 Accel-sim 模拟器框架进行比较,因为我们的模型是基于它构建的。


我们使用了来自 12 个不同套件的多种基准测试。所用套件列表以及应用程序数量和不同输入数据集的数量可在表 3 中找到。总体而言,我们使用了 143 个基准测试,其中 83 个是不同的应用程序,其余的只是改变了输入参数。

2

性能准确性

表 4 展示了两种模型(我们的模型和 Accel-sim 模型)相对于每个 GPU 真实硬件的平均绝对百分比误差(MAPE)。可以看出,在所有评估的 GPU 中,我们的模型比 Accel-sim 模型准确得多,对于最大的 GPU—— 英伟达 RTX A6000,我们模型的 MAPE 不到 Accel-sim 模型的一半。在相关性方面,两种模型非常相似,但我们的模型略胜一筹。


图 5 展示了英伟达 RTX A6000 以及 143 个基准测试在两种模型下的绝对百分比误差(APE),这些基准测试按照每个模型的误差从小到大排序。可以看到,对于所有应用程序,我们的模型始终比 Accel-sim 模型具有更低的绝对百分比误差,并且在一半的应用程序中,差异相当显著。此外,可以观察到 Accel-sim 模型在 10 个应用程序上的绝对百分比误差大于或等于 100%,在最坏的情况下达到了 543%,而我们的模型绝对百分比误差从未超过 62%。如果将第 90 个百分位数作为尾部准确性的指标,Accel-sim 模型的绝对百分比误差为 82.64%,而我们的模型为 31.47%。这证明了我们的模型比 Accel-sim 模型更加准确和可靠。

3

指令预取的敏感性分析

流缓冲区指令预取器的特性对全局模型的准确性有很大影响。在本节中,我们分析了不同配置下的误差,包括禁用预取器、使用完美指令缓存,以及使用大小为 1、2、4、8、16 和 32 条目的流缓冲区预取器。所有配置均基于英伟达 RTX A6000。每个配置的 MAPE 如表 5 所示。可以看出,大小为 16 的流缓冲区能获得最佳的准确性。

从表 5 中的加速比结果中,我们还可以得出另一个结论:在 GPU 中,像流缓冲区这样简单直接的预取器的性能与完美指令缓存相近。这是因为每个子核心中的不同线程束通常执行相同的代码区域,并且典型的通用计算 GPU(GPGPU)应用程序的代码控制流并不复杂,所以预取接下来的 N 行代码通常效果良好。需要注意的是,由于 GPU 不进行分支预测,因此实施 Fetch Directed Instruction 预取器并不值得,因为这需要添加一个分支预测器。


关于模拟准确性,我们得出结论:在不研究指令缓存增强的情况下,使用完美指令缓存通常能在保证相当准确性的同时提高模拟速度。然而,对于控制流较为关键的基准测试,如 dwt2d、lud或nw,使用完美指令缓存或不使用流缓冲区会导致显著的不准确(与使用完美指令缓存相比,差异超过 20%;与不使用预取的指令缓存相比,差异超过 200%)。这种不准确是因为完美指令缓存无法捕捉到频繁在不同代码段之间跳转所带来的性能损失,而不使用预取则会过度惩罚程序其他部分的执行,这也表明这些基准测试仍有改进的空间。

4

寄存器文件架构的敏感性分析

表 6 展示了寄存器文件缓存的存在以及每个存储体增加寄存器文件读端口数量对模拟准确性和性能的影响。它还展示了在理想情况下(所有操作数都能在单个周期内获取)的结果。所有配置在所有基准测试中的平均性能和准确性相似。然而,对特定基准测试(如计算密集型的 MaxFlops(Accel-sim GPU Microbenchmark)和配置为 sgemm 参数的 Cutlass)进行更深入的研究,会发现更细微的差异。这两个基准测试都严重依赖固定延迟的算术指令,这些指令对寄存器文件访问限制导致的停顿特别敏感,因为它们通常每条指令使用三个操作数。


对于 MaxFlops,无论 RFC 是否存在,性能都是相同的,因为只有一条静态指令使用它。值得注意的是,当每个寄存器文件存储体使用两个读端口时,性能显著提高了约 44%。考虑到每条指令通常使用三个操作数,而四个读端口(每个存储体两个)足以满足需求,这种改进是合理的。相比之下,从模拟准确性的角度来看,两个读端口的配置存在显著偏差。

在配置为 sgemm 的 Cutlass 中,没有寄存器文件缓存的单端口配置导致性能大幅下降(0.78 倍)。这一性能下降与观察到的程序中 35.9% 的静态指令至少在一个操作数中使用寄存器文件缓存的情况一致。然而,引入每个存储体有两个读端口的寄存器文件后,性能提高了 12%,这表明寄存器文件及其缓存的组织仍有改进的空间。

总之,寄存器文件架构(包括其缓存)对单个基准测试有重要影响,因此对其进行准确建模非常重要。每个存储体一个端口加上一个简单的缓存,平均性能接近具有无限制数量端口的寄存器文件,但对于某些个别基准测试,差距仍然很大,这表明这可能是一个有趣的研究领域。

5

依赖管理机制分析

在本小节中,我们分析了本文所解释的软硬件依赖处理机制对性能和面积的影响,并将其与前几代 GPU 使用的传统计分板方法进行比较。表 7 展示了这两个指标的结果。面积开销是相对于一个 SM 的常规寄存器文件面积(256KB)报告的。

基于传统计分板的机制需要与可写入的寄存器数量相同的条目数,即每个线程束 332 个条目(255 个用于常规寄存器,63 个用于统一寄存器,7 个用于谓词寄存器,7 个用于统一谓词寄存器)。此外,需要两个计分板:一个用于检测写后写(WAW)/ 写后读(RAW)冲突,另一个用于检测读后写(WAR)冲突。这是因为尽管指令按顺序发出,但由于可变延迟指令的存在,操作数的读写可能会乱序进行。例如,像内存指令这样的可变延迟指令在发出后会被排队,并可能在较年轻的算术指令写入结果后读取其源操作数,如果前者的源操作数与后者的目的操作数相同,就会发生读后写冲突。虽然第一个计分板每个条目只需要一位,但随着每个条目的消费者数量增加,第二个计分板需要更多的硬件资源。假设每个条目支持多达 63 个消费者,单个线程束将需要 2324 位(332 + 332×log2 (63 + 1))来处理依赖关系。对于整个 SM,这相当于 111,552 位,占寄存器文件大小的 5.32%。

相比之下,本文提出的软硬件机制需要六个 6 位的依赖计数器、一个 4 位的停顿计数器和一个 yield 位。这相当于每个线程束仅 41 位,每个 SM 为 1968 位。在开销方面,这仅占寄存器文件大小的 0.09%,远低于计分板方法。

总之,基于控制位的软硬件协同设计优于其他替代方案,并且其面积开销可以忽略不计。在支持每个 SM 多达 64 个线程束的 GPU(如英伟达 Hopper)中,与计分板相比,这种开销差异更加显著。对于控制位方案,其开销为 0.13%,而对于具有 63 个消费者的计分板机制,开销为 7.09%。此外,我们发现使用由两个计分板组成的计分板机制(一个用于 RAW/WAW 冲突,另一个用于最多支持 63 个消费者的 WAR 冲突)在模拟准确性上与使用控制位相当。因此,对于那些不公开控制位值的应用程序(如 Deepbench 基准测试套件中的某些内核),这是一个有效的替代方案。

6

对其他英伟达架构的适用性

在本文中,我们主要关注英伟达安培架构。然而,我们在文中揭示的研究结果也适用于其他架构,如图灵架构。除了报告四个安培架构 GPU 的结果外,表 4 还展示了一个图灵架构 GPU 的结果,对于英伟达 RTX 2080 Ti,我们的模型相对于 Accel-sim 的 MAPE 提高了 6.94%。

虽然我们已经在图灵架构和安培架构上验证了该模型,但我们认为我们的研究结果仍然适用于其他英伟达架构。英伟达的公开声明和 SM 架构图表明,重大的架构变化主要集中在张量核心、光线追踪单元的增强,以及同一 TPC 中 SM 之间的分布式共享内存等小功能上。尽管如此,为了使模型适用于这些其他架构,仍需要估计一些指令(如内存指令)的延迟。

相关工作

在学术界和工业界,模拟器是评估计算机架构设计理念的主要工具,因为评估新设计的成本较低。此外,模拟器也是检验设计是否接近实际硬件的有效工具。通用计算 GPU 也不例外,像英伟达这样的领先厂商已经公开了部分内部模拟器(英伟达架构模拟器,NVArchSim 或 NVAS)的创建过程。在学术领域,有两个流行的开源模拟器。第一个是 MGPUSim,它模拟 AMD GCN 3 架构,并针对支持虚拟内存的多 GPU 系统。另一个替代模拟器是 Accel-Sim 框架,这是一个周期精确的、最先进的跟踪驱动模拟器,支持 CUDA 应用程序,模拟类似英伟达的现代架构,它基于早期的 GPGPU-Sim 3 模拟器。

在文献中,有许多研究致力于逆向工程 CPU 的架构组件,例如英特尔的分支预测器或英特尔的缓存设计等。

关于英伟达 GPU,已经有多项研究致力于揭示其特定组件的信息。Ahn 等人和 Jin 等人分别针对英伟达 Volta 和安培架构的片上网络进行逆向工程。Lashgar 等人研究了英伟达 Fermi 和 Kepler 架构处理内存请求的能力。Jia 等人介绍了 Volta 和 Turing 架构的一些缓存特性,如缓存行大小和关联性、指令延迟以及寄存器文件的一些细节。Khairy 等人探索了 Volta 架构的 L1 数据缓存和 L2 缓存设计。Abdelkhalik 等人建立了安培架构中 PTX 和 SASS 指令之间的关系及其执行延迟。关于张量核心,不同的研究 31, 43, 44, 53, 54, 72, 82, 91 对其可编程性和微架构进行了研究。Zhang 等人对 Turing 和 Ampere 架构的 TLB 进行了逆向工程,目的是对多实例 GPU 进行攻击。关于现代英伟达 GPU 架构的控制流,Shoushtary 等人为 Turing 架构的控制流指令定义了一种合理的语义,与实际硬件跟踪相比,误差仅为 1.03%。Amert 等人研究了英伟达 Jetson TX2 中的 GPU 任务调度器以及它与 ARM CPU 的交互。最后,Wong 等人描述了早期 Tesla 架构的许多组件,如缓存、TLB、SIMT 控制流行为和协作线程阵列(CTA)屏障。

在以往针对其他 GPU 厂商的研究中,Gutierrez等人指出,在模拟过程中,直接使用 AMD GPU 的机器指令集架构(ISA)而非中间语言,对于准确评估瓶颈和潜在解决方案至关重要。此外,GAP 工具能够识别真实的 AMD GPU 与在 gem5中模拟的 GPU 之间的差异,这使得 gem5 中 AMD GPU 模拟的准确性得到了提升。最后,Gera等人引入了一种模拟框架,并对英特尔的集成 GPU 进行了特性描述。

编译器提示(也称为控制位)至少从英伟达开普勒(Kepler)架构起就已在 GPU 中使用。格雷(Gray)等人发表了对这些控制位的描述。在开普勒、麦克斯韦(Maxwell)和帕斯卡(Pascal)架构中,每 3 到 7 条指令中通常就有一条是编译器插入的提示指令。贾(Jia)等人指出,诸如 Volta 或 Turing 等较新的架构已将指令位宽从 64 位增加到 128 位。因此,新架构不再通过特定指令来实现提示功能,而是在每条指令中都包含提示位。这些位不仅旨在提升硬件性能,还能通过防止数据冲突来确保程序的正确性。CPU 也会利用编译器提示,帮助硬件更好地决定如何使用不同的资源。这些提示用于支持 CPU 的不同组件,如数据旁路、分支预测和缓存等。

据我们所知,我们的工作首次揭示了现代英伟达 GPU 的核心微架构,并开发出了精确的微架构模拟模型。我们在研究中发现的一些新特性包括:对控制位语义的完整定义以及支持这些控制位的微架构变化、指令发射调度器的行为、寄存器文件及其相关缓存的微架构,以及内存流水线的多个方面。这些方面对于准确建模现代英伟达 GPU 至关重要。

结论

本文通过在真实硬件上进行逆向工程,揭示了行业内现代英伟达 GPU 的微架构。我们剖析了指令发射阶段的逻辑,包括分析线程束的就绪条件,并发现线程束之间的指令发射调度器遵循编译器引导的贪心优先且最年轻优先(CGGTY)策略。此外,我们揭示了寄存器文件的不同细节,例如端口数量及其宽度。我们还阐释了寄存器文件缓存的工作原理。此外,本文展示了内存流水线的一些重要特性,如加载 / 存储队列的大小、子核心之间的竞争,以及内存指令粒度访问如何影响延迟。此外,我们分析了取指阶段,并提出了一种符合现代英伟达 GPU 要求的取指方案。

此外,本文通过整理、详细解释和扩展之前关于控制位的公开信息,对其进行了总结。

我们还在模拟器中对所有这些细节进行建模,并将这个新模型与真实硬件进行比较,结果表明,该模型在周期准确性方面比以前的模型提高了 18.24% 以上,更接近实际情况。

此外,我们证明了在 GPU 中使用基于简单流缓冲区的指令预取在模拟准确性和性能方面表现良好,接近完美的指令缓存。我们还展示了现代英伟达 GPU 中基于控制位的依赖管理机制如何优于其他方案,如传统的计分板机制。

最后,我们研究了寄存器文件缓存和寄存器文件读端口数量对模拟准确性和性能的影响。

总体而言,我们可以得出结论,GPU 是硬件与编译器的协同设计,其中编译器指导硬件处理依赖关系,并引入可以提高性能和能效的提示信息。

致谢本文作者:

Rodrigo Huerta、José-Lorenzo Cruz、Mojtaba Abaie Shoushtary和Antonio González

半导体精品公众号推荐

专注半导体领域更多原创内容

关注全球半导体产业动向与趋势

*免责声明:本文由作者原创。文章内容系作者个人观点,半导体行业观察转载仅为了传达一种不同的观点,不代表半导体行业观察对该观点赞同或支持,如果有任何异议,欢迎联系半导体行业观察。

今天是《半导体行业观察》为您分享的第4034期内容,欢迎关注。


『半导体第一垂直媒体』

实时 专业 原创 深度

公众号ID:icbank

喜欢我们的内容就点“在看”分享给小伙伴哦

半导体行业观察

2025-05-14

半导体行业观察

2025-05-14

半导体行业观察

2025-05-14

半导体行业观察

2025-05-14

半导体行业观察

2025-05-14

半导体行业观察

2025-05-14

证券之星资讯

2025-05-14

首页 股票 财经 基金 导航