百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

对 GPU 内部工作原理的通俗介绍

bigegpt 2024-09-03 11:12 3 浏览

更多互联网精彩资讯、工作效率提升关注【飞鱼在浪屿】(日更新)

本文总结了 GPU 执行方式的一些较底层级别的情况。尽管与 CPU 相比,GPU 编程并没有那么复杂,但它也与硬件在做什么并不完全匹配。原因是我们不能在没有一些 API 的情况下只对 GPU 进行编程。几年来,我们有了像 DirectX 12 或 Vulkan 这样的现代显式 API,它们缩小了与硬件发生的事情的差距。然而,仍然有一些底层值得解释。

虽然这篇文章不是关于全部图形或计算 API,但我会使用一些来自 Vulkan 的名称,主要是因为它是唯一的现代和多平台API。


GPU 部分 - 快速回顾

以尽可能底层的级别列出 GPU 最重要的组成部分:

  • FP32 单元
  • FP64 和/或 SPU(专用处理单元)
  • INT32 单位
  • 寄存器
  • 片上存储器:L0(消费级别硬件很少见)、L1层(每个计算单元的共享内存)、L2层(每个GPU的存储)
  • 片外存储器(设备存储器)
  • 指令调度器
  • 指令分发器
  • TMU、ROP - 固定管道单元

这些都分为许多层次结构,最终组合成了图形卡。


GPU 是一种重要的异步等待机制

它比CPU 端的任何东西都要复杂得多。但是,逻辑仍然一样。分派一些工作并在做其他事情的同时等待结果。需要同步确保事情是否已完成。Vulkan 中的 Fence 是同步 CPU 和 GPU 的主要机制。


GPU 中的核心

GPU 世界中所谓的core更多是一个营销术语。现实要复杂得多。在Nvidia显卡称为CUDA core或者AMD的GPU称为core,是非常简单的运行浮点运算的单元。不像 CPU 那样做花哨的事情(例如:分支预测、乱序执行、推测执行/数据获取)。它们不能独立运行。它们与一堆邻居有关(比如Scalar vs Vector)。现在开始,我们称它们为着色单元。但我们还应该提到一个更大的硬件结构,它包含许多这样的core:计算单元。这个具有更多通常 CPU core包含的功能,如缓存、寄存器等。还有一些特定于 GPU 的元素,例如调度程序、分派程序、ROP、TMU、插值器、混合器等等。虽然这可能类似于 CPU,但着色单元和计算单元都不应该被视为core。着色单元太简单了,但计算单元要多得多。

每个供应商都有自己的计算单元命名:

英伟达使用 SM(流式多处理器)

AMD 使用 CU(计算单元)和 WGP(自 RDNA 架构以来的工作组处理器)

Intel 使用 EU(执行单元)和 Xe Core(从新的 Arc 架构开始)

从图灵架构开始,还有两种新的内核:RT Cores 和 Tensor Cores。RT Cores加速 BVH(Bounding Volume Hierarchy), Tensor Cores加速 FMA 对精度较低的矩阵的操作(通常可以忽略处理机器学习的 32 位浮点数)。


并发 vs 并行

现在我们知道了计算单元由什么组成。谈谈其中的 1 个误解。在很多文章中并发和并行很容易互换。一般来说,SPMT(单程序多线程)可以忽略这种差别。GPU 供应商不会公开 SM/CU 内部的内容(除了共享内存大小和工作组大小),特别是有多少着色单元。这就是为什么从着色器编写者的角度来看没有任何区别的主要原因。但是,让我们了解一下将工作分派给 GPU 时实际发生的情况。我们将使用来自 Nvidia 的图灵架构的流式多处理器/SM图解释:

每个这样的 SM 都有 64 个 FP32 着色单元。假设我们分派 4 个独立子组(128 个线程)的工作,这些子组仅包含单精度浮点数。第一个和第二个子组(64 个线程)可以并行执行,因为有足够的硬件着色单元来覆盖它们。第三个和第四个将被提取到寄存器中并等待前一个完成/停止。如果是停顿,则第三次和第四次同时执行到第一和第二。当然,可能会发生例如只有第 2 个子组会停止,然后第 1 个和第 3 个可能并行执行。在大多数情况下,这种区别在编写代码时不会给您带来任何优势,但现在应该很清楚,并行和并发两种情况发生在 GPU 上意味着不同的事情。

如果和CPU对比,那么 GPU 上的并发是一个类似于 SMT 和超线程的概念,但规模不同。


最小的工作单元

一旦准备好数据,我们就可以将其分派到工作组中。工作组是包含作为一个整体提交的至少 1 个工作单元(与 1 个硬件线程匹配)的结构。它可以是 1 个或数千个操作。但是对于 GPU 来说,我们提供多少数据并不重要,因为它们都被分成与底层硬件匹配的组......


最小的执行单元不是最小的工作单元

......这些组每个供应商都有不同的名称:

  • Warp - 在 Nvidia 上
  • Wave(fronts) - 在 AMD 上
  • Wave - 使用 DX12 时
  • Subgroup - 使用 Vulkan 时(自 1.1 起)

Subgroup子组长度因硬件供应商而异。AMD 在 Vega 卡上有 64 个浮点数,Navi使用 32/64 组合。Nvidia 使用 32 个浮点数。英特尔可以在 8/16/32 配置下运行。这些子组大小对于理解差异至关重要:虽然最小的工作单元确实是 1 个线程,但我们的 GPU 将运行至少工作组大小的线程来执行它!鉴于 GPU 是为大量数据而设计的,这实际上非常快。所有与子组不完全匹配的非最佳数据组合都通过一种称为延迟隐藏的机制来缓解。

还值得一提的是,以块的形式处理数据,因为光栅单元将数据输出为四边形(而不是单独的像素),而历史上 GPU 只能处理图形管道。稍后采样器使用的导数需要四边形。如今硬件生产商努力平衡子组的大小,因为:

较小的子组意味着较小的发散成本,但也降低了内存合并(效率)

更大的子组意味着代价高昂的分歧,但会增加记忆合并(灵活性)


注册文件与缓存

在描述什么是延迟隐藏之前,需要了解 1 个关键的硬件因素:寄存器大小。如果只有一个方面了解 GPU 和 CPU 之间的区别,那就是:GPU寄存器文件比缓存大!以 Nvidia 的 RTX 2060 为例。每个 SM 有一个大小为 256KB 的寄存器,同时 L1/共享内存只有96KB。


延迟隐藏

知道了 GPU 寄存器有多大,现在就可以理解 GPU 在处理大量数据时如此高效的原因。大多数工作是并发执行的。即使子组中只有 1 个线程必须等待某些东西(例如:内存获取),GPU 也不会等待它。整个子组被标记为stalled/停滞。从存储在寄存器中的子组池中执行另一个符合条件的子组。一旦前一个完成阻止它的操作,它就会再次重新运行以完成工作。在实际情况下,它发生在数千个子组中。如果我们等待某事(延迟),能够立即切换到另一个子组对 GPU 至关重要。它通过滚动执行另一组符合条件的子组来隐藏等待时间。

一些命名法:

active Subgroup - 正在执行的子组

resident Subgroup/Subgroup in flight - 它存储在寄存器中

eligible Subgroup - 它存储在寄存器中并标记为准备运行或重新运行


使用率

简单解释:GPU 饱和度的比率。

使用率不是我们利用GPU的好坏!这是有多少常驻子组(隐藏延迟的机会)。即使在占用率较低的情况下,ALU 也可以承受最大压力,但在这种情况下,将很快失去良好的利用率。

详细解释:假设某个计算单元可以有 32 个常驻子组(寄存器文件的全部容量)。现在派遣 32 个完全独立的工作子组来使其完全饱和。给定 32 个可用的子组,即使 31 个子组被停止,我们仍然有机会通过执行第 32 个来隐藏延迟。基本上就是这样:您为所有可能的Subgroup in flight派遣了多少独立子组的比率。

现在假设分派 8 个着色器,每个着色器使用 128 个线程(涵盖硬件中的 4 个子组)。但是这次这些着色器需要一起处理(如果它们是完全独立的,那么驱动程序会将它们分成 4 个独立的子组,有效地创建与上述完全相同的情况)。换句话说,这次每个线程需要4个寄存器。计算单元保持不变,所以它仍然只有 32 个可能的子组在运行。现在发生的事情是隐藏延迟的可能性减少了 4 倍(切换到另外 4 个子组),因为现在每个工作组都大 4 倍。这意味着我们现在的入住率只有 25%。这里有一个重要的概念要记住:增加每个子组的寄存器使用量会降低整体占用率. 当然,除非您拥有结构良好的数据,否则需要分派的着色器几乎永远不会以 100% 的占用水平结束。把它推向极端:如果你调度 1 个填充计算单元整个寄存器大小的大着色器,那么当某些东西停顿时没有什么可以切换(没有延迟隐藏的可能性)。


套准压力和套准溢出

进一步地看一下前面的例子。如果每个线程都需要更多的寄存器空间怎么办?每次增加所需寄存器空间相当的线程要求时,也会增加寄存器压力。寄存器压力低没什么可担心的,只会降低占用率(这也不错,直到总是有隐藏延迟的工作)。但是一旦开始增加每个线程的寄存器使用量,我们将不可避免地达到驱动程序溢出的压力水平。寄存器溢出是将应存储在寄存器中的数据移动到 L1/L2 缓存和/或片外存储器(Vulkan 中的设备存储器)的过程。如果占用率真的很低,驱动程序可能会这样做,保持一些隐藏延迟机会,代价是内存访问速度变慢。

附带说明:虽然溢出到片上存储器实际上可能会提高性能4,但在溢出到片外存储器时这一点并不明显。


标量与向量

上面说我们不能执行小于工作组大小的工作。实际上还更复杂。AMD 硬件本质上是一个载体。这意味着它具有用于向量运算和标量运算的单独单元。另一方面,英伟达本质上是标量意味着子组可以处理其他类型的混合(主要是 F32 和 INT32 的组合)。

我们实际上可以执行比 Subgroup 小的工作?正确答案是:不能,但调度器可以。划分需要执行的工作取决于调度程序。调度程序仍然会采用至少子组大小的标量数量的事实,它只是不会将它用于任何场景。AMD 必须使用整个向量,但不需要的操作将被归零并丢弃。英特尔将此解决方案更进一步扩展,因为矢量大小确实可以变化!

除非对反馈送到 GPU 的数据布局进行微优化,否则差异可以忽略不计。如果数据结构良好,AMD 方法应该会给出非常好的结果。对于更多随机数据,英伟达可能会领先。


使用非硬件固有的类型

有两种情况:

  1. 使用比普通本机大小更大的类型
  2. 使用小于常见本机大小的类型

大多数消费级 GPU 使用 32 个浮点单元作为最常见的原生大小。那么如果使用双精度浮点数(64 位)会发生什么?除了最高寄存器使用率(GPU 中的寄存器使用 32 位元素,因此 64 个浮点数占用其中的 2 个)之外,除非同时分派的数量过多,否则不会发生任何坏事。大多数图形供应商提供单独的 F64 单元或专用单元来处理更精确的操作。

使用比原生尺寸更小的尺寸更有趣。首先,实际上可能拥有可以处理这些的硬件。几年来,第二个精度较低的浮子需求量很大。不仅因为机器学习,还因为游戏特定的场景。图灵是一个非常有趣的架构,因为它将 GPU 拆分为 GTX 和 RTX。前者没有 RT 核心和 Tensor 核心,而后者两者都有。RTX 的 Tensor 是特殊的 FP16 单元,也可以处理 INT8 或 INT4 类型。他们专门从事 FMA(融合乘加)矩阵运算。Tensor 核心的主要目的是使用 DLSS 6。GTX 版本的图灵架构(1660、1650)没有 Tensor 核心,而是免费提供 FP16 单元!它们不再专门用于矩阵运算,但调度程序可以在需要时随心所欲地使用它们。

16 位大小的浮点数也称为半浮点数。

如果在没有等效硬件的 GPU 上使用 F16,既不是 Tensor 核心也不是单独的 FP16 单元,会发生什么?仍然使用 FP32 单元来处理,但更重要的是,会浪费寄存器空间。但也有 1 大改进:减少了带宽。


分支很糟糕,对吧?

视情况而定。当有人以消极的方式谈论分支时,他或她意味着它发生在子组内。换句话说,子组内分支是不好的!但这只是故事的一半。想象一下,运行 Nvidia GPU 并分派 64 个工作线程。如果 32 个连续的线程以 1 条路径结束,而其他 32 个以另一条路径结束,则分支非常好。换句话说,子组间分支完全没问题。但是如果只有一个线程会在这 32 个压缩浮点数中分支,那么其他线程将等待它(标记为不活动),我们以内部子组分支结束。


L1 缓存 vs LDS vs 共享内存

根据命名,LDS(本地数据存储)和共享内存实际上表示完全相同。LDS 是 AMD 使用的名称,而共享内存是 Nvidia 创造的术语。

L1和 LDS/Shared Memory 是不同的东西,但它们在硬件中占据相同的空间。我们对 L1 缓存的使用没有任何明确的控制。这一切都由驱动管理。唯一可以编程的是与 L1 缓存共享空间的本地数据存储(具有可配置的比例)。一旦我们开始使用共享内存,我们可能会遇到一些问题……


内存库冲突

共享内存被分成组。将bank视为与数据正交。每个 bank 如何映射到内存访问取决于 bank 的数量和word 的大小。假设 32 个带 4 字节长字的 bank - 如果有一个 64 个浮点数组,那么只有第 0 和第 32 个元素将以 bank1 结尾,第 1 和第 33 个元素将以 bank2 结尾等。如果来自 Subgroup 的每个线程都访问唯一的 bank,那么这是理想的情况,因为可以在一个加载指令中完成。如果 2 个或更多线程从单个内存库请求不同的数据,则会发生库冲突。这意味着对这些数据单元的访问需要序列化。在最坏的情况下,这实际上意味着它可能需要 32 倍的时间。

多个线程从同一个 bank 访问特定值不是问题。如果所有线程访问 1 个bank中的 1 个值,那么它被称为广播-,它只读取 1 个共享内存,并且这个值被广播到所有线程。如果一些(但不是全部)线程访问来自特定bank的 1 个值,那么我们就有了多播。


由于延迟隐藏和共享内存速度,bank 冲突实际上可能无关紧要。在某些子组发现冲突访问之前,调度程序可以切换到另一个。

库冲突可能只发生在子组内!子组之间不存在bank冲突。

也可能出现注册bank冲突。


着色器进程编译类似于... Java

Vulkan 和 DX12 等现代 API 可以从运行图形/计算应用程序中卸载着色器编译。事先将 GLSL/HLSL 编译成 SPIR-V,并将其作为中间表示(字节码)保存,以便稍后由驱动程序使用。但是驱动程序接受它(可能在最后一刻进行更改 - 例如不断的专业化)并再次将其编译为供应商和/或硬件特定的代码。这里的逻辑与 C# 或 Java 等托管语言发生的情况非常相似,我们将代码编译成 IL,然后由 CLR 或 JVM 在特定硬件上编译/解释。


指令集架构

如果你想更深入,GPU如何执行指令集真的很好读。有 2 家公司为其特定硬件免费共享 ISA:

  • AMD ISA

https://gpuopen.com/documentation/amd-isa-documentation/

  • 英特尔 ISA

https://01.org/linuxgraphics/documentation


额外知识 - Linux 供应商驱动程序名称

让我们深入了解 Vulkan 驱动程序的复杂情况。

对于 Nvidia,有 2 个选择:

  • nouveau - 可用于日常工作,如办公室或观看 YouTube,但不适用于游戏或计算,开发受某些 Nvidia 决策的阻碍
  • Nvidia 专有驱动程序 - 在大多数情况下可行的方法,工作完美,但没有代码访问权限

AMD:

  • AMDVLK - 开源版本
  • Mesa 3D - 提供开源 radv 驱动程序的 3D 库,是 AMD 最受欢迎的选择
  • AMD 专有 AMDGPU-PRO

英特尔:

  • Mesa 3D - 与 AMD 类似,驱动程序是开放的并且是该库的一部分

相关推荐

当Frida来“敲”门(frida是什么)

0x1渗透测试瓶颈目前,碰到越来越多的大客户都会将核心资产业务集中在统一的APP上,或者对自己比较重要的APP,如自己的主业务,办公APP进行加壳,流量加密,投入了很多精力在移动端的防护上。而现在挖...

服务端性能测试实战3-性能测试脚本开发

前言在前面的两篇文章中,我们分别介绍了性能测试的理论知识以及性能测试计划制定,本篇文章将重点介绍性能测试脚本开发。脚本开发将分为两个阶段:阶段一:了解各个接口的入参、出参,使用Python代码模拟前端...

Springboot整合Apache Ftpserver拓展功能及业务讲解(三)

今日分享每天分享技术实战干货,技术在于积累和收藏,希望可以帮助到您,同时也希望获得您的支持和关注。架构开源地址:https://gitee.com/msxyspringboot整合Ftpserver参...

Linux和Windows下:Python Crypto模块安装方式区别

一、Linux环境下:fromCrypto.SignatureimportPKCS1_v1_5如果导包报错:ImportError:Nomodulenamed'Crypt...

Python 3 加密简介(python des加密解密)

Python3的标准库中是没多少用来解决加密的,不过却有用于处理哈希的库。在这里我们会对其进行一个简单的介绍,但重点会放在两个第三方的软件包:PyCrypto和cryptography上,我...

怎样从零开始编译一个魔兽世界开源服务端Windows

第二章:编译和安装我是艾西,上期我们讲述到编译一个魔兽世界开源服务端环境准备,那么今天跟大家聊聊怎么编译和安装我们直接进入正题(上一章没有看到的小伙伴可以点我主页查看)编译服务端:在D盘新建一个文件夹...

附1-Conda部署安装及基本使用(conda安装教程)

Windows环境安装安装介质下载下载地址:https://www.anaconda.com/products/individual安装Anaconda安装时,选择自定义安装,选择自定义安装路径:配置...

如何配置全世界最小的 MySQL 服务器

配置全世界最小的MySQL服务器——如何在一块IntelEdison为控制板上安装一个MySQL服务器。介绍在我最近的一篇博文中,物联网,消息以及MySQL,我展示了如果Partic...

如何使用Github Action来自动化编译PolarDB-PG数据库

随着PolarDB在国产数据库领域荣膺桂冠并持续获得广泛认可,越来越多的学生和技术爱好者开始关注并涉足这款由阿里巴巴集团倾力打造且性能卓越的关系型云原生数据库。有很多同学想要上手尝试,却卡在了编译数据...

面向NDK开发者的Android 7.0变更(ndk android.mk)

订阅Google官方微信公众号:谷歌开发者。与谷歌一起创造未来!受Android平台其他改进的影响,为了方便加载本机代码,AndroidM和N中的动态链接器对编写整洁且跨平台兼容的本机...

信创改造--人大金仓(Kingbase)数据库安装、备份恢复的问题纪要

问题一:在安装KingbaseES时,安装用户对于安装路径需有“读”、“写”、“执行”的权限。在Linux系统中,需要以非root用户执行安装程序,且该用户要有标准的home目录,您可...

OpenSSH 安全漏洞,修补操作一手掌握

1.漏洞概述近日,国家信息安全漏洞库(CNNVD)收到关于OpenSSH安全漏洞(CNNVD-202407-017、CVE-2024-6387)情况的报送。攻击者可以利用该漏洞在无需认证的情况下,通...

Linux:lsof命令详解(linux lsof命令详解)

介绍欢迎来到这篇博客。在这篇博客中,我们将学习Unix/Linux系统上的lsof命令行工具。命令行工具是您使用CLI(命令行界面)而不是GUI(图形用户界面)运行的程序或工具。lsoflsof代表&...

幻隐说固态第一期:固态硬盘接口类别

前排声明所有信息来源于网络收集,如有错误请评论区指出更正。废话不多说,目前固态硬盘接口按速度由慢到快分有这几类:SATA、mSATA、SATAExpress、PCI-E、m.2、u.2。下面我们来...

新品轰炸 影驰SSD多款产品登Computex

分享泡泡网SSD固态硬盘频道6月6日台北电脑展作为全球第二、亚洲最大的3C/IT产业链专业展,吸引了众多IT厂商和全球各地媒体的热烈关注,全球存储新势力—影驰,也积极参与其中,为广大玩家朋友带来了...