はじめに
- この記事はひとりNEONアドベントカレンダー2020 20日目の記事です
-
昨日は
mov
命令を紹介した - 本日は内積
dot
命令を紹介する
$ 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要素ごとの集まりとみなしている
- なお、第3引数は
-
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での対応状況
- ついでに、OpenCVでの状況を覗いて見た(2020/12/16時点、masterブランチ(OpenCV 4.1.2以降相当))
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_DOT
が0
で決め打ちなので、ちゃんとまだサポートされてないっぽい
intrin_neon.hpp
// TODO
#define CV_NEON_DOT 0
追記
- 初日にArm v8.2命令には深入りしない、と書いたものの、この命令は
dotprod
拡張命令であり、Arm v8.2で導入された拡張命令の一部である - 残念ながら手元には
dotprod
命令に対応したSoCを載せたSBCは無いので、解説はこれぐらいで
おわりに
- 今日は内積を計算する
dotprod
命令を紹介した1 - 明日も手島の執筆予定で、何を書こう。。。。
-
内積命令と言っているけれど、8bit4つをとって32bitに足し合わせる挙動はINT8のDNN推論演算そのものである。なので、おそらくDeep Learningのブームを背景に追加された命令だと推測する(個人の感想です) ↩