НЕОН эмуляция инструкций ВННИ - PullRequest
2 голосов
/ 10 марта 2020

В процессоре Intel Cascade Lake Intel появились новые инструкции AVX-512 VNNI , которые могут ускорить вывод нейронных сетей на процессор. Я интегрировал их в Simd Library для ускорения S ynet (моя небольшая структура для вывода нейронных сетей) и получил значительное повышение производительности.

Фактически я использовал только одну инструкцию _mm512_dpbusd_epi32 (vpdpbusd), которая позволяет выполнять умножение 8-разрядных целых чисел со знаком и без знака, а затем накапливать их в 32-разрядных целочисленных аккумуляторах.

Будет здорово провести аналоговую оптимизацию для NEON (платформа ARM).

Итак, возникает вопрос:

Существует ли какой-либо аналог инструкции NEON для эмуляции vpdpbusd? Если нет аналога, как лучше всего эмулировать инструкцию?

Ниже приведена скалярная реализация (чтобы лучше понять, что должна делать функция):

inline void pdpbusd(int32x4_t& sum, uint8x16_t input, int8x16_t weight)
{
    for (size_t i = 0; i < 4; ++i)
        for (size_t j = 0; j < 4; ++j)
            sum[i] += int32_t(input[i * 4 + j]) * int32_t(weight[i * 4 + j]);
}

1 Ответ

1 голос
/ 10 марта 2020

Самая простая реализация этого требует 3 инструкции; vmovl.s8, vmovl.u8 для расширения 8-битных значений со знаком и без знака до 16-битных, после чего следует vmlal.s16, чтобы сделать 16-битное умножение со знаком, накопленное в 32-битный регистр. И поскольку vmlal.s16 обрабатывает только 4 элемента, вам потребуется секунда vmlal.s16 для умножения и накопления следующих 4 элементов - так что 4 инструкции для 4 элементов.

Для синтаксиса aarch64 соответствующие инструкции sxtl, uxtl и smlal.

Редактировать: Если выходные элементы должны быть агрегированы горизонтально, нельзя использовать объединенные инструкции умножения с накоплением vmlal. Тогда решение будет vmovl.s8 и vmovl.u8, затем vmul.i16 (для 8 входных элементов), vpaddl.s16 (для агрегирования двух элементов по горизонтали), а затем еще один vpadd.i32 для получения суммы 4 элементов по горизонтали , Итак, 5 инструкций для 8 входных элементов или 10 инструкций для полного 128-битного вектора, за которым следует один окончательный vadd.s32 для накопления окончательного результата в аккумуляторе. На AArch64, эквивалентном vpadd.i32, addp, можно обрабатывать 128-битные векторы, так что это на одну инструкцию меньше.

Если вы используете instrinsics, реализация может выглядеть примерно так:

int32x4_t vpdpbusd(int32x4_t sum, uint8x16_t input, int8x16_t weight) {
    int16x8_t i1 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(input)));
    int16x8_t i2 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(input)));
    int16x8_t w1 = vmovl_s8(vget_low_s8(weight));
    int16x8_t w2 = vmovl_s8(vget_high_s8(weight));
    int16x8_t p1 = vmulq_s16(i1, w1);
    int16x8_t p2 = vmulq_s16(i2, w2);
    int32x4_t s1 = vpaddlq_s16(p1);
    int32x4_t s2 = vpaddlq_s16(p2);
#if defined(__aarch64__)
    int32x4_t s3 = vpaddq_s32(s1, s2);
#else
    int32x4_t s3 = vcombine_s32(
        vpadd_s32(vget_low_s32(s1), vget_high_s32(s1)),
        vpadd_s32(vget_low_s32(s2), vget_high_s32(s2))
    );  
#endif
    sum = vaddq_s32(sum, s3);
    return sum;
}
...