amdgpu相关基础概念

以AMD Radeon Instinct™ MI60 GPU为例,给出了具体参数:

工作组大小限制:1024个工作项

波前大小:64

CU数量:64

一个CU最多容纳40个波前(如果使用暂存内存,则限制为32个)

因此,一个工作组可以由1到16个波前组成(因为波前大小64,工作组最多1024,所以最多16个波前)

最多可以有2560个波前(64个CU * 每个CU40个波前),即163840个工作项(2560波前 * 64)

一个CU由4个执行单元(EU,也称为SIMD)组成,每个SIMD可以容纳10个波前

每个SIMD有256个64宽的双字向量寄存器(每个向量寄存器有64个双字,每个双字4字节,所以一个向量寄存器是256字节?注意,这里说64-wide是指每个向量寄存器有64个元素,每个元素是一个DWORD(4字节),所以一个向量寄存器是256字节)

每个CU有800个双字标量寄存器(每个4字节,所以共3200字节)

单个波前最多可以访问256个64宽向量寄存器和112个标量寄存器

一个CU有64KiB的LDS

 

我们可以理解AMD GPU执行模型的基本概念和层次结构:网格(grid)-> 工作组(workgroup)-> 波前(wavefront)-> 工作项(Work-item)/通道(lane)。

用一个简单的比喻来帮助理解

第一层:任务分解(从“做什么”到“谁来做”)

假设我们要处理一张 1920x1080 的图片(约207万个像素)。在GPU编程模型中:

1、一个工作项:就是“处理一个像素”这个任务。它是执行的最小逻辑单位,对应一个线程。
我们有207万个这样的工作项。

2、一个工作组:为了管理方便,我们把所有像素分成很多小组。比如,把图片分成 120 x 68 个块,每个块是 16 x 16 个像素。
这16x16=256个像素组成的“小方块”,就是一个工作组。工作组内的像素(工作项)可以互相协作(共享数据)。

3、调度网格:这个由 120 x 68 个工作组成组成的、覆盖整张图片的二维结构,就是调度网格。
它是一个三维的抽象,但我们可以用二维来想象。(x, y) 坐标就代表“第几行、第几列的工作组”。

小结:程序员说“处理这张图”,GPU系统将其分解为:网格 -> 工作组 -> 工作项。


第二层:硬件执行(军队如何真正运作)

军队(GPU硬件)有自己的一套执行方式,不一定和逻辑分解一一对应。

1、波前:这是硬件调度和执行的实际单位。GPU硬件(计算单元CU)一次不是处理1个,也不是处理256个工作项,
而是处理一个固定大小的“组”,比如64个。这64个工作项(必须是同一个工作组内、ID连续的64个)被捆绑在一起,
形成一个波前(也叫波)。可以把它想象成一个“步兵方阵”,齐步走,执行同一个命令。

通道:波前里的每个士兵(工作项)称为一个通道。它有自己在方阵内的编号(通道ID)。

2、SIMT(单指令多线程):这是GPU的核心执行模式。

关键:一个波前(64个通道)在同一个时钟周期内,执行同一条指令。

但数据不同:虽然指令相同(比如“把A寄存器加B寄存器”),但每个通道的A和B寄存器里存放的是自己负责的那个像素的数据。结果是64个像素并行完成了“加法”操作。

小结:逻辑上的“工作组”(256个任务),在硬件上可能被分成4个“波前”(64个任务一组)来执行。工作项是逻辑线程,波前是物理执行单元。


第三层:处理分歧(当“方阵”内士兵任务不同时)

问题来了:如果代码中有 if-else 分支,比如“如果像素亮度>128,则执行操作A,否则执行操作B”。
那同一个波前里的64个像素,可能有些要走A分支,有些要走B分支。但硬件规定他们必须执行同一条指令!怎么办?

1、执行掩码:GPU为每个波前配备了一个执行掩码,可以想象成64个开关,每个控制一个通道(士兵)。

2、编译器魔法:编译器会重组代码来解决这个问题。

步骤1:设置掩码,只打开“需要执行A分支”的那些通道的开关。

步骤2:让整个波前执行A分支的指令。此时,被关闭开关的通道(要走B分支的)虽然也“听”到了指令,但不会真正更新自己的状态(寄存器、内存)。

步骤3:切换掩码,只打开“需要执行B分支”的通道的开关。

步骤4:让整个波前执行B分支的指令。

3、两种控制流:

发散控制流:当掩码没有全部打开(即波前内部分通道被禁用)时,称为发散。这会严重降低性能,因为本来可以一步完成的工作,现在要分两步走。

波前统一控制流:所有通道都执行相同的路径,这是最理想、最高效的情况。

小结:GPU用“执行掩码+串行化执行不同路径”来模拟逻辑上的条件分支,代价是潜在的性能损失。


第四层:硬件架构(工厂的车间和工具)


1、计算单元:这是GPU的“核心车间”。一个GPU有很多个CU。每个CU可以同时容纳和管理多个波前(比如40个)。

2、资源与权衡:

寄存器:每个CU有固定数量的寄存(分为标量寄存器和向量寄存器)。

向量寄存器:每个通道都有自己独立的一份。64个通道就有64份。用来存放每个像素的私有数据。

标量寄存器:整个波前共享一份。用来存放所有通道都一样的公共数据(比如循环上限)。

本地数据存储:这是CU内部的一块共享内存,同一个工作组内的所有波前都可以高速访问它,用于协作。大小固定(比如64KB)。如果给每个工作组分得多,那一个CU里能放下的工作组就少,反之亦然。

暂存内存:这是每个工作项(通道)的私有内存,但物理上是从显卡的全局显存中划出来的。当寄存器不够用时,就用它。硬件会优化访问模式,让相邻通道访问相邻内存地址,以提升缓存效率。


小结:CU是执行基地,内部有私有的寄存器、共享的LDS,并能从全局显存分配私有暂存空间。这些资源的总量限制了同时能驻扎多少“军队”(波前和工作组)。


关键概念快速回顾

image

 

以MI60 GPU为例,把数字填进去

文档最后一段给出了MI60的具体参数,让我们把抽象概念具体化:

任务规模:一个工作组最多 1024 个工作项(士兵)。一个波前固定 64 人。所以,一个工作组由 1到16个 波前方阵组成。

工厂规模:有 64 个兵营(CU)。

兵营容量:一个兵营最多驻扎 40 个方阵(波前)。如果士兵用了私人储物柜(暂存内存),则只能驻扎 32 个(因为管理开销大了)。

全军规模:全GPU最多同时有 64 CU * 40 波前/CU = 2560 个方阵在活动。也就是 2560 * 64 = 163,840 个士兵(工作项)在并行工作。

兵营结构:一个兵营(CU)有 4 个排(SIMD/EU),每个排能管理 10 个方阵。

武器装备:

每个排(SIMD)有 256 个向量武器架(向量寄存器),每个架子能放 64 件武器(供一个方阵的64人每人一件)。

整个兵营(CU)有 800 个标量武器箱(标量寄存器),全兵营共享。

一个方阵(波前)最多能申请 256 个向量武器架和 112 个标量武器箱。

每个兵营有一个 64KB 的连队共享储物间(LDS)。


这对调试器(ROCgdb)意味着什么?

理解了上述模型,你就知道ROCgdb要调试什么:

1、它调试的是“波前”,因为这是硬件状态的实际载体。当你让程序“暂停”时,实际上是某些波前停止了。

2、你需要能查看每个通道(工作项)的状态,比如它的私有向量寄存器、它看到的私有内存。

3、你需要能查看共享状态,比如LDS里的内容。

4、当遇到断点时,你需要理解这个波前可能处于发散执行的状态,即只有部分通道是活跃的。

5、你需要知道如何读取程序计数器来确定波前停在代码的哪个位置,并可能需要根据断点指令的长度进行调整。

这就是为什么文档开头说,调试器API只提供控制执行和检查状态的原始操作,而符号映射、栈展开等高级功能需要调试器(如GDB)
自己利用DWARF信息来完成——因为API只提供“硬件方阵”的原始状态,如何把这些状态映射回程序员写的“像素处理函数”和变量,是调试器上层的工作。

posted on 2025-12-01 17:49  lh03061238  阅读(0)  评论(0)    收藏  举报

导航