3

使用 AVX/AVX2 内在函数,我可以使用以下方法收集 8 个值集,1,2 或 4 字节整数或 4 字节浮点数:

_mm256_i32gather_epi32()

_mm256_i32gather_ps()

但目前,我有一个案例,我正在加载在 nvidia GPU 上生成并存储为 FP16 值的数据。如何对这些值进行矢量化加载?

到目前为止,我找到了_mm256_cvtph_ps() 内在函数

但是,该内在函数的输入是__m128i值,而不是__m256i值。

查看 Intel Intrinsics Guide,我没有看到将 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 个正确形成的值。我认为目前每第二个对我来说都是假的?

4

1 回答 1

3

确实没有针对 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 值(也偏离 0 倍2**112)。

未经测试的内在版本:

__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;
}
于 2020-06-16T21:52:21.990 回答