LoginSignup
3
0

More than 3 years have passed since last update.

内積命令

Last updated at Posted at 2020-12-19

はじめに

dot.png

$ grep ^vdot /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h
vdot_u32 (uint32x2_t __r, uint8x8_t __a, uint8x8_t __b)
vdotq_u32 (uint32x4_t __r, uint8x16_t __a, uint8x16_t __b)
vdot_s32 (int32x2_t __r, int8x8_t __a, int8x8_t __b)
vdotq_s32 (int32x4_t __r, int8x16_t __a, int8x16_t __b)
vdot_lane_u32 (uint32x2_t __r, uint8x8_t __a, uint8x8_t __b, const int __index)
vdot_laneq_u32 (uint32x2_t __r, uint8x8_t __a, uint8x16_t __b,
vdotq_lane_u32 (uint32x4_t __r, uint8x16_t __a, uint8x8_t __b,
vdotq_laneq_u32 (uint32x4_t __r, uint8x16_t __a, uint8x16_t __b,
vdot_lane_s32 (int32x2_t __r, int8x8_t __a, int8x8_t __b, const int __index)
vdot_laneq_s32 (int32x2_t __r, int8x8_t __a, int8x16_t __b, const int __index)
vdotq_lane_s32 (int32x4_t __r, int8x16_t __a, int8x8_t __b, const int __index)
vdotq_laneq_s32 (int32x4_t __r, int8x16_t __a, int8x16_t __b, const int __index)

dot命令

  • 引数はint32x4_t型を1つ、int8x16_t型を2つ取る。もしくは符号なしの全く同じ型を引数に取る
  • 第2引数と第3引数の各要素ごとに積を求め、4つずつまとめて総和を取る
  • そして第1引数に加算する

dot_lane命令

  • 4要素の積をとって1つの和に集約して、第1引数に加算する演算はdot命令と同じ
  • 対応する要素を使うのでなく、第3引数のうち、第4引数で指定したレーンを使用する
    • なお、第3引数はint8x16_t型だが、指定できるレーンは[0:3]の範囲。つまり、第3引数は要素が16個あるが、4要素ごとの集まりとみなしている
  • dup_lane命令と同じくqが付く場所によって、4パターン存在する
命令 第1、第2引数及び戻り値のbit幅 第3引数のbit幅
dot_lane 64bit 64bit
dotq_lane 128bit 64bit
dot_laneq 64bit 128bit
dotq_laneq 128bit 128bit

OpenCVでの対応状況

intrin_neon.hpp
inline v_uint32x4 v_dotprod_expand(const v_uint8x16& a, const v_uint8x16& b)
{
#if CV_NEON_DOT
    return v_uint32x4(vdotq_u32(vdupq_n_u32(0), a.val, b.val));
#else
    const uint8x16_t zero   = vreinterpretq_u8_u32(vdupq_n_u32(0));
    const uint8x16_t mask   = vreinterpretq_u8_u32(vdupq_n_u32(0x00FF00FF));
    const uint16x8_t zero32 = vreinterpretq_u16_u32(vdupq_n_u32(0));
    const uint16x8_t mask32 = vreinterpretq_u16_u32(vdupq_n_u32(0x0000FFFF));

    uint16x8_t even = vmulq_u16(vreinterpretq_u16_u8(vbslq_u8(mask, a.val, zero)),
                                vreinterpretq_u16_u8(vbslq_u8(mask, b.val, zero)));
    uint16x8_t odd  = vmulq_u16(vshrq_n_u16(vreinterpretq_u16_u8(a.val), 8),
                                vshrq_n_u16(vreinterpretq_u16_u8(b.val), 8));

    uint32x4_t s0 = vaddq_u32(vreinterpretq_u32_u16(vbslq_u16(mask32, even, zero32)),
                              vreinterpretq_u32_u16(vbslq_u16(mask32, odd,  zero32)));
    uint32x4_t s1 = vaddq_u32(vshrq_n_u32(vreinterpretq_u32_u16(even), 16),
                              vshrq_n_u32(vreinterpretq_u32_u16(odd),  16));
    return v_uint32x4(vaddq_u32(s0, s1));
#endif
}
  • v_dotprod_expand命令の内部でvdotq_u32が使われているのが確認できる
  • しかし、肝心のCV_NEON_DOT0で決め打ちなので、ちゃんとまだサポートされてないっぽい
intrin_neon.hpp
// TODO
#define CV_NEON_DOT 0

追記

  • 初日にArm v8.2命令には深入りしない、と書いたものの、この命令はdotprod拡張命令であり、Arm v8.2で導入された拡張命令の一部である
  • 残念ながら手元にはdotprod命令に対応したSoCを載せたSBCは無いので、解説はこれぐらいで

おわりに

  • 今日は内積を計算するdotprod命令を紹介した1
  • 明日も手島の執筆予定で、何を書こう。。。。

  1. 内積命令と言っているけれど、8bit4つをとって32bitに足し合わせる挙動はINT8のDNN推論演算そのものである。なので、おそらくDeep Learningのブームを背景に追加された命令だと推測する(個人の感想です) 

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0