はじめに
- この記事はひとりNEONアドベントカレンダー2020 21日目の記事です
- 昨日は内積命令を紹介した
- 今日は
fp16
命令を紹介する
TL;DR
- fp16命令は、Arm v7とArm v8で微妙に指す範囲が違う。
- Arm v7 で提供されるのは、fp32(いわゆる
float
)から、fp16への変換と逆変換のみで、厳密には拡張命令 - Arm v8 では、変換命令も含めて命令セットに含まれたが、fp16のままの演算命令がv8.2で追加された
Arm v7 の場合
- 実質的に「使える」fp16命令は、以下の2つのみ1
vcvt_f16_f32 (float32x4_t __a)
vcvt_f32_f16 (float16x4_t __a)
- 前者は
float16x4_t
型を返し、後者はfloat32x4_t
型を返す - 変換だけじゃ意味ないじゃん、と思うなかれ。GPUに渡したりする際に、予めfp16に変換しておくことで、転送コストを下げられるのである。(昔の筆者のSlideShareとかに書いた)
- この2命令だけ、NEONの範囲外で、
-mfpu=neon-fp16
というオプションをGCC
にコンパイル時に渡す必要がある
g++ -mfpu=neon-fp16 fp16.cpp
- また、
arm_neon.h
にもその様子が見て取れる。以下はRaspberry Pi 3 (OSは32bitなので、Arm v7)のarm_neon.h
#pragma GCC push_options
#pragma GCC target ("fpu=neon-fp16")
#if defined (__ARM_FP16_FORMAT_IEEE) || defined (__ARM_FP16_FORMAT_ALTERNATIVE)
__extension__ static __inline float16x4_t __attribute__ ((__always_inline__))
vcvt_f16_f32 (float32x4_t __a)
{
return (float16x4_t)__builtin_neon_vcvtv4hfv4sf (__a);
}
#endif
#if defined (__ARM_FP16_FORMAT_IEEE) || defined (__ARM_FP16_FORMAT_ALTERNATIVE)
__extension__ static __inline float32x4_t __attribute__ ((__always_inline__))
vcvt_f32_f16 (float16x4_t __a)
{
return (float32x4_t)__builtin_neon_vcvtv4sfv4hf (__a);
}
#endif
#pragma GCC pop_options
-
#pragma GCC target ("fpu=neon-fp16")
が示す通り、-mfpu=neon-fp16
オプションが渡されない限りこのセクションは無効で、コンパイルエラーになる - このセクションは
push_options
からpop_options
までの間であり、定義されてるのはわずか2つのintrinsic関数のみである - これも、
/proc/cpuinfo
で確認できる - 以下は、Raspberry Pi 3 (32bitのRaspbian OS)で
/proc/cpuinfo
を確認した結果
$ cat /proc/cpuinfo
processor : 0
model name : ARMv7 Processor rev 4 (v7l)
BogoMIPS : 76.80
Features : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer : 0x41
CPU architecture: 7
CPU variant : 0x0
CPU part : 0xd03
CPU revision : 4
:
- 初日に表した通り、
Features
の行にneon
の文字も見えるが、先頭にあるのはhalf
である - この
half
フラグが立ってることで前述の2命令が使えるようになる - 拡張命令の組み合わせで言えば、NEON拡張命令はサポートするけど、
half
の2命令「だけ」サポートしないCPUというのは理論上あり得ることになる。しかし、筆者はそんなCPU見たこと無いので、見たことある人は是非コメント欄で教えて欲しい
Arm v8
- Arm v8 では、Arm v7で提供されていた変換命令は、通常の命令セットに含まれたので、NEON同様、実行時にチェックしなくても変換命令が使えることが保証されている
- 一方で、DLの流行を背景に、fp16のままCPUで演算したい需要が高まった
- そこで、Arm はv8.2の拡張命令で、fp16のまま演算できる命令セットを追加した。
- NVIDIA Jetson AGX Xavier や ODROID-C4などでこの拡張命令が使える
$ cat /proc/cpuinfo
processor : 0
model name : ARMv8 Processor rev 0 (v8l)
BogoMIPS : 62.50
Features : fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp
CPU implementer : 0x4e
CPU architecture: 8
CPU variant : 0x0
CPU part : 0x004
CPU revision : 0
MTS version : 43306594
- 上記はJetson AGX Xavier 上の
/proc/cpuinfo
である - どちらがそうなのかは不明だが、
Features
欄の末尾にあるfphp
とasimdhp
がfp16のまま演算できる命令をサポートしている証である
命令セット | 機能 | サポート対象 |
---|---|---|
Armv7 | NEON | 拡張命令(neon) |
Armv7 | fp16(変換) | 拡張命令(half) |
Armv7 | fp16(演算) | 未サポート |
Armv8 | NEON | Armv8の命令セット内 |
Armv8 | fp16(変換) | Armv8の命令セット内 |
Armv8 | fp16(演算) | 拡張命令セット(fphp、asimdhp) |
-
トリッキーなのは、Raspberry Pi 3みたいな、「チップはArm v8(Cortex A53)なんだけれど、OSが32bit」の場合、チップがその命令をサポートしていても、OSがサポートしないため、
illegal instruction
となってしまう。 2 -
演算命令一覧(一部抜粋)
$ grep ^v.*f16 /usr/lib/gcc/aarch64-linux-gnu/7.5.0/include/arm_neon.h
vabs_f16 (float16x4_t __a)
vabsq_f16 (float16x8_t __a)
vceqz_f16 (float16x4_t __a)
vceqzq_f16 (float16x8_t __a)
vcgez_f16 (float16x4_t __a)
vcgezq_f16 (float16x8_t __a)
vcgtz_f16 (float16x4_t __a)
vcgtzq_f16 (float16x8_t __a)
vclez_f16 (float16x4_t __a)
vclezq_f16 (float16x8_t __a)
vcltz_f16 (float16x4_t __a)
vcltzq_f16 (float16x8_t __a)
vcvt_f16_s16 (int16x4_t __a)
vcvtq_f16_s16 (int16x8_t __a)
vcvt_f16_u16 (uint16x4_t __a)
vcvtq_f16_u16 (uint16x8_t __a)
vcvt_s16_f16 (float16x4_t __a)
vcvtq_s16_f16 (float16x8_t __a)
vcvt_u16_f16 (float16x4_t __a)
vcvtq_u16_f16 (float16x8_t __a)
vcvta_s16_f16 (float16x4_t __a)
vcvtaq_s16_f16 (float16x8_t __a)
vcvta_u16_f16 (float16x4_t __a)
vcvtaq_u16_f16 (float16x8_t __a)
vcvtm_s16_f16 (float16x4_t __a)
vcvtmq_s16_f16 (float16x8_t __a)
vcvtm_u16_f16 (float16x4_t __a)
vcvtmq_u16_f16 (float16x8_t __a)
vcvtn_s16_f16 (float16x4_t __a)
vcvtnq_s16_f16 (float16x8_t __a)
vcvtn_u16_f16 (float16x4_t __a)
vcvtnq_u16_f16 (float16x8_t __a)
:
vfmaq_f16 (float16x8_t __a, float16x8_t __b, float16x8_t __c)
- DL目的だと、
vfmaq_f16
を呼ぶ人が多いんじゃないかなぁ、という感想。
bfloat16
- ネタ切れという訳ではないが、Arm のv8.6で拡張命令として
bfloat16
対応がアナウンスされた - 公式命令リファレンスにも命令が表示されている
- 参考までに
-
bfloat16
はIEEE754
準拠ではなく、最近のDL目的で使われるようになった、fp16とfp32の中間のようなフォーマット - bit幅は16bitだが、指数部がfp32と同じく8bitあり、その分仮数部が7bitしかない
-
- Arm v8.6ではbfloat16のままの演算命令が提供される(らしい)。手元に対応ボード/チップが無いので未検証
終わりに
- Arm v7と Arm v8におけるfp16命令、およびArm v8.2で追加されたfp16のまま演算する拡張命令を紹介した
- 明日も手島の執筆の予定で、現時点で原稿は白紙である。マジで明日何を書こう。
-
「使える」と書いたのは、
f16
を末尾に持つ命令は他にも存在する。例えばロードのvld1_f16
とか、reinterpret
命令など存在するが、中身をいじる命令は、変換命令の2つのみである。 ↩ -
厳密に言えば、fp16命令関連に限り、Raspberry Pi 3で困ることは無い。もともとCPU がCortex A53であり、A53でサポートされてるfp16関連の命令は変換の2命令だけである。よって32bit OS上でも、変換命令はサポートされており、利用できる。ただ、
div
やsqrt
の回でも触れたが、Raspberry Pi 3のSoCには、double
の命令やfloat
のdiv
命令やsqrt
命令がサポートされている。されているのだが、OSが当該命令をサポートしていないため、CPUにはあるのにソフトウェアからは利用できない状況に陥り、ぐぬぬぬぬ、となる。昔、フォーラムかSOかどこかで、「Raspberry Pi上で64bitOS動かしたい人は何がしたいの?SoCだからメモリだって決め打ちで拡張できないし、そんな性能差出ないよね?32bitOSでも問題無いじゃない?」と言う意見を目にしたことがあった。「大アリだよ!!」 ↩