在支持 AVX-512 和 BF16 的 CPU 上,您可以使用 512 位向量寄存器来存储 32 个 16 位浮点数。
我找到了将 FP32 值转换为 BF16 值的内在函数(例如:_mm512_cvtne2ps_pbh),但我还没有找到任何直接从内存加载 BF16 值的内在函数。如果我总是将它们转换为 BF16,那么总是加载 FP32 中的值似乎有点浪费。是否不支持直接 BF16 负载,或者我只是还没有找到正确的内在函数?
内在的奇怪监督。 asm 中没有针对 BH16 的特殊
mov
指令,但那是因为您不需要这样的指令:您只需使用 vmovups
因为 asm 不关心类型。 (除了有时整数与 FP 域之外,因此可能更喜欢 FP 加载或存储指令 - 整数 vmovdqu
可能在某些 CPU 上有一个额外的从加载到 FP ALU 的延迟转发周期。)
如果对齐加载/存储适用于您的用例,只需将
__m512bh*
指向您的数据并取消引用它即可。 (硬件 SIMD 向量指针和相应类型之间的“reinterpret_cast”是未定义的行为吗? - 它被明确定义为相当于对齐的加载或存储内在函数,并且允许为任何其他数据起别名)。
如果没有,那么正如 @chtz 指出的那样,您可以
memcpy
到/从 __m512bh
变量。现代编译器知道如何内联和优化小型固定大小的 memcpy,尤其是变量的确切大小。 @chtz 在 Godbolt 上的演示表明它优化了我们想要的 GCC 和 clang -O1
的方式,就像 __m512bh*
的 deref 但适用于未对齐。
但是 MSVC 不太好;它工作正常,但本地变量的 memcpy 实际上保留堆栈空间并将值存储到其中,并将其保留在 ZMM0 作为返回值。 (不重新加载副本,但不优化存储和死存储到
res
。)
对于内在函数,甚至没有来自
__m512
、__m512d
或 __m512i
的强制转换内在函数。 (或者对于任何更窄的矢量宽度。)
但是大多数编译器也允许您对向量类型使用 C 风格的转换,就像这样将这些位重新解释(类型双关)为不同的向量类型:
__m512bh vec = (__m512bh) _mm512_loadu_ps( ptr ); // Not supported by MSVC
这不是由Intel的内在函数指南定义的标准事物,但GCC和clang至少实现了C风格的转换(和C++
std::bit_cast
,可能还有static_cast
),与内在函数API的函数(如)相同_mm512_castsi512_ps
或 _mm512_castps_ph
(我们希望 BF16 存在 FP16 内在函数)。
AVX-512 加载内在函数采用
void*
,这清楚地表明可以在任何类型的数据上使用它们。所以这不需要指针的转换,只需要矢量数据。
256 位和 128 位整数加载/存储采用相应的
__m256i*
或 __m128i*
指针,FP 加载采用 float*
。但使用严格别名仍然是安全的_mm_loadu_ps( (float*)&int_vector[i] )
。不管怎样,一旦你得到 __m256
或 __m128
,(__m256bh) vec
将在大多数编译器中工作。
MSVC 被这个演员噎住了。如果您使用的是 C++,则可能会为 MSVC 使用 C++20
std::bit_cast<__m512h>( vec )
。 但是,如果您想编写在 MSVC 以及 GCC/Clang 上高效编译的可移植 C,您唯一的选择可能是取消引用对齐指针。 memcpy
在 MSVC 上编译为死存储,转换值不会工作,向量指针的 deref 需要在 GCC/Clang 上对齐。 MSVC 始终避免指令的对齐检查版本,因此如果您愿意 #ifdef
,在 MSVC 上取消引用未对齐的 __m512h*
可能是安全的。
(在没有 AVX 的情况下解引用
__m128*
是不安全的,因为它可能会折叠到像 addps xmm0, [rdi]
这样的内存源操作数中,这确实需要对齐,但这仅适用于旧版 SSE 事物。VEX / EVEX 编码默认允许不对齐。 raw deref 不会发明仅需要对齐方式的 vmovntps
存储;如果需要 vmovxxx
,即使已知指针已对齐,它也会使用 vmovups
而不是 vmovaps
。 clang will 在能够证明其安全时使用对齐强制指令,这与 MSVC 和经典 ICC 不同。)