计算机组成与设计学习——GPU篇(四):多线程多处理器架构

为满足不同市场细分的需求,GPU 实现了可扩展数量的多处理器结构——实际上,GPU 本身就是由多个多处理器组成的多处理器。此外,每个多处理器都高度支持多线程,能够高效地执行大量细粒度的顶点着色器线程和像素着色器线程。一款基础质量优良的 GPU 拥有两到四个多处理器,而专为游戏发烧友或计算平台设计的 GPU 则拥有几十个这样的多处理器。本节将关注其中一个多线程多处理器架构,即 NVIDIA Tesla 流式多处理器(SM)的一个简化版本。

为何选择使用多处理器而非多个独立处理器?因为在每个多处理器内部的并行性可以提供局部高性能,并且支持 B.3 节所述的细粒度并行编程模型进行广泛多线程处理。一个线程块中的各个线程在一个多处理器内共同执行,以便共享数据。我们在此描述的多线程多处理器设计采用了一种紧密耦合架构,拥有八个标量处理器核心,最多可执行 512 个线程)。为了提高面积效率和功率效率,多处理器让八个处理器核心共享一些大型复杂单元,包括指令缓存、多线程指令单元以及共享内存 RAM。

大规模多线程

GPU 处理器高度支持多线程以实现多个目标:

  • 掩盖来自 DRAM 的内存加载和纹理获取延迟:由于 GPU 通常配备小型流式缓存而非类似 CPU 的大容量工作集缓存,因此内存和纹理获取的延迟可能需要数百个处理器时钟周期。一次获取请求通常需要经历完整的 DRAM 访问延迟以及互联和缓冲延迟。通过多线程技术,在一个线程等待加载或纹理获取完成的同时,处理器可以执行其他线程,从而利用计算有效覆盖延迟。
  • 支持细粒度并行图形着色器编程模型:为图形处理提供大量的独立线程,即使单个线程面临较长的内存延迟,也可以保证许多处理器保持繁忙状态,高效执行任务。
  • 支持细粒度并行计算编程模型:在计算领域同样提供了数千个独立线程,确保处理器资源得到充分利用。
  • 虚拟化物理处理器为线程和线程块,以提供透明的可扩展性:将物理处理器抽象为线程和线程块,使得程序能够在不同规模的硬件上透明地进行扩展。
  • 简化并行编程模型至编写单一线程的串行程序:通过多线程机制,程序员只需关注单一线程的逻辑,即可轻松应对大规模并行计算场景,简化了编程难度。

图形顶点着色器或像素着色器程序是为单个线程设计的,用于处理一个顶点或一个像素。同样地,CUDA 程序是一个 C 语言程序,针对单个线程计算某个结果。图形渲染和计算程序会实例化许多并行线程,以绘制复杂的图像和计算大规模的结果数组。

为了动态平衡不断变化的顶点着色器和像素着色器工作负载,每个多处理器同时执行多个不同的线程程序以及不同类型着色器程序。为了支持图形着色语言中的独立顶点、图元及像素编程模型,以及 CUDA C/C++ 的单线程编程模型,GPU 中的每个线程都拥有自己的私有寄存器、每线程私有内存、程序计数器和线程执行状态,并能够执行独立的代码路径。

为了高效执行数百个并发轻量级线程,GPU 多处理器采用了硬件多线程技术——它在硬件层面管理并执行数百个并发线程,且无需调度开销。同一线程块内的并发线程可以通过一条指令在屏障处进行同步。轻量级线程创建、零开销线程调度和快速屏障同步有效地支持了极细粒度的并行计算。

多处理器架构

统一的图形与计算多处理器可以执行顶点、几何和像素片段着色器程序,以及并行计算程序。如图B.4.1所示,一个示例多处理器由8个标量处理器(SP)核心组成,每个核心都配备了大型多线程寄存器文件(RF)、两个特殊功能单元(SFUs)、一个多线程指令单元、指令缓存、只读常量缓存和共享内存。

16 KB的共享内存用于存储图形数据缓冲区和共享计算数据。在CUDA中声明为__shared__的变量就驻留在共享内存中。为了实现逻辑图形流水线工作负载通过多处理器多次映射处理,如B.2节所述,顶点、几何和像素线程具有独立的输入和输出缓冲区,并且它们的工作负载独立于线程执行到来和完成。

每个SP核心包含标量整数和浮点运算单元,能够执行大部分指令。SP采用硬件多线程技术,支持多达64个线程。每个流水线化的SP核心每时钟周期能为其所分配的每个线程执行一条标量指令,时钟频率根据不同GPU产品范围从1.2 GHz至1.6 GHz不等。每个SP核心配备有1024个32位通用目的寄存器的大容量RF,这些寄存器在分配给它的线程间进行划分。程序会声明其对寄存器的需求,通常每个线程需要16到64个标量32位寄存器。SP能够同时运行使用少量寄存器的许多线程,或者使用更多寄存器的较少线程。编译器会对寄存器分配进行优化,以平衡寄存器溢出的成本与减少线程数的成本之间的关系。

像素着色器程序通常使用16个或更少的寄存器,使得每个SP最多可以同时运行64个像素着色器线程来覆盖长延迟的纹理获取操作。而编译后的CUDA程序通常每个线程需要32个寄存器,这限制了每个SP只能运行32个线程,因此在该示例多处理器上,这样的内核程序每个线程块最多只能启动256个线程,而非最大可支持的512个线程。
在这里插入图片描述

图B.4.1 这种多线程多处理器配置有八个标量处理器(SP)核心。这八个SP核心各自配备了一个大型的多线程寄存器文件(RF),并且它们共同分享一个指令缓存、一个多线程指令发布单元、一个常量缓存、两个特殊功能单元(SFUs)、一个互连网络以及一个多库共享内存。

流水线式的特殊功能单元(SFUs)执行线程指令,用于计算特殊函数以及从基本顶点属性插值像素属性。这些指令可以与SPs上执行的其他指令并发执行。SFU的详细描述将在后续内容中给出。

多处理器通过纹理接口在纹理单元上执行纹理获取指令,并通过内存接口处理对外部内存的加载、存储和原子访问指令。这些指令也可以与SPs上的指令并发执行。对共享内存的访问则利用了SP处理器与共享内存库之间的低延迟互连网络。

单指令多线程(SIMT)

为了高效管理和执行数百个运行多个不同程序的线程,多处理器采用单指令多线程(single-instruction multiple-thread,SIMT)架构。它以称为“”(warp)的形式创建、管理、调度和执行并发线程。术语“束”来源于纺织业,是最早的并行线程技术。图B.4.2中的照片显示了一束从织布机中产生的并行线程。本示例多处理器使用32个线程的SIMT束大小,在四个时钟周期内,八个SP核心中的每一个都会执行四条线程。在B.7节描述的Tesla SM多处理器同样采用了32个并行线程的束大小,为像素线程和计算线程提供效率时,每个SP核心会执行四个线程。

SIMT:一种处理器架构,该架构能够将一条指令并行应用于多个独立线程。
wrap:在SIMT架构中,一组并行线程同时执行同一指令的集合。

线程块由一个或多个束组成。此示例SIMT多处理器管理16个束的池,总共包含512个线程。构成同一束的各个并行线程类型相同,同时从同一程序地址开始执行,但除此之外,它们可以自由分支并独立执行。在每次指令发出时,SIMT多线程指令单元选择准备执行下一条指令的一个束,并将该指令发送给该束中活跃的线程。SIMT指令会同步广播到束中活跃的并行线程;由于独立分支或预判条件,个别线程可能处于非活动状态。在此多处理器中,每个SP标量处理器核心使用四个时钟周期对一个束中的四个独立线程执行指令,反映出束线程与核心之间的4:1比例。
在这里插入图片描述

图B.4.2 在SIMT(单指令多线程)架构中,采用多线程束调度机制。这种调度器会选取一个就绪的束(warp),并将指令同步发送给构成该束的所有并行线程。由于不同的束是相互独立的,所以调度器每次可以选择不同的束进行指令发布。

SIMT处理器架构类似于单指令多数据(SIMD)设计,SIMD在一个指令作用于多个数据通道上,但SIMT的不同之处在于其将一个指令应用于多个并行且独立的线程,而不仅仅是多个数据通道。对于SIMD处理器,一个指令控制一组多个数据通道,而对于SIMT处理器,一个指令控制一个独立线程,SIMT指令单元为效率向一束独立并行线程发布指令。

当束中所有线程采取相同的执行路径时,SIMT处理器能实现完全效率和性能。如果通过数据依赖性条件分支导致束中的线程分歧,则每条分支路径上的执行将会串行化,当所有路径完成时,线程会收敛到相同的执行路径。对于长度相等的路径,分歧的if-else代码块效率为50%。多处理器使用分支同步栈来管理分歧和收敛的独立线程。无论不同的束是否执行公共或不重叠的代码路径,它们都能以全速独立执行。因此,相比早期GPU,SIMT GPU在分支代码上的效率和灵活性显著提升,因为其束宽度远小于先前GPU的SIMD宽度。

与SIMD向量架构相比,SIMT允许程序员为单个独立线程编写线程级别的并行代码,同时也支持许多协调线程的数据并行代码。出于程序正确性的考虑,程序员基本上可以忽略束的SIMT执行属性;然而,通过确保代码尽量避免让束中的线程分歧,可以获得实质性的性能提升。实际上,这类似于传统代码中缓存行的作用:设计正确性时可以安全地忽略缓存行大小,但在追求峰值性能时必须将其纳入代码结构的考量之中。

SIMT束执行与分歧

SIMT调度独立束的方法相比早期GPU架构的调度更为灵活。一个束由相同类型的并行线程组成,如顶点、几何、像素或计算线程。像素片段着色器处理的基本单元是作为四个像素着色器线程实现的2x2像素四边形。多处理器控制器将像素四边形单元打包成束,并以类似方式将顶点和图元组合成束,同时还将计算线程打包成束。一个线程块包含一个或多个束。

SIMT设计在束中并行线程间高效共享指令获取和发布单元,但要求有一个完整的活跃线程束才能获得最高的性能效率。这种统一的多处理器能够并发地调度和执行多种类型的束,从而实现顶点和像素束的同时执行。其束调度器运行速度低于处理器时钟频率,因为每个处理器核心有四个线程通道。在每次调度周期中,它选择一个束来执行SIMT束指令,如图B.4.2所示。发出的束指令会在四个处理器周期内作为四组八条线程执行。处理器流水线需要几个时钟周期的延迟来完成每条指令。如果活跃束的数量乘以每个束的时钟数超过了流水线延迟,则程序员可以忽略流水线延迟。对于这款多处理器而言,八个束的循环调度使得同一束中的连续指令之间间隔32个周期。如果程序能够在每个多处理器上保持256个活跃线程,那么最多32个周期的指令延迟就可以从单个顺序线程中隐藏起来。然而,当活跃束较少时,处理器流水线深度会变得明显,可能导致处理器停顿。

一个具有挑战性的设计问题是如何实现在不同束程序和程序类型动态混合下的零开销束调度。指令调度器必须每隔四个时钟周期选择一个束以每时钟周期为每个线程发出一条指令,相当于每个处理器核心的IPC(每周期指令数)为1.0。由于束是独立的,唯一的依赖关系来自于同一束内的顺序指令。调度器使用寄存器依赖计分板来筛选出活动线程准备好执行指令的束。它优先考虑所有这样的就绪束,并选择优先级最高的一个进行发布。优先级设定必须考虑到束类型、指令类型以及对所有活跃束公平的需求。

管理线程和线程块

多处理器控制器和指令单元负责管理线程和线程块。控制器接收工作请求和输入数据,并仲裁对共享资源的访问,包括纹理单元、内存访问路径和I/O路径。对于图形工作负载,它同时创建和管理三种类型的图形线程:顶点、几何和像素线程。每种图形工作类型都有独立的输入和输出路径。控制器将这些输入工作类型累积并打包成执行相同线程程序的SIMT束(即并行线程)。它分配一个空闲束,为束中的线程分配寄存器,并在多处理器中启动束执行。每个程序都会声明其每个线程所需的寄存器数量;控制器仅在能够为束线程分配请求的寄存器数量时才会启动一个束。当束中的所有线程退出时,控制器会解包结果并释放束寄存器和其他资源。

控制器创建协同线程阵列(cooperative thread arrays,CTA),实现CUDA线程块作为一或多个并行线程束。当能够创建所有CTA束并分配所有CTA资源时,它就会创建一个CTA。除了线程和寄存器外,CTA还需要分配共享内存和屏障。程序会声明所需容量,而控制器会在能够分配这些容量后才启动CTA。然后,它按照束调度速率创建CTA束,以便CTA程序立即以全多处理器性能开始执行。控制器监控CTA中所有线程何时退出,并释放CTA共享资源及其束资源。

协同线程阵列:一组并发线程,它们执行相同的线程程序,并可能协同计算一个结果。在CUDA编程模型中,CTA实现了一个CUDA线程块

  • 29
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值