Hi!请登陆

Intel核显的一些解析

2022-6-16 87 6/16

Intel的核显,通常以genX划分代际,内部俗称GEN。

但最近研究了很多与之架构相关的内容,就总结一下写出来。

对于各代包含的具体型号与参数,维基上介绍得很详细了。

在这里就着重写一些内部设计和概念,其中很多内容也是来自于官方的架构文档。

之前还回答过对gen11的看法,不过现在看来还是存在不少错误,以这里的为准。


EU (Excution Unit)

GEN的最核心模块是EU(Excution Unit)。

Intel核显的一些解析

这张图从haswell的gen7.5[1]一直用到icelake的gen11[2],可以说是非常ya经gao典的结构了。(gen11的pdf用的另一张图,但换汤不换药。)

如图所示,一个EU里存在7个thread,称作EU thread。这个thread和软件层面的thread不同,即OpenCL/CUDA/OpenGL/DirectX/Vulkan等API里的thread不等于一个EU thread。

每个thread具有一系列GRF(通用寄存器)ARF(架构相关寄存器),互不共享。他们后边挂着一个thread arbiter线程派发器,下面挂着4个单元。每个周期,7个EU thread中最多能有4个被各挑出一条指令,送给不同的后端单元

从这一角度看,EU thread的真实身份就很明确了,对应到软件层面就是OpenCL/Vulkan/OpenGL里的subgroupCUDA里的warpDirectX里的wave;对应到其他厂商就是AMD的wavefrontNVIDIA的warp

ISA[3]

[(pred)] opcode (exec-size|exec-offset) dst src0 [src1] [src2]

GEN的ISA,每条指令是16字节(128bit)。对于一些特殊的常见指令,(如大多数float MAD),还可以启用compact,将指令缩减到8字节(64bit)。

SIMD

GEN的SIMD设计比较神奇。熟悉的人应该知道NVIDIA的warp大小是32,而AMD的wavefront大小是64(虽然RDNA又设计了一套SIMD32[4])。但Intel的SIMD是可变的。

事实上,SIMT和SIMD不是互斥的:SIMT将粒度放小到单个线程,方便了开发;而硬件上不可能无节制地提供自由度,于是通常用SIMD来实现更高的效率。而将SIMT映射到SIMD时不可避免地会丢失一部分自由度,这也就是subgroup/warp/wave产生的原因

subgroup将多个线程绑定在一起,同步运行,每个线程操作一个scalar的话,这一组线程的操作数刚好就能合并到一起构成一个vector。

subgroup等定义规定了这几组线程会同步做同样的操作,但并不意味着在硬件层面他们要同时完成同一个操作。举个例子就是AMD的GCN架构,对外开放SIMD64,内部ALU其实是SIMD16[4],一组wavefront分四个周期发给ALU。

同样的,既然硬件层面和软件层面的SIMD lane数量解耦了,ASM层面的SIMD也可以和软件层面的不一致。想象一下如果64个线程里只有一个线程要做一项操作,怎么办?你可以:

  1. 用mask设定好只让那个线程对应的lane生效 。
  2. 在ASM层面说明这个操作只需要一个SIMD lane去完成。

GEN就是用了后者的设计。ASM里每条指令开头都会有(SIMD x),这里的x就说明了有几个lane要进行操作,x可以是1/2/4/8/16/32。其中32似乎只能用在byte数据做Load/Store上,而某些操作指令也对x有限制。

当然ASM层面的SIMD是编译后才知道的,由编译器决定。对于那些运行时才能知道是否操作的情况,GEN同时提供了mask的方式。

同时由于SIMD得在编译后才能确定的,在进行OpenCL之类的编程时可能需要手动查询,你得到的通常是ASM中存在的最大的一个SIMD。当然你也可以手动提示编译器按某种SIMD去编译,这个就先不多讲了。

寄存器

每个EU具有28KB的GRF,每个EU thread具有4KB。这4KB是由128个SIMD8的寄存器组成的,每个寄存器是SIMD8-256bit,可以看作是128个YMM寄存器,ASM写法是r0~r127。

虽然每个SIMD channel是32bit,但访问时粒度可以细到字节8bit。所以,访问时需要带上格式标识,例如b(byte),w(word),d(dword),ud(unsigned dword),hf(half float),f(float),df(double float)等。

通过格式标识指定了单位元素的数据格式后,加上前面提到的SIMD lane标识,一条指令要访问多少数据也就可以确定。

但是指定访问的数据可能并不是连续的,甚至不一定是规则的,而GEN恰好提供了一种很奇特的寄存器寻址方式。

RegNum.SubRegNum:type

简单来说,设计成SIMD8是有特殊意义的。

每个寄存器从上往下排列,就构成了一张2维网格,GEN的ISA允许指定寄存器号(行号),起始寄存器位置(列号),横竖方向stride,宽度(每行提取几个元素),从而实现了非常自由而又非常别扭的寻址方式。

Intel核显的一些解析

而这个2维网格又使得寄存器可以按bank排列(如8个bank),降低硬件设计复杂度的同时尽可能保证访问单个完整寄存器的吞吐性能。不过这也会带来bank conflic的问题,在一些特定情况下降低吞吐率。

前面的链接里还提到了一些ARF,不同代GEN的ARF可能有些差异。

其中最常使用的寄存器是

  • acc累加寄存器,这个寄存器提供了64bit精度,一些计算时会用到。
  • f.flag寄存器,用来存mask

ARF和GRF不同之处就在于,一些指令的一些操作数不能寻址ARF,或者一些指令隐含ARF作为操作数(如mac和mach隐含acc)。

GEN的ISA还允许寄存器间接寻址,即将寄存器的内容作为寄存器地址来进行访问。我之前也不相信这类指令真的存在,直到前两天我亲眼见证了……

Intel核显的一些解析
r[a0.0]

相关推荐