CUDA 核心有向量指令吗?
Do CUDA cores have vector instructions?
根据大多数 NVidia 文档,CUDA 核心是标量处理器,应该只执行标量运算,这将被矢量化为 32 分量 SIMT 扭曲。
但 OpenCL 有矢量类型,例如 uchar8
。它的大小与 ulong
(64 位)相同,可以由单个标量核心处理。如果我对 uchar8
向量进行运算(例如按分量加法),这是否也会映射到单核上的指令?
如果一个块(工作组)中有1024个工作项,每个工作项处理一个uchar8
,这是否有效并行处理8120个uchar
?
编辑:
我的问题是,如果专门针对 CUDA 架构(独立于 OpenCL),"scalar" 内核中是否有一些矢量指令可用。因为如果内核已经能够处理 32 位类型,那么如果它也可以处理 32 位 uchar4
的加法,这将是合理的,特别是因为矢量运算经常用于计算机图形学。
If I do operations on a uchar8 vector (for example component-wise addition), will this also map to an instruction on a single core?
据我所知,它将始终在单个内核上(来自单个内核/工作项的指令不会跨内核,除了像障碍这样的特殊指令),但它可能不止一个指令。这取决于您的硬件是否原生支持对 uchar8 的操作。如果不是,则 uchar8 将根据需要分成任意多的部分,并且每个部分将使用单独的指令进行处理。
OpenCL 非常 "generic" 因为它支持许多不同的矢量 type/size 组合,但现实世界的硬件通常只实现一些矢量 type/size 组合。您可以为 "preferred vector size" 查询 OpenCL 设备,它应该会告诉您什么是该硬件最有效的。
CUDA 具有 "built-in"(即预定义)矢量类型,对于 4 字节数量(例如 int4
)最大为 4,对于 8 字节数量最大为 2(例如 double2
)。 CUDA 线程的最大 read/write 事务大小为 16 字节,因此这些特定大小的选择往往符合 that maximum.
这些作为典型结构公开,因此您可以参考 .x
以仅访问向量类型的第一个元素。
与 OpenCL 不同,CUDA 不提供用于基本算术的内置运算 ("overloads"),例如+
、-
等,用于对这些向量类型进行逐元素运算。没有什么特别的原因你不能自己提供这样的重载。同样,如果您想要 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 硬件不为单个 core/clock/instruction 中的 8 路 uchar 加法提供任何直接支持,因此 OpenCL(在 NVIDIA GPU 上实现)真的不可能有太大不同。在低级别,底层机器代码将是一系列操作,而不是单个指令。
顺便说一句,CUDA(或 PTX,或 CUDA 内在函数)确实在单个 core/thread/instruction 中提供了有限数量的向量运算。这方面的一些例子是:
一组有限的"native""video" SIMD instructions. These instructions are per-thread, so if used, they allow for "native" support of up to 4x32 = 128 (8-bit) operands per warp, although the operands must be properly packed into 32-bit registers. You can access these from C++ directly via a set of built-in intrinsics。 (CUDA warp 是一组 32 个线程,是支持 CUDA 的 GPU 上锁步并行执行和调度的基本单元。)
向量 (SIMD) 乘法累加运算,它不能直接转换为单个特定的逐元素运算重载,即所谓的 int8 dp2a 和 dp4a 指令。这里的 int8 有点误导。它不是指 int8 向量类型,而是指单个 32 位 word/register 中 4 个 8 位整数的压缩排列。同样,这些可以通过 intrinsics.
访问
16 位浮点在 cc 5.3 和更高版本的 GPU 中通过 half2
向量类型原生支持,用于某些操作。
新的 Volta tensorCore 有点像 SIMD-per-thread 操作,但它在一组 16x16 输入矩阵上运行(warp-wide)产生 16x16 矩阵结果。
即使使用可以将某些向量操作映射到硬件支持的各种操作 "natively" 的智能 OpenCL 编译器,也不会完全覆盖。在单个 core/thread 上,在单个指令中,没有对 8 宽向量(例如 uchar8
)的操作支持,以选择一个示例。所以一些序列化是必要的。实际上,我不认为 NVIDIA 的 OpenCL 编译器那么聪明,所以我的期望是,如果你研究机器代码,你会发现这样的每线程向量操作完全序列化。
在 CUDA 中,您可以为某些 操作和向量类型提供您自己的重载,这些重载大约可以在一条指令中表示。例如,可以使用 __vadd4() intrinsic 执行 uchar4
添加 "natively" (可能包含在您的运算符重载实现中。)同样,如果您正在编写自己的运算符重载,我不会不认为使用两条 __vadd4()
指令执行 uchar8
元素向量加法会很困难。
根据大多数 NVidia 文档,CUDA 核心是标量处理器,应该只执行标量运算,这将被矢量化为 32 分量 SIMT 扭曲。
但 OpenCL 有矢量类型,例如 uchar8
。它的大小与 ulong
(64 位)相同,可以由单个标量核心处理。如果我对 uchar8
向量进行运算(例如按分量加法),这是否也会映射到单核上的指令?
如果一个块(工作组)中有1024个工作项,每个工作项处理一个uchar8
,这是否有效并行处理8120个uchar
?
编辑:
我的问题是,如果专门针对 CUDA 架构(独立于 OpenCL),"scalar" 内核中是否有一些矢量指令可用。因为如果内核已经能够处理 32 位类型,那么如果它也可以处理 32 位 uchar4
的加法,这将是合理的,特别是因为矢量运算经常用于计算机图形学。
If I do operations on a uchar8 vector (for example component-wise addition), will this also map to an instruction on a single core?
据我所知,它将始终在单个内核上(来自单个内核/工作项的指令不会跨内核,除了像障碍这样的特殊指令),但它可能不止一个指令。这取决于您的硬件是否原生支持对 uchar8 的操作。如果不是,则 uchar8 将根据需要分成任意多的部分,并且每个部分将使用单独的指令进行处理。
OpenCL 非常 "generic" 因为它支持许多不同的矢量 type/size 组合,但现实世界的硬件通常只实现一些矢量 type/size 组合。您可以为 "preferred vector size" 查询 OpenCL 设备,它应该会告诉您什么是该硬件最有效的。
CUDA 具有 "built-in"(即预定义)矢量类型,对于 4 字节数量(例如 int4
)最大为 4,对于 8 字节数量最大为 2(例如 double2
)。 CUDA 线程的最大 read/write 事务大小为 16 字节,因此这些特定大小的选择往往符合 that maximum.
这些作为典型结构公开,因此您可以参考 .x
以仅访问向量类型的第一个元素。
与 OpenCL 不同,CUDA 不提供用于基本算术的内置运算 ("overloads"),例如+
、-
等,用于对这些向量类型进行逐元素运算。没有什么特别的原因你不能自己提供这样的重载。同样,如果您想要 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 硬件不为单个 core/clock/instruction 中的 8 路 uchar 加法提供任何直接支持,因此 OpenCL(在 NVIDIA GPU 上实现)真的不可能有太大不同。在低级别,底层机器代码将是一系列操作,而不是单个指令。
顺便说一句,CUDA(或 PTX,或 CUDA 内在函数)确实在单个 core/thread/instruction 中提供了有限数量的向量运算。这方面的一些例子是:
一组有限的"native""video" SIMD instructions. These instructions are per-thread, so if used, they allow for "native" support of up to 4x32 = 128 (8-bit) operands per warp, although the operands must be properly packed into 32-bit registers. You can access these from C++ directly via a set of built-in intrinsics。 (CUDA warp 是一组 32 个线程,是支持 CUDA 的 GPU 上锁步并行执行和调度的基本单元。)
向量 (SIMD) 乘法累加运算,它不能直接转换为单个特定的逐元素运算重载,即所谓的 int8 dp2a 和 dp4a 指令。这里的 int8 有点误导。它不是指 int8 向量类型,而是指单个 32 位 word/register 中 4 个 8 位整数的压缩排列。同样,这些可以通过 intrinsics.
访问
16 位浮点在 cc 5.3 和更高版本的 GPU 中通过
half2
向量类型原生支持,用于某些操作。新的 Volta tensorCore 有点像 SIMD-per-thread 操作,但它在一组 16x16 输入矩阵上运行(warp-wide)产生 16x16 矩阵结果。
即使使用可以将某些向量操作映射到硬件支持的各种操作 "natively" 的智能 OpenCL 编译器,也不会完全覆盖。在单个 core/thread 上,在单个指令中,没有对 8 宽向量(例如 uchar8
)的操作支持,以选择一个示例。所以一些序列化是必要的。实际上,我不认为 NVIDIA 的 OpenCL 编译器那么聪明,所以我的期望是,如果你研究机器代码,你会发现这样的每线程向量操作完全序列化。
在 CUDA 中,您可以为某些 操作和向量类型提供您自己的重载,这些重载大约可以在一条指令中表示。例如,可以使用 __vadd4() intrinsic 执行 uchar4
添加 "natively" (可能包含在您的运算符重载实现中。)同样,如果您正在编写自己的运算符重载,我不会不认为使用两条 __vadd4()
指令执行 uchar8
元素向量加法会很困难。