使用 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;
}