はじめに
以前からFPGAに興味があり、ボード(Altera MAX10など)も持っていたのですが、なかなか手がつけられない状態でした。
別の記事でGPUをOpenCLで触る機会があって、AWSのFPGAではOpenCLのコードを高位合成で動かせると聞いて、今回チャレンジしてみました。
これまでの道のり
以下の記事で色々ためしてみた行列積の計算を、AWSのFPGAでもなるべく高速にやってみようと思いました。
- D言語で始めるOpenCL(1) 導入編
- D言語で始めるOpenCL(2) ローカルメモリー編
- D言語で始めるOpenCL(3) 下準備&行列パディング編
- D言語で始めるOpenCL(4) プライベートメモリー編
なお、ホスト言語はD言語です。D言語はC言語のライブラリとのインターフェイスが充実しており、今回のSDAccel用のOpenCLライブラリも特別な考慮なしに使用できました。
独自拡張部分についても、関数宣言や構造体定義の追加のみで問題ありませんでした。
今回のソースコードのD言語側については、こちらの記事を参照してみてください。
今回のソースコード
先述の通りD言語ですが、カーネルはOpenCL Cです。D言語はC言語と近いので特に読むのに支障はないと思います。多分……。
AWSにFPGAインスタンスを立てる
まずはAWSにf1インスタンスを立てます。
なお、リージョンはバージニア北部(us-east-1
)とします。使えるリージョンが限られているので注意してください。
インスタンス上限変更申請
FPGAが使えるf1インスタンスは最初の状態では上限が0になっていて使えません。サポートセンターで上限緩和を申請する必要があります。
私の場合、サポートセンターへの「ケースの作成」で
- サービス制限の緩和
- EC2インスタンス
- バージニア北部
- f1.2xlarge
- 新しい上限値 1
を選択し、日本語で「FPGA開発のため」などと書いて送信したところ、1営業日ほどで上限が緩和されました。
インスタンス作成
次にFPGAインスタンス作成です。
実は**ソフトウェアエミュレーション・ハードウェアエミュレーションの間はFPGAインスタンスはまだ不要です。**なので、上限解除申請中にも行えます。
必要な手順は下記の通りです。
FPGAを使用するリージョン(バージニア北部)などで、
- AMI MarketplaceでAWSのFPGA Developer AMIを選択
- 「FPGA」で検索すると出てきます。
- インスタンスタイプを選択
- 最初はz1d.2xlargeという巨大インスタンスが選択されていますが、行列積をやる程度であれば、t3.xlargeなどで十分です。
- というか、z1d.2xlargeでもそんなに速くならない……。
-
/dev/sdb
のボリュームサイズを30GB程度に増やす。- プロジェクトファイルが置かれる場所で、デフォルトの5GBだとすぐいっぱいになる。
あとは普通にSSHが行える設定をすれば大丈夫です。/dev/sdb
のボリュームサイズの部分が割と地雷です。
セットアップ
インスタンスが作成できたら、FPGAを使うためのツール群をインストールします。
今回はホスト側をD言語で書いてしまったので、D言語コンパイラのインストールが必要です。
# 人生最良の行動
$ curl -fsS https://dlang.org/install.sh | bash -s dmd
次に、aws-fpgaというAWSのFPGA開発キットをビルドします。
こちらはGithubで配布されているので、git clone
します。
$ cd /home/centos/src/project_data
$ git clone https://github.com/aws/aws-fpga.git
Cloning into 'aws-fpga'...
remote: Enumerating objects: 21, done.
remote: Counting objects: 100% (21/21), done.
remote: Compressing objects: 100% (18/18), done.
remote: Total 7272 (delta 3), reused 10 (delta 2), pack-reused 7251
Receiving objects: 100% (7272/7272), 299.16 MiB | 48.06 MiB/s, done.
Resolving deltas: 100% (3812/3812), done.
Checking out files: 100% (1830/1830), done.
$ cd aws-fpga/
$ source sdaccel_setup.sh
最後のsource sdaccel_setup.sh
は起動ごとに必要になるので、~/.bashrc
などに書いておきます。
ついでに、ビルド時になぜかこれをしないとエラーになるLC_ALL=C
の設定や、D言語の有効化も追加します。
$ echo source /home/centos/src/project_data/aws-fpga/sdaccel_setup.sh >> ~/.bashrc
$ echo export LC_ALL=C >> ~/.bashrc
$ echo 'source ~/dlang/$(~/dlang/install.sh list)/activate' >> ~/.bashrc
以上が済んだら、一度ログアウトしてから再度ログインし、色々ログイン時に設定されることを確認します。
もしこの解説のソースコードを動かす場合、Githubからのgit clone
を行なってください。submodule
の更新も必要です。
$ cd /home/centos/src/project_data
$ git clone https://github.com/outlandkarasu-sandbox/dlang-aws-fpga
$ cd dlang-aws-fpga
$ git submodule update -i
ここまでで環境のセットアップは完了です。次にようやくOpenCLでFPGAを動かしていきます。
OpenCLでFPGAを動かす
SDAccelを使用してOpenCLでFPGAを動かす場合、以下の手順が必要です。
詳しくは、AWS FPGAのGithubリポジトリにまとまっています。
- OpenCL CでFPGA用カーネルのソースコードを作成する。
- OpenCL Cのソースコードを
xocc
コマンドでビルドする。 - 必要に応じてソフトウェアエミュレーション・ハードウェアエミュレーションでテストを行う。
- AWS FPGA向けイメージ(AFI)を作成する。
-
xclbin
ファイルからawsxclbin
ファイルが作成される。
-
- AFI登録完了まで待つ。
- FPGA搭載インスタンスで、作成された
awsxclbin
ファイルをカーネルとして呼び出す。-
createProgramWithBinary
でawsxclbin
を指定してプログラムを作成し、カーネルを呼び出す。
-
OpenCL CでFPGA用カーネルのソースコードを作成する
詳しくはSDAccel 環境プログラマ ガイドなどを参照すると良さそうです。
また、ワークグループ数の指定や各種最適化用の属性指定などに色々とGPUと異なる部分があります。(後述)
OpenCL Cのソースコードをxocc
コマンドでビルドする。
Makefileを書きました。それほど難しいオプションは無いと思いますが、メモリのバンク指定などがあまりドキュメントに載っていませんでした。
エミュレーションでテストを行う。
ソフトウェアエミュレーション・ハードウェアエミュレーションは、xclbin
ファイルをホストプログラムでclCreateProgramWithBinary
で読み込んで実行することにより行えます。
ソフトウェアとハードウェアのどちらのエミュレーションになるかは、xclbin
ビルド時のTARGET
パラメーターに拠ります。
実行速度は ソフトウェアエミュレーション >>> ハードウェアエミュレーション なので、まずはTARGET=sw_emu
でテストすることをお勧めします。
ハードウェアエミュレーションでは、カーネルのレイテンシや使用ロジックセル数などの細かいレポートが出るようです。ただし、処理するデータ量を1KB以上にするとものすごく時間がかかりました。
頻繁にビルドとテストを繰り返す場合、float
であれば10〜100個程度のデータでのみ確認するとよいようです。
AWS FPGA向けイメージ(AFI)の登録
この作業はAWS FPGA特有のものになります。(多分)
AWSのFPGAはAFIというものに本来のFPGA用ビットストリームを格納し、OpenCLでの実行時にそれをコンフィギュレーションして動くようです。
そのためのホストプログラム用の情報がawsxclbin
であるようです。
なお、AFI登録コマンドを実行してから実際に登録されるまで30分程度掛かります。
aws ec2 describe-fpga-images
で状態を確認でき、そちらがavailable
になってから初めて使用可能になります。
awsxclbin
ファイルをカーネルとして呼び出す
先述の通りawsxclbin
をclCreateProgramWithBinary
で読み込んで実行することになります。
今までのOpenCLカーネルと同様に呼び出せますが、OpenCLのバージョンがEmbedded
のため、いくつかの関数(clEnqueueCopyBuffer
など?)が使用できないのに注意が必要です。
また、実行時にはハードウェアアクセスする関係でsudo
が必要になります。下記のような感じです。
$ sudo sh
# source /opt/xilinx/xrt/setup.sh
# ./product #実行
SDAccelでの最適化
実際に動かしたカーネルはこちら。
試行錯誤の末に、こんな形になりました……。
それぞれどんな最適化を考慮したか記載します。
ワークグループ設定
reqd_work_group_size
属性でワークグループサイズを指定するのが大事なようです。
reqd_work_group_size
属性をつけると、そのワークグループサイズでなければ実行できなくなります。(異なるサイズを指定するとエラー)
この値を元に、後述のパイプラインの展開などを行なっているようです。
パイプライン
FPGAの並列化は主にループのパイプライン化で行われるようです。
パイプライン化を行う指定はxcl_pipeline_workitems
属性などで行えます。
xcl_pipeline_workitems
属性では、ワークアイテム別にパイプラインの異なるステージを順次実行するようです。
パイプライン化が完全に行えた場合、xcl_pipeline_workitems
を指定した内側は平均1クロックで処理できます。
大きなローカル変数などをループ間で共有するようなコーディングをしていると、パイプライン展開で整合性が崩れたりするのか、計算結果が謝ってしまう場合がありました。
OpenCL Cとはいえ、パイプライン化がされることを意識して、ループ間の依存や変数の共有が無くなるよう試行錯誤する必要がありそうです。
配列のパーティショニング
ローカルの配列変数は、FPGAのBlockRAMが割り当てられるようです。
このとき、BlockRAMに接続するためのポートが用意されますが、デフォルトでは1つの変数に1ポートしか用意されず、並列アクセスが行えなくなるようです。
xcl_array_partition
属性を指定することでバンク分割などを行い、パイプラインステージなどからの同時アクセスが可能になります。
コアを増やす
これはOpenCL Cのコードの外側の設定になりますが、xocc
コマンドの--nk
オプションで論理合成するカーネルの数を指定できます。
カーネルの数が増えると、同時実行できるワークグループの数が増え、高速化できるようです。
ワークグループの設定をちゃんとやっていれば恩恵が受けられます。
ただし、今はまだ最大で16までしか指定できないようです。
メモリのバンクを割り当てる
カーネルが読み書きするOpenCLバッファーをグローバルメモリーの別々のバンクに割り当てることで、グローバルメモリーの帯域幅を広く使えるようになります。
xocc
へのオプション指定も必要になります。
xocc -c \
-k product \
--target hw \
--platform ${AWS_PLATFORM} \
--max_memory_ports product \ #productカーネルにメモリーポートを最大数割り振る。
--save-temps \
-o product.xo \
product.cl
xocc -l \
--target hw \
--platform ${AWS_PLATFORM} \
--max_memory_ports product \ #productカーネルにメモリーポートを最大数割り振る。
--sp product_1.m_axi_gmem0:bank0 \ #各メモリーポートを何番のバンクに割り当てるか
--sp product_1.m_axi_gmem1:bank1 \ #m_axi_gmem0〜2はカーネル引数の順序になっている。
--sp product_1.m_axi_gmem2:bank2 \ #カーネルインスタンス数を増やす場合、product_2について同様の指定を追加する。
--nk product:1 \
--save-temps \
--profile_kernel data:all:all:all:all \
--report_level estimate --report_dir ./report \
-o product.xclbin \
product.xo
結果
先ほどのソースの通りに頑張って最適化したものを動かしたのですが、まだ60GFLOPS程度しか出せていません……。ビルド時間の制約などでFPGAの能力を出し切っていないように思います。
本当にGPUと対抗するつもりであれば、やはりHDLでカーネルを書く必要があるのかもしれません。
今後は、手元のボードでHDLをちゃんと勉強していきたいです。
ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。