使用 AVX 收集半浮点值
Gathering half-float values using AVX
使用 AVX/AVX2 内在函数,我可以使用以下方法收集 8 个值的集合,1,2 或 4 字节整数,或 4 字节浮点数:
_mm256_i32gather_epi32()
_mm256_i32gather_ps()
但目前,我有一个案例,我正在加载在 nvidia GPU 上生成并存储为 FP16 值的数据。我如何对这些值进行矢量化加载?
到目前为止,我找到了 _mm256_cvtph_ps() intrinsic.
但是,该内在函数的输入是 __m128i 值,而不是 __m256i 值。
查看 Intel Intrinsics 指南,我没有看到将 8 个值存储到 _mm128i 寄存器中的收集操作?
如何将 FP16 值收集到 __m256 寄存器的 8 个通道中?是否可以将它们作为 2 字节短裤向量加载到 __m256i 中,然后以某种方式将其减少为 __m128i 值以传递到转换内部?如果是这样,我还没有找到这样做的内在函数。
更新
我按照@peter-cordes 的建议尝试了演员阵容,但我得到了虚假的结果。另外,我不明白那是怎么回事?
我的 2 字节 int 值存储在 __m256i 中为:
0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX
那么我怎样才能简单地转换为 __m128i 需要将其紧密打包为
XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
演员会那样做吗?
我当前的代码:
__fp16* fielddensity = ...
__m256i indices = ...
__m256i msk = _mm256_set1_epi32(0xffff);
__m256i d = _mm256_and_si256(_mm256_i32gather_epi32(fielddensity,indices,2), msk);
__m256 v = _mm256_cvtph_ps(_mm256_castsi256_si128(d));
但结果似乎不是 8 个格式正确的值。我认为目前每第二个对我来说都是假的?
确实没有针对 16 位值的收集指令,因此您需要收集 32 位值并忽略其中的一半(并确保您不会不小心从无效内存中读取)。此外,_mm256_cvtph_ps()
需要较低 128 位通道中的所有输入值,不幸的是,没有通道交叉 16 位洗牌(直到 AVX512)。
但是,假设您只有有限的输入值,您可以进行一些位运算(避免 _mm256_cvtph_ps()
)。如果将半精度值加载到 32 位寄存器的上半部分,则可以执行以下操作:
SEEEEEMM MMMMMMMM XXXXXXXX XXXXXXXX // input Sign, Exponent, Mantissa, X=garbage
算术右移 3(这会将符号位保留在需要的位置):
SSSSEEEE EMMMMMMM MMMXXXXX XXXXXXXX
屏蔽掉底部过多的符号位和垃圾(0b1000'11111'11111111111'0000000000000
)
S000EEEE EMMMMMMM MMM00000 00000000
这将是一个有效的单精度浮点数,但指数将偏离 112=127-15
(偏差之间的差异),即您需要将这些值乘以 2**112
(这可能是结合任何后续操作,您打算稍后再做)。请注意,这也会将次正常的 float16 值转换为相应的次正常的 float32 值(也相差 2**112
)。
Un测试内部版本:
__m256 gather_fp16(__fp16 const* fielddensity, __m256i indices){
// subtract 2 bytes from base address to load data into high parts:
int32_t const* base = (int32_t const*) ( fielddensity - 1);
// Gather 32bit values.
// Be aware that this reads two bytes before each desired value,
// i.e., make sure that reading fielddensitiy[-1] is ok!
__m256i d = _mm256_i32gather_epi32(base, indices, 2);
// shift exponent bits to the right place and mask away excessive bits:
d = _mm256_and_si256(_mm256_srai_epi32(d, 3), _mm256_set1_epi32(0x8fffe000));
// scale values to compensate bias difference (could be combined with subsequent operations ...)
__m256 two112 = _mm256_castsi256_ps(_mm256_set1_epi32(0x77800000)); // 2**112
__m256 f = _mm256_mul_ps(_mm256_castsi256_ps(d), two112);
return f;
}
使用 AVX/AVX2 内在函数,我可以使用以下方法收集 8 个值的集合,1,2 或 4 字节整数,或 4 字节浮点数:
_mm256_i32gather_epi32()
_mm256_i32gather_ps()
但目前,我有一个案例,我正在加载在 nvidia GPU 上生成并存储为 FP16 值的数据。我如何对这些值进行矢量化加载?
到目前为止,我找到了 _mm256_cvtph_ps() intrinsic.
但是,该内在函数的输入是 __m128i 值,而不是 __m256i 值。
查看 Intel Intrinsics 指南,我没有看到将 8 个值存储到 _mm128i 寄存器中的收集操作?
如何将 FP16 值收集到 __m256 寄存器的 8 个通道中?是否可以将它们作为 2 字节短裤向量加载到 __m256i 中,然后以某种方式将其减少为 __m128i 值以传递到转换内部?如果是这样,我还没有找到这样做的内在函数。
更新
我按照@peter-cordes 的建议尝试了演员阵容,但我得到了虚假的结果。另外,我不明白那是怎么回事?
我的 2 字节 int 值存储在 __m256i 中为:
0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX 0000XXXX
那么我怎样才能简单地转换为 __m128i 需要将其紧密打包为
XXXX XXXX XXXX XXXX XXXX XXXX XXXX XXXX
演员会那样做吗?
我当前的代码:
__fp16* fielddensity = ...
__m256i indices = ...
__m256i msk = _mm256_set1_epi32(0xffff);
__m256i d = _mm256_and_si256(_mm256_i32gather_epi32(fielddensity,indices,2), msk);
__m256 v = _mm256_cvtph_ps(_mm256_castsi256_si128(d));
但结果似乎不是 8 个格式正确的值。我认为目前每第二个对我来说都是假的?
确实没有针对 16 位值的收集指令,因此您需要收集 32 位值并忽略其中的一半(并确保您不会不小心从无效内存中读取)。此外,_mm256_cvtph_ps()
需要较低 128 位通道中的所有输入值,不幸的是,没有通道交叉 16 位洗牌(直到 AVX512)。
但是,假设您只有有限的输入值,您可以进行一些位运算(避免 _mm256_cvtph_ps()
)。如果将半精度值加载到 32 位寄存器的上半部分,则可以执行以下操作:
SEEEEEMM MMMMMMMM XXXXXXXX XXXXXXXX // input Sign, Exponent, Mantissa, X=garbage
算术右移 3(这会将符号位保留在需要的位置):
SSSSEEEE EMMMMMMM MMMXXXXX XXXXXXXX
屏蔽掉底部过多的符号位和垃圾(0b1000'11111'11111111111'0000000000000
)
S000EEEE EMMMMMMM MMM00000 00000000
这将是一个有效的单精度浮点数,但指数将偏离 112=127-15
(偏差之间的差异),即您需要将这些值乘以 2**112
(这可能是结合任何后续操作,您打算稍后再做)。请注意,这也会将次正常的 float16 值转换为相应的次正常的 float32 值(也相差 2**112
)。
Un测试内部版本:
__m256 gather_fp16(__fp16 const* fielddensity, __m256i indices){
// subtract 2 bytes from base address to load data into high parts:
int32_t const* base = (int32_t const*) ( fielddensity - 1);
// Gather 32bit values.
// Be aware that this reads two bytes before each desired value,
// i.e., make sure that reading fielddensitiy[-1] is ok!
__m256i d = _mm256_i32gather_epi32(base, indices, 2);
// shift exponent bits to the right place and mask away excessive bits:
d = _mm256_and_si256(_mm256_srai_epi32(d, 3), _mm256_set1_epi32(0x8fffe000));
// scale values to compensate bias difference (could be combined with subsequent operations ...)
__m256 two112 = _mm256_castsi256_ps(_mm256_set1_epi32(0x77800000)); // 2**112
__m256 f = _mm256_mul_ps(_mm256_castsi256_ps(d), two112);
return f;
}