ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

NNをNEON FP16で動かそうとしたら遅くなった話

書いてから教えてもらいましたが、ラズパイに乗っているARM Cortex-A53にはFP16対応のNEONは搭載されていません。ですので、すべての演算でFP32への変換が入り遅くなります。@hiratch‏ に感謝。

コキュートスを使って、なんとかボードで意味のあるNNを動かそうと試行錯誤しています。

FP16とは

FP16とは半精度浮動小数点数のことです。正確にはこの辺を見てもらうとして

https://ja.wikipedia.org/wiki/%E5%8D%8A%E7%B2%BE%E5%BA%A6%E6%B5%AE%E5%8B%95%E5%B0%8F%E6%95%B0%E7%82%B9%E6%95%B0

要するに通常の浮動小数点数が32bitなので、それを半分の精度の16bitで実装するフォーマットです。Deep Learningにおいては、メモリアクセスがボトルネックになる事があり、その場合データ量が半分になるので速度の向上が見込めます。またARM NEONを使う時でも、64bitレジスタの中に倍のデータが格納でき計算が早くなることが期待できます。
欠点としは、当然精度が下がることと、FP16をサポートしていないプロセッサ(x86など)ではデータやソースが共通にできない等があります。

ちなみにFP16のデータを作るにはnumpyを使えば一発です。

環境

ボード

Raspberry Pi 3 MODEL B

Cソース

今回テストに使ったソースはこれ。コキュートスで生成しています。
https://gist.github.com/natsutan/b2379ca023036f27aaeb756e7d6cccb6

全体はtiny-yoloを動かしています。
https://github.com/natsutan/cocytus/tree/master/example/tiny-yolo/c
これの重みデータや途中の変数をFP16に変換しています。

コンパイルオプション

cmakeの中でこのように設定しました。

add_definitions("-pg -Wall -g -Iinc -mfloat-abi=hard -ffast-math -mfp16-format=ieee -Ofast -mcpu=cortex-a53 -mfpu=neon-fp16 -save-temps=cwd")

結果

残念なことに浮動小数点数バージョンが21秒なのに対し、FP16バージョンでは48秒と遅くなってしまいました。

せっかく-save-tempsオプションを使っているので、アセンブラの出力を見てみましょう。
https://gist.github.com/natsutan/95f889c1bab3627b2975598fcc57641b

出したコードはよくわかりませんが、floatからfp16へ変換している箇所がいくつか見つかります。

.LVL26:
	.loc 1 59 0 discriminator 1
	ble	.L18
	ldr	r3, [sp, #36]
	.loc 1 59 0 is_stmt 0
	mov	r7, #0
	ldr	r2, [sp, #52]
	mov	lr, r7
	vcvtb.f32.f16	s1, s1
	vcvtb.f32.f16	s2, s2
	add	r2, r3, r2
	vcvtb.f32.f16	s3, s3
	str	r2, [sp, #16]
	vcvtb.f32.f16	s4, s4
	mov	r2, r3
	vcvtb.f32.f16	s9, s9
	vcvtb.f32.f16	s5, s5
	vcvtb.f32.f16	s6, s6
	vcvtb.f32.f16	s7, s7
	vcvtb.f32.f16	s8, s8

Cソースの浮動小数点数はすべてFP16にしているので、コンパイル時に不要なfp16->float(32bit)->fp16の変換が入っていると思われます。

NEONを直に書くしかないですね。