低価格FPGAでニューラルネットワークの推論実行を並列化して高速化を図る:FPGAにニューラルネットワークを実装する(5)(1/2 ページ)
FPGAにニューラルネットワークを実装するプロセスを学ぶ本連載。第5回では、低価格FPGAである「Tang Nano 9K」を用いた文字認識AIの推論実行について、並列化による高速化を試みたので、その結果を紹介する。
はじめに
ニューラルネットワークの計算処理にFPGAを使う大きなモチベーションの一つとして並列化が挙げられます。並列化した分だけパフォーマンスの向上を図れますし、クロックを落とせば消費電力を低減することができます。
本連載のタイトルである「FPGAにニューラルネットワークを実装する」ことは、大規模データセンターなどで深層学習系アプリケーションのパフォーマンスを上げるためにCPUのオフロードアクセラレーターとして使うケースもあるでしょう。この場合、データセンターの廃熱対策、言い直せば省電力化にもつながります。
一方、IoT(モノのインターネット)的にAI(人工知能)系デバイスをエッジにばらまくときにも役立ちます。その際、電源供給のインフラが整っている場合は別として、それ以外のフィールドに設置される場合は太陽光や風力、水力などの自然エネルギーかバッテリー、あるいはその組み合わせのシステムに頼るしかありません。そんな時、デバイスの消費電力を抑えることがシステム設計のキーとなります。
そんなわけで今回は、ハイパフォーマンスを狙うか低消費電力を狙うかはさておき、FPGAを使うことでさまざまな恩恵を享受できる、ニューラルネットワークの推論部の並列化についてお話します。
⇒連載「FPGAにニューラルネットワークを実装する」バックナンバーはこちら
筆者の今岡氏が「Open Source Conference 2024 Tokyo/Fall」にブース出展!
東京・浅草で開催されるオープンソースの祭典「Open Source Conference 2024 Tokyo/Fall」に本連載筆者の今岡氏が「FPGAスタートアップ」としてブース出展します。「FPGA以外でもMONOistに掲載した記事のことなら何でもお答えします」とのことです。詳細は以下のイベントURL、展示ブース一覧のURLからご確認ください。
日時:2024年10月26日(土)10:00〜16:00
会場:東京都立産業貿易センター台東館 7階
団体名:FPGAスタートアップ
イベントURL:https://event.ospn.jp/osc2024-fall/
展示ブース一覧のURL:https://event.ospn.jp/osc2024-fall/exhibit
ソースコードの説明
では今回使用するソースコードを見てみましょう。以下のGitHubのリポジトリからソースコードの「neuro.v」を入手してください。コードについて説明する際の行番号などはこちらを参照してください。
リスト1は、最新版の1つ前のリポジトリにアップロードしたソースコードのコメント部です。リスト2は執筆時最新版のソースコードのコメント部です。いずれもVerilogで記述しています。
前回の連載第4回では、学習ロジックをFPGAで行えるようにして、かつ学習ロジックの並列化を図りました。
今回はリスト2のコメントにある通り、推論部分の並列化を行います。1つ前のソースコードでは学習時にノイズを混入するコードにしていますが、執筆時最新版のソースコードではその部分は削除しています。
1: // Parallelized reasoning. 2: // Noise is mixed in during learning. 3: // The commented out line is also left.
1: // noise is not mixed in during learning. 2: // Comment out lines were deleted.
ここからソースコード本文の説明に入るのですが、推論部分を比較してみます。前回の連載第4回で示した、最新版の1つ前のソースコードをまずおさらいしておきましょう。
リスト3は推論部分に当たる24行目です。なぜこのままでは並列化できないかについて説明します。その前に、この24行目の式について少し説明しておきます。
sum = sum + ((neuros[m]==1)?links[(k<<4)+(k<<3)+k+m]:(-links[(k<<4)+(k<<3)+k+m]));
sum = sum + ((neuros[m]==1)?links[25*k+m]:(-links[25*k+m]))
linksの添え字を計算する場合、リスト4のように素直にkに25を掛けてそれにmを足すのが常とう手段ですが、乗算を使うとFPGAのリソースを消費するのではないかと思い、kを25倍する代わりにシフト演算を使いました。結局のところシフト演算にしたからといってFPGAのリソースを節約できたかというとそうでもなく、あまり効果はありませんでした。しかし、掛け算をシフト演算に置き換えて実装した方が消費するリソースを削減する効果がある場合があります。この例だと25という定数を掛けていますが、この方法は掛け算する一方が定数である必要があります。
リスト3を見ると、上位ビットに向けてkを4つシフトさせて、それとkを上位ビット3つシフトさせたものとkを足します。この処理でkに25を掛けたことになります。
どうしてそうなるかというと、まず25をバイナリで表すと“11001”となります。このバイナリの表し方は左が上位ビット右が下位ビットとなります。それで25をある変数に掛けるためにはそれぞれビットが立ってる桁数分上位ビット方向にシフトさせてそれらを合計すると25掛けたことになります。
このあたりの話は、別連載の「オリジナルCPUでバイナリコード入門」などでも取り上げたいと思っています。
次のリスト5で示すコードは並列化できない例です。Verilogでfor文を使うとそれらのループはコードに展開されるます。例えば、24行目の代入文をノンブロッキングモードに書き換えれば並列化できそうですが、話はそう単純ではなくて、依存関係を残したままノンブロッキング代入を使うと思った通りの値が得られないことが多々あります。そこはPCのプログラミングとは別のFPGAならではの配慮というか注意が必要なところです。
リスト5において、なぜノンブロッキングモードにするだけではダメかというと、それは実行順序に依存するコードを含んでいるのが要因となります。
というわけで、22行目と24行目を考察してみてください。22行目と24行目のsumの使い方が順序依存になっていることが分かります。
20: if (btn[0]==0)if (counter[22]==0) begin 21: for (k=0;k<25;k=k+1) begin 22: sum=0; 23: for (m=0;m<25;m=m+1) 24: sum = sum + ((neuros[m]==1)?links[(k<<4)+(k<<3)+k+m]:(-links[(k<<4)+(k<<3)+k+m])); 25: neuros[k]=(sum>0)?1:0; 26: end 27: end
Copyright © ITmedia, Inc. All Rights Reserved.