Самая простая реализация этого требует 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;
}