Сбор значений половинного числа с помощью AVX - PullRequest
3 голосов
/ 16 июня 2020

Используя встроенные функции AVX / AVX2, я могу собрать наборы из 8 значений, 1,2- или 4-байтовых целых чисел или 4-байтовых чисел с плавающей запятой, используя:

_mm256_i32gather_epi32 ()

_mm256_i32gather_ps ()

Но в настоящее время у меня есть случай, когда я загружаю данные, которые были сгенерированы на графическом процессоре nvidia и сохранены как значения FP16. Как я могу сделать векторизованную загрузку этих значений?

До сих пор я нашел _mm256_cvtph_ps () intrinsi c.

Однако, ввод для этого intrinsi c - это значение __ m128i , а не значение __ m256i .

Глядя на Intel Intrinsics Guide, я не вижу операций сбора, которые хранят 8 значений в регистр _mm128i?

Как я могу собрать значения FP16 в 8 дорожек регистра __m256? Можно ли векторно загрузить их как 2-байтовые шорты в __m256i, а затем как-то уменьшить это до значения __m128i, которое будет передано в преобразование intrinsi c? Если это так, я не нашел встроенных функций для этого.

ОБНОВЛЕНИЕ

Я пробовал приведение, как было предложено @ 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 правильно сформированных значений. Я думаю, что каждый второй сейчас для меня фальшивка?

1 Ответ

2 голосов
/ 17 июня 2020
• 1000 Кроме того, для _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 Test intrinsi c версия:

__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;
}
...