Сбор значений половинного числа с помощью AVX

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

_mm256_i32gather_epi32 ()

_mm256_i32gather_ps ()

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

Пока что я нашел _mm256_cvtph_ps () встроенный.

Однако входными данными для этого внутреннего элемента является значение __m128i, а не значение __m256i.

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

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


person Bram    schedule 16.06.2020    source источник
comment
В процессорах x86 нет аппаратной поддержки для сбора (или разброса) с элементами, более узкими, чем 32-разрядные. Если вам действительно нужен сбор для несмежных значений, да, вы, вероятно, захотите собрать 8x 32-битных элементов и перетасовать их до 8x 16-битных элементов в нижней части __m256i и использовать это как __m128i (с приведением) . Будьте осторожны, чтобы сбор верхнего элемента вашего массива не мог перейти на неотображенную страницу. И да, единственная поддержка x86 для чисел с плавающей точкой половинной точности - это преобразование их в / из одинарной точности (до будущего AVX512).   -  person Peter Cordes    schedule 16.06.2020
comment
Если у вас есть несколько сборок, вы могли бы амортизировать упаковку, перетасовывая или смешивая 2 вектора вместе, а затем переупорядочивая материал после преобразования в float?   -  person Peter Cordes    schedule 16.06.2020
comment
Для 16-битной части сбора: Собирать встроенные AVX2 и 512 для 16-битных целых чисел?   -  person Peter Cordes    schedule 16.06.2020
comment
И, кстати, неудивительно, что инструкция SIMD или встроенная функция, такая как _mm256_cvtph_ps, которая расширяет каждый элемент, имеет вход, который составляет половину ширины его вывода.   -  person Peter Cordes    schedule 16.06.2020
comment
@PeterCordes Спасибо, меня смущает ваше первое утверждение. Я могу собрать 8 байтов, используя значение шкалы «1» в _mm256_i32gather_epi32 (), если впоследствии я замаскирую все старшие биты. Я это проверил. И я почти уверен, что со шкалой 2 я могу сделать то же самое и для 16b int. О приведении: я могу просто сделать (__m128i) со значением __m256i? Я попробую.   -  person Bram    schedule 16.06.2020
comment
Чтобы быть переносимым, вы должны использовать _mm256_castsi256_si128 для преобразования из __m256i в __m128i (хотя преобразование в стиле C может работать на большинстве компиляторов).   -  person chtz    schedule 16.06.2020
comment
@Bram: Насколько я понимаю из этой инструкции, вы на самом деле собираете 8 несовпадающих двойных слов. Конечно, затем вы можете игнорировать или замаскировать все, кроме младших байтов, или, как предлагает Питер, вы можете вместо этого перетасовать их.   -  person Nate Eldredge    schedule 16.06.2020


Ответы (1)


На самом деле нет инструкции по сбору для 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).

Непроверенная внутренняя версия:

__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;
}
person chtz    schedule 16.06.2020
comment
Является ли субнормальное вообще особенным, помимо требования конечного? Думаю, может, нет. Но это было бы, если бы вы попытались изменить масштаб с помощью целочисленного добавления в поле экспоненты вместо умножения FP. - person Peter Cordes; 17.06.2020
comment
Субнормальные значения должны работать, поскольку битовый сдвиг преобразует их в соответствующие субнормальные числа с плавающей запятой 32 (которые также отличаются от субнормальных чисел с плавающей запятой на коэффициент 2**122). Но на самом деле я этого не проверял. Если бы не было субнормальных входов, окончательное умножение действительно могло бы быть произведено целочисленным сложением. Умножение с плавающей запятой имеет дополнительное преимущество, заключающееся в том, что его можно комбинировать (возможно, в FMA) с некоторыми последующими операциями с плавающей запятой. - person chtz; 17.06.2020
comment
Спасибо за обнаружение 122-й опечатки (я также сделал это в комментариях к источнику, но константа должна быть хорошей (может быть, написать (127+127-15)<<23 было бы лучше) - person chtz; 17.06.2020
comment
Возможно, также стоит добавить комментарий в блок кода о загрузке 2 байта перед каждым элементом. И в тексте более четко говорится о последствиях: это может сломаться для массива, выровненного по странице, если ему не предшествует отображенная страница, если вы соберете элемент 0. Возможно, легко пропустить новичков, которые действительно не поняли, что это делает или уже обдумывал последствия более широкого элемента раньше. Хорошая идея, кстати, намного лучше, чем то, о чем я думал, с vpblendw 2 vectors + vpshufb + vextracti128 для подачи 2x vcvtph2ps или с некоторыми вариациями на это. - person Peter Cordes; 17.06.2020