CUDA 内核有向量指令吗?

Posted

技术标签:

【中文标题】CUDA 内核有向量指令吗?【英文标题】:Do CUDA cores have vector instructions? 【发布时间】:2018-06-28 22:15:58 【问题描述】:

根据大多数 NVidia 文档,CUDA 内核是标量处理器,应该只执行标量操作,这将被矢量化为 32 分量 SIMT 扭曲。

但 OpenCL 具有矢量类型,例如 uchar8。它的大小与 ulong(64 位)相同,可由单个标量核心处理。如果我对 uchar8 向量进行操作(例如逐个添加),这是否也会映射到单核上的指令?

如果一个块(工作组)中有 1024 个工作项,并且每个工作项处理一个 uchar8,这是否会有效地并行处理 8120 个uchar

编辑: 我的问题是,如果在 CUDA 架构上(独立于 OpenCL),“标量”内核中是否有一些向量指令可用。因为如果内核已经能够处理 32 位类型,那么它也可以处理 32 位 uchar4 的添加是合理的,尤其是因为向量运算经常用于计算机图形学中。

【问题讨论】:

Are GPU/CUDA cores SIMD ones?的可能重复 虽然问题的标题非常相似,但它不是(那个)重复的。另一个问题是关于 SIMD 的问题,它实际上是由 warp 处理的。在这里作者询问子寄存器操作,例如将 8 个 8 位值打包到一个 64 位常规寄存器中并对其执行有意义的操作。 【参考方案1】:

CUDA 具有“内置”(即预定义)向量类型,对于 4 字节数量(例如 int4)最大为 4,对于 8 字节数量最大为 2(例如 double2 )。一个 CUDA 线程的最大读/写事务大小为 16 字节,因此这些特定的大小选择往往与 that maximum 一致。

这些是作为典型结构公开的,因此您可以引用例如 .x 来仅访问向量类型的第一个元素。

与 OpenCL 不同,CUDA 不为基本算术提供内置操作(“重载”),例如+- 等,用于对这些向量类型进行逐元素操作。没有什么特别的原因你不能自己提供这样的重载。同样,如果您想要uchar8,您可以轻松地为此类提供结构定义,以及任何所需的运算符重载。这些可能就像您对普通 C++ 代码所期望的那样实现。

那么,可能一个潜在的问题是,CUDA 和 OpenCL 在这方面的实现有什么区别?如果我在uchar8 上操作,例如

uchar8 v1 = ...;
uchar8 v2 = ...;
uchar8 r = v1 + v2;

OpenCL 和 CUDA 在机器性能(或低级代码生成)方面有何不同?

对于支持 CUDA 的 GPU 来说可能不多。 CUDA 核心(即底层 ALU)对 uchar8 上的此类操作没有直接的本机支持,此外,如果您编写自己的 C++ 兼容重载,您可能会为此使用 C++ 语义,这将本质上是串行的:

r.x = v1.x + v2.x;
r.y = v1.y + v2.y;
...

因此,这将分解为在 CUDA 内核(或在 CUDA SM 内的适当整数单元中)执行的一系列操作。由于 NVIDIA GPU 硬件不直接支持单核/时钟/指令中的 8 路 uchar 添加,因此 OpenCL(在 NVIDIA GPU 上实现)确实没有太大的不同。在底层,底层机器代码将是一系列操作,而不是一条指令。

顺便说一句,CUDA(或 PTX,或 CUDA 内在函数)确实在单个内核/线程/指令中提供了有限数量的向量操作。这方面的一些例子是:

    一组有限的“本地”"video" SIMD instructions。这些指令是每个线程的,因此如果使用它们,它们允许每个 warp 最多支持 4x32 = 128(8 位)操作数,尽管操作数必须正确打包到 32 位寄存器中。您可以通过一组内置的intrinsics 直接从 C++ 访问这些。 (CUDA warp 是一组 32 个线程,是支持 CUDA 的 GPU 上锁步并行执行和调度的基本单元。)

    向量 (SIMD) 乘法累加运算,不能直接转换为单个特定的元素运算重载,即所谓的 int8 dp2a 和 dp4a 指令。这里的 int8 有点误导。它不是指 int8 向量类型,而是指在单个 32 位字/寄存器中的 4 个 8 位整数数量的打包排列。同样,这些可以通过intrinsics 访问。

    在 cc 5.3 及更高版本的 GPU 中,通过 half2 向量类型本机支持 16 位浮点,用于某些操作。

    新的 Volta tensorCore 有点像单线程 SIMD 操作,但它在一组 16x16 输入矩阵上操作(warp-wide),产生 16x16 矩阵结果。

即使使用可以将某些向量操作映射到硬件“本机”支持的各种操作的智能 OpenCL 编译器,也不会完全覆盖。举一个例子,在单个核心/线程上,在单个指令中没有对 8 宽向量(例如uchar8)的操作支持。所以一些序列化是必要的。在实践中,我不认为 N​​VIDIA 的 OpenCL 编译器那么聪明,所以我的期望是,如果你研究机器代码,你会发现这种每线程向量操作完全序列化。

在 CUDA 中,您可以为某些 操作和向量类型提供自己的重载,这些重载可以近似地在一条指令中表示。例如,uchar4 add 可以使用__vadd4() intrinsic“本地”执行(可能包含在您的运算符重载实现中。)同样,如果您正在编写自己的运算符重载,我认为这不会很困难使用两条__vadd4() 指令执行uchar8 元素向量相加。

【讨论】:

【参考方案2】:

如果我对 uchar8 向量进行操作(例如逐个添加),这是否也会映射到单核上的指令?

AFAIK 它将始终位于单个内核上(来自单个内核/工作项的指令不会跨内核,除非像屏障这样的特殊指令),但它可能不止一条指令。这取决于您的硬件是否原生支持 uchar8 上的操作。如果没有,那么 uchar8 将根据需要被分解成尽可能多的部分,并且每个部分都将使用单独的指令进行处理。

OpenCL 非常“通用”,因为它支持许多不同的向量类型/大小组合,但实际硬件通常只实现一些向量类型/大小组合。您可以查询 OpenCL 设备的“首选矢量大小”,这应该会告诉您什么是该硬件最有效的。

【讨论】:

以上是关于CUDA 内核有向量指令吗?的主要内容,如果未能解决你的问题,请参考以下文章

Cuda 内核返回向量

将包含向量的结构传递给CUDA内核

可以使用 movss 指令替换整数数据吗?

在内核上工作的 CUDA 上的向量

向量指令和标量指令

CUDA 加法与移位指令性能