メインコンテンツへスキップ

FPGA ハッカソン参加記録

·314 文字·2 分
著者
Ikari Shota
Master’s student at the University of Tokyo.

以前,Agile X FPGA hackathonに参加しました.これは AMD の Kria KV260 を使った FPGA AI デザインハッカソンで、YOLOv5n を FPGA 上で動かし、速度と精度の両立を目指すというものでした. このハッカソンを通して非常に多くの学びを得ることができました. そこでこの記事はその参加記録となります.

本競技のスコアは次式で定義されていました。

$$ \mathrm{score} = \mathrm{mAP} \times \mathrm{FPS} $$
ハッカソンにおける我々の戦略
ハッカソンの全体像と我々の戦略

この記事では大会参加時に何をどのように取り組んだのかを整理します.

Hackathonのベースラインに関して
#

今回の大会のベースラインは YOLOv3系の実装でした。 まずここで分かったのは、精度とスコアのバランスが非常に重要だということでした. 事前評価では、シングルスレッドの YOLOv3 実装は 3.8 FPS, mAP 47.4%, score 180 程度でした。一方で、初期段階で実装した YOLOv3-tiny のマルチスレッド版は、本番動画に対して 52.4 FPS, mAP 24.3%, score 1274 まで伸びました。

この差はかなり象徴的で、推論の処理そのものを速くするだけではなく、その前後処理や表示も含めてパイプライン全体を組み替えた方が効くことを示していました。 そこで戦略を次の 3 本柱に定めました。

  1. YOLOv5n を再学習して精度を確保する。
  2. DPU の構成を比較して、KV260 上で最も throughput が出る設定を探す。
  3. ソフトウェア実装をパイプライン化して、I/O と DPU 実行を重ねる。

モデル選定と学習方針
#

採用したモデルは YOLOv5n でした。理由はかなり実務的で、軽量で再学習しやすく、DPU に載せるところまでの見通しが立ちやすかったからです。

YOLOv5n は CNN ベースで比較的扱いやすく、パラメータ数も少ないモデルです。資料では YOLOv3-tiny の約 4 分の 1 規模とされていて、まず動かして高速化する対象として適していました。さらに Ultralytics の実装が使えるため、ハッカソンの短い期間でも再学習と検証を実施しやすい構成でした。

再学習した YOLOv5n とその比較
再学習した YOLOv5n と比較結果。データセットの改善と量子化・枝刈りの影響をまとめて確認しました。

モデル作成の流れは、おおむね次の通りでした。

  1. Ultralytics ベースで YOLOv5n を再学習する。
  2. DPU で扱いやすい演算になるようにモデル側を調整する。
  3. COCO2017 だけでは足りない分を Open Images v7 で補う。
  4. 人クラスの不足アノテーションは YOLOv11 系で擬似ラベルを生成して補完する。
  5. 全パラメータの約 30% を枝刈りする。
  6. int8 量子化を行い、Vitis AI で .xmodel にコンパイルする。

この段階で、精度は当然落ちます。 だいたい次のような感じでした.

  • 再学習後: mAP 約 45%
  • 枝刈り後: mAP 約 40%
  • 量子化後: mAP 約 34%

つまり、FPGA 上で速く回すための工程を入れるたびに精度は削られます。 ただ、競技では絶対精度を最大化するより、\(\mathrm{mAP} \times \mathrm{FPS}\) を最大化するために,ここではできるだけ高速化の方に割り振ることを考えました。

DPU 構成の比較
#

今回かなり効いたのは、DPU IP の構成を綿密に比較したことでした。KV260 上で試した主な候補は B1024 x 2B4096 x 1 で、最終的には B4096 を 1 基 使う構成を採用しました。

DPU の最適化を比較したスライド
DPU の最適化。B1024 を 2 枚使う場合と B4096 を 1 枚使う場合を比較し、後者を採用しました。

加えて、B4096 側では ALU Parallelism を 8 に設定しました。URAM/DRAM や DSP の設定も含めて FPGA リソースをできるだけ使い切る方向で調整しました。

ここで大事だったのは、DPU 単体の理論性能ではなく、モデルとランタイムを含めた実測で判断したことでした。資料では、同じ YOLOv5n とソフトウェア実装を使った場合、B4096 x 1 は B1024 x 2 より全体で約 11% 高速化 されたとされています。DPU を 2 基にすれば必ず有利、というほど単純ではありませんでした。

ソフトウェア実装の最適化
#

実際に最も効いたのはここでした。推論の流れは次の3段階に整理しました。

  1. ReadFrame: 動画からフレームを読み込む。
  2. RunYOLO: 前処理、DPU 推論、後処理をまとめて行う。
  3. DisplayFrame: 結果を重畳して表示または出力する。

この 3 段階を独立スレッドにし、間にキューを置いて非同期にデータを受け渡す構成にしました。特に RunYOLO は、前処理・推論・後処理をあえて同じスレッド内で処理しています。細かく分割すると、キューに積んだポインタの整合性や画像データの寿命が問題になり、かえって不安定化するためです。

さらに、各段の処理時間とキュー占有率を可視化するツールも作っていました。これがかなり重要で、DPU そのものより ReadFrame や DisplayFrame がボトルネックになる場面が見えるようになりました。

ソフトウェア全体の最適化を示すスライド
ソフトウェア全体の最適化。ReadFrame、Pre-process、DPU、Post-process、DisplayFrame をまとめて見直しました。
可視化ツールによる性能分析と改善のスライド
可視化ツールによる性能分析と改善。推論だけでなく、ReadFrame と DisplayFrame もボトルネックになりました。

ここから入れた具体的な改善は次の通りです。

  • ReadFrame: 動画読み込みを OpenGL 系から ffmpeg ベースに寄せて高速化する。
  • DisplayFrame: 表示処理自体を速くするのではなく、呼び出し回数を間引いて負荷を落とす。
  • PreProcess: 正規化処理を LUT 化して、CPU 側の前処理を軽くする。
  • C++ 実装: メモリ管理やデータ受け渡しの無駄を削り、アイドル時間を減らす。
最適化の結果を示すスライド
最適化の結果。ReadFrame は 3 倍、DisplayFrame は 10 倍、DPU 側の推論処理は 3 倍程度高速化しました。

資料では、ReadFrame のライブラリ切り替えで 約 3 倍、DisplayFrame の呼び出し間引きで 約 10 倍 の改善がありました。 当初は推論処理での計算処理よりCPUでの前処理と後処理の方が重かったために, これらの改善は非常に効きました。

スレッド数とボトルネック
#

RunYOLO は CPU で前後処理を行い、その一部だけを DPU に委譲します。そのため、DPU が動いている間の CPU アイドル時間を埋める目的でスレッド数を増やすのは理にかなっていました。

ただし、増やせば無限に速くなるわけではありません。資料の評価では、RunYOLO のスレッド数を増やすと FPS は上がるものの、4 スレッド前後でほぼ収束 していました。最終実装では RunYOLO をより多めに立てた測定も行っていますが、結局は ReadFrame 側からの供給が頭打ちになり、余ったスレッドは待機状態に入ります。

このあたりも、推論器だけ見ていると判断を誤ります。システム全体の throughput を見るなら、ボトルネックがどの段にあるかを先に押さえる必要がありました。

最終結果
#

最終的な構成では、我々のチームでは次の結果になりました。

  • 本番計測: 177.8 FPS
  • 本番 20 枚評価: mAP 24.55%
  • 1000 枚での再評価: mAP 34.37%
  • 大会順位: 2 位

やはり一番大きかったのは,mAP をある程度保ちつつ FPS を大きく押し上げたことでした。 これはさまざまな創意工夫がなければ届かないスコアだったと思います。

振り返り
#

FPGA に関するハッカソンに参加したのは今回が初めてでしたが、このプロセスを通して、 「まず可視化し、そのうえで性能を定量的に評価すること」の重要性を強く学びました。 統合的なスコアを判断するのではなくて, それぞれの処理時間・キュー占有率・スループットなどを個別に最適化を緻密に行うことで, 本当に効いている改善とそうでない改善を切り分け, そこに注力することで全体の最適化を行うことができたと思います.

全体を通して非常にいい経験を積むことができたと思っております. Hackathon参加中に議論してくださった友人や先輩方,大変ありがとうございました.