daisukeの技術ブログ

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

量子化:論文 Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference を読む(3)

量子化の3回目です!

Googleが2017年12月にarXivに登録した論文の「Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference」の続きで、今回は 3章の予定でしたが、2章に関係が深い Appendix を先に読んでいきます。

はじめに

量子化の記事一覧です。良かったら参考にしてください。

量子化の記事一覧

今回は、2章に関係が深い Appendix です。

参考サイト

arxiv.org

A. Appendix: Layer-specific details

レイヤごとの詳細が書かれているようです。

ChatGPTに翻訳させて、補足していくスタイルでいきます。

A. Appendix: レイヤ別の詳細

A.1. 数学的関数

双曲線関数、ロジスティック関数、ソフトマックスなどの数学的関数は、ニューラルネットワークによく登場します。これらの関数は純粋な固定小数点演算で実装されており、浮動小数点演算で実装される方法と同様に行われるため、ルックアップテーブルは必要ありません。

活性化関数などは、乗数  M で行われたような固定小数点演算で実装されていると言っています。

A.2. Addition

一部のニューラルネットワークでは、単純に二つのアクティベーション配列を加算するだけの単純な加算レイヤータイプを使用します。

このような加算レイヤーは、量子化推論では浮動小数点よりもコストが高くなります。なぜなら、リスケーリングが必要だからです。

一方の入力を他方のスケールにリスケールするために、セクション2.2の最後で見たように、乗数M = S1/S2を用いた固定小数点乗算が必要となり、実際の加算が単純な整数加算として行われる前に処理しなければなりません。

最後に、結果を出力配列のスケールに合わせるために再度リスケールする必要があります。

通常の浮動小数点数を使う場合は、2つの出力を加算して合流させるネットワーク(ResNetなど)の場合、一方のスケールに、もう一方のスケールをリスケールしてから、加算を行う必要があると言っています。

さらに、加算した後に出力のスケールにリスケールが必要があると言っています。

あらかじめ、前段の2つの出力スケールを、加算した後のスケールにしておけばいいのでは?と思いましたが、それをやってしまうと、出力スケールは 8bit を十分に使えないから精度が悪くなるのだと思います。

A.3. Concatenation

完全に一般的なサポートとして、結合レイヤーは加算レイヤーと同様にリスケーリングの問題を抱えています。

uint8の値のリスケーリングは情報を失う操作になるため、結合は本来ロスレスであるべきなので、私たちはこの問題に対処するために異なるアプローチを採用します。

具体的には、ロスのあるリスケーリングを実装する代わりに、結合レイヤー内のすべての入力アクティベーションと出力アクティベーションが同じ量子化パラメータを持つことを要求します。これにより、リスケーリングが不要となり、結合はロスレスであり、算術操作も不要になります。

結合レイヤとは、複数の出力をチャンネル方向に結合するようなレイヤのことです。

上で言ったようなことを実装しているようです。複数の出力のスケールを同じにすればいいということですね。

B. Appendix: ARM NEON details

ARM NEON とは、ArmのSIMD拡張命令セットです。SIMD は、Single Instruction Multiple Data の略で、1つの命令で、複数のデータに対して処理を実行できます。4つのデータの場合、本来なら4命令かかるところを1命令で処理できるということです。

このセクションでは、ARM NEON命令セットでのアセンブリプログラミングに精通していることを前提としています。以下の命令ニーモニックは64ビットARM命令セットを指していますが、議論は32ビットARM命令にも同様に適用されます。

この記事全体で参照される固定小数点乗算は、SQRDMULH命令に正確にマッピングされます。正しい丸めを行う命令としてSQRDMULHを使用することが非常に重要であり、SQDMULHを使用してはいけません。

セクション2.2で言及された最も近い値への丸めを行う右シフトは、正確にはARM NEONのどの命令にもマッピングされません。

ハードル高いこと言われてます。SQRDMULH命令については次回以降に詳しく調べるとします。セクション 2.2 とは、乗数  M を整数で演算する話ですね。

問題は、「右シフトによる丸め」命令であるRSHLが可変の負オフセットを持つ場合、ゼロから離れるように丸めるのではなく、上方向に丸めてしまうことです。

例えば、RSHLを使って  −12 2^{3} で割る操作を実装すると、その結果は  −2 ではなく  −1 になります。これは「最も近い数に丸める」べきところです。これは全体的な上方向へのバイアスを引き起こし、ニューラルネットワークの推論においてエンドツーエンドの精度の大幅な低下を招くことが観察されています。正しい「最も近い数に丸める」右シフトは、適切な修正算術を組み合わせることでRSHLを使って実装することができます。

行列乗算のコア蓄積の効率的なNEON実装には、次のトリックを使用します。乗算加算操作(10)では、最初にオペランドの型をuint8からint8に変更します(これは量子化された値とゼロ点から128を引くことで行えます)。したがって、コア乗算加算は次のようになります。

RSHL命令についても別途調べますが、-12 を 8 で割ったときに、-1.5 なので、-2 になってほしいところが、-1 になるのが問題だと言ってます。

 \rm{int32} \mathrel{+}= \rm{uint8} × \rm{uint8} \tag{B.1}

セクション3で述べたように、量子化トレーニングプロセスを少し調整することで、重みがint8値に量子化された際に値が−128になることを防ぐことができます。

そのため、(B.1)式での積は−128 × −128にはならず、絶対値が  2^{14} 未満となります。このため、(B.1)式ではローカルなint16アキュムレータに二つの積を蓄積してから、真のint32アキュムレータに蓄積することが可能です。

これにより、8-way SIMD乗算(int8オペランドに対するSMULL)、続いて8-way SIMD乗算加算(int8オペランドに対するSMLAL)、そしてint32アキュムレータへのペアワイズ加算と蓄積(SADALP)が使用できるようになります。

まだ読んでない3章の話が出てきて分からなくなりました。また、分かり次第、追記します。

おわりに

今回は、Appendix の A と B を読みました。Appendix B は、ARM NEON 命令セットについて、もう少し調べる必要がありそうなので、次回に調べたいと思います。

今回はここまでにします!

最後までお読みいただき、ありがとうございました。