AWS F1 で始めるサーバーサイド FPGA (2)

server-fpga

このコースの前の記事はこちら

はじめに

この連載では AWS F1 サービスを題材に FPGA を使ったアプリケーションのアクセラレートを体験してみる予定です。手を動かし始める前の準備として、この記事では FPGA アクセラレータの開発環境を紹介します。

FPGA の開発と聞くと、Verilog などのハードウェア記述言語を習得しないといけないと思われるかもしれませんが、実は C++ だけで FPGA アクセラレータを開発できる環境が整っています。C++ で 記述できるとはいえ、ある程度のデジタル回路の知識は必要となります。デジタル回路については他の連載記事が参考になりますのでぜひご覧ください。

FPGA アクセラレータの使い方

FPGA アクセラレータを導入するとき、ベンダーのソリューションを利用する場合と、独自にアクセラレータを開発する場合の 2 通りの方法があります。

前回の記事で紹介した Twitch 社の事例は前者にあたります。自ら FPGA 開発を行うのではなく、NGCodec 社からハードウェアエンコーダー IP を導入して動画配信のデータ転送量削減に成功したのでした。データセンターでサービスを提供している一般的な IT 企業では、FPGA 開発ができるハードウェアエンジニアを抱えていない場合が多いようです。そのためベンダーからソリューションを導入して FPGA アクセラレータを利用するケースが多いかと思います。このような場合、通常は FPGA に関する知識や理解はほとんど必要なく、ベンダーが用意した手順書にしたがってインストールすればすぐに使い始めることができます。

一方、要求に合うソリューションが存在しないときや、独自の技術や製品を開発するときにはアクセラレータの開発が必要になります。この場合、FPGA 開発に関する知識はもちろんのこと、高速化する対象のアルゴリズムとそのハードウェア実装の深い理解が必要になります。もちろん、FPGA 開発の経験が豊富な企業と協力してアクセラレータを開発することも考えられます 。

ここからは、ザイリンクス製 FPGA を対象とするアクセラレータ開発環境の概要を紹介します。

Vitis 統合ソフトウェア プラットフォーム

Vitis (私はバイティスと発音しています) はザイリンクスが無償で提供する FPGA アクセラレータ向け開発環境です。エッジデバイスからオンプレミス (Alveo カード) 、クラウドまで、複数のプラットフォームをサポートするツールです。今回はサーバーサイドである Alveo カードとクラウドに対象を絞り、 Vitis が提供する機能を説明します。

Vitis 統合ソフトウェア プラットフォーム
適応型プラットフォームを活用してエッジやクラウドでの運用を可能にする包括的な設計環境

ここで、サーバーサイド FPGA とは何かを定義しておきます。この連載ではサーバーサイド FPGA を、CPU と FPGA が PCI Express (以下 PCIe) で接続され、CPU と FPGA がそれぞれ独立したメモリを持った構成となっていて、CPU 上で動作するアプリケーションの処理の一部を FPGA にオフロードすることで処理の高速化を実現する形態を指すこととします。

サーバーサイド FPGA の構成

サーバーサイド FPGA でアクセラレータが動く仕組みはおおよそ次のようになります。

  1. CPU 側のメモリから FPGA 側のメモリへ、アクセラレータへの入力データをコピーする


  2. アクセラレータが入力データをメモリから読み出し、処理した結果 (出力データ) をメモリへ書き出す


  3. FPGA側 のメモリから CPU側 のメモリへ出力データをコピーする


※ FPGA 側のメモリを介さずに CPU 側のメモリとアクセラレータとの間で直接データを転送することも考えられますが、現在提供されているプラットフォームではサポートされていないため、ここではひとまず考えないことにします。

このようなアクセラレータの仕組みをゼロから実現するにはどのような開発が必要になるでしょうか。

従来の FPGA アクセラレータ開発

アクセラレータの動作の流れから、FPGA のデザインに必要な機能を考えてみます。少なくとも以下の機能が必要です。

  • アクセラレート対象の演算機能 (カーネルと呼びます)
  • PCIe を介して CPU とデータをやり取りする機能 (DMA)
  • メモリへアクセスする機能 (メモリコントローラ)
  • カーネルの動作を制御する機能 (コントローラ)
  • 機能間でデータや設定を受け渡す機能 (バス)

他にも、実際にアクセラレータを運用するにあたって必要になりそうな機能がいくつか考えられます。

  • 異常検知のために FPGA の温度や電圧値、電流値など、カードの状態を取得する機能
  • 回路をリセットする機能
  • パーシャルリコンフィグで動作中に回路を書き換える機能
  • 出荷後にデザインを更新できるようにカード上のフラッシュを書き換える機能

CPU で動作するソフトウェアにもドライバや API のレベルで FPGA のそれぞれの機能に対応する実装が必要になります。これらの機能をひとつずつブレイクダウンして仕様に落とし込んでいき、ハードウェアとソフトウェアを設計していくことになります。全体を図にすると次のようになります。

さらには、アクセラレータとしてしっかりパフォーマンスを出すためにはカーネル自体の性能を上げる他に、カーネルの稼働率がなるべく高くなるような状況を作り出すことが重要です。そのためには、カーネル実行とデータ転送を並列化し、データ転送時間がアプリケーションの性能 (スループット) に影響を与えないようにします。これにはマルチスレッドによるプログラミングが必要になりますし、カーネル実行とデータ転送を効率よく行うためのスケジューリング機能も必要になります。

なんだか気が遠くなってきました。ゼロから FPGA アクセラレータを完成させるには膨大な作業とノウハウが必要そうですね。

Vitis による FPGA アクセラレータ開発

FPGA はどのようなハードウェアでも実装できる反面、真っさらとも言えるので、アクセラレータを実装するときには開発が難しい側面もあることが分かりました。Vitis はこの課題を解決しようとしています。

Vitis はユーザーが FPGA アクセラレータの本質的な開発に専念するためのツールであり、プラットフォーム (基盤) です。Vitis を使った開発で必要な作業は、カーネルとアプリケーションの設計のみになります。Vitis ではアクセラレータ開発に共通する機能は、あらかじめ設計されたものがプラットフォームとして提供されます。さらに、実装されるカーネルの仕様に応じて必要な回路 (例えばメモリコントローラやデータバスなど) がツールによって自動生成されます。

アプリケーション設計の標準規格である OpenCL Runtime API を利用して、カーネル実行とデータ転送をプログラミングします。OpenCL Runtime API では、FPGA の機能はカーネルとメモリに抽象化され、開発者は FPGA を意識することなくアプリケーションを記述できます。カーネル実行とデータ転送は基本的に非同期で実行され、コマンドキューと呼ばれる概念を用いてそれらのスケジューリングを制御します。一部の API を除いてスレッドセーフです。

カーネル設計では、特定のインターフェース仕様に従ってカーネルを設計することで、ツールにより自動でプラットフォームと接続され、OpenCL Runtime API を介してアプリケーションから起動できるようになります。

HLS によるカーネル設計の例

記事の冒頭で、C++ のみで FPGA アクセラレータを開発できる、と書きました。アプリケーションを C++ で記述できることはもちろんのこと、カーネルに関しても高位合成 (High Level Synthesis、HLS) と呼ばれる技術により C++ でハードウェアを記述することができます。簡単な例を使って HLS によるカーネル設計の例を見てみましょう。なお、説明が長くなってしまうので、ここでは詳細な手順や完全なコードは示しません。

例として、次のような演算を FPGA にオフロードすることを考えます (この例はデータ転送量に対する演算量が少なく、メモリアクセスがボトルネックになる悪い例です!) 。

a と b は一度決めたら変更しないパラメータ (定数ですね)、x と y はメモリ上の配列とします。配列の大きさを n とすると、C++ ではこの演算を次の関数で定義できます。

void func(float* y, float* x, float a, float b, int n) {
  for (int i = 0; i < n; i++) {
    y[i] = a * x[i] + b;
  }
}

カーネルを設計する際、通常は表現したいハードウェアをスクラッチで記述することになるのですが、この例は簡単なため、ソフトウェアのコードをそのままカーネルとすることができます。必要なことは、このコードをどのようにハードウェア化するかをツールに教えてあげることです。次にカーネルのコードを示します。

void func(float* y, float* x, float a, float b, int n) {
#pragma HLS INTERFACE m_axi port=y offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi port=x offset=slave bundle=gmem
#pragma HLS INTERFACE s_axilite port=y      bundle=control
#pragma HLS INTERFACE s_axilite port=x      bundle=control
#pragma HLS INTERFACE s_axilite port=a      bundle=control
#pragma HLS INTERFACE s_axilite port=b      bundle=control
#pragma HLS INTERFACE s_axilite port=n      bundle=control
#pragma HLS INTERFACE s_axilite port=return bundle=control

  for (int i = 0; i < n; i++) {
#pragma HLS PIPELINE
    y[i] = a * x[i] + b;
  }
}

元のソフトウェアのコードに pragma (プラグマ) が追加されています。C++ 言語規格においてプラグマは、コンパイラの処理系依存の動作を制御するための記述方法です。HLS では、C++ のコードをどのようにハードウェア化すべきかをツールに教えるために使用されます。

まずは for ループの中から見ていきましょう。#pragma HLS PIPELINE という指示が追加されています。前回の記事で紹介したように、FPGA は次々と流れてくるデータを処理するのが得意で、これを実現するアーキテクチャをパイプラインと呼ぶのでした。このプラグマは、for ループの中の処理をパイプラインとして実装するように指示するものです。この指示により、for 文は次の図のような回路に変換されます (イメージが伝わりやすいよう簡略化しています)。

左から x の要素が次々と入力されると、右から計算結果の y が次々と出力されていく回路です。これで演算器の部分は完成です。

次に必要なのは、パラメータの a、b、n をカーネルに渡す方法です。これらのパラメータは関数の引数となっており、実行時に値が決まるため、カーネルを実行する直前に設定されます。次のプラグマ記述により、カーネルのレジスタマップ上にこれらの引数が割り当てられ、AXI-Lite スレーブを介してカーネルの外から値を設定できるようになります。

#pragma HLS INTERFACE s_axilite port=a      bundle=control
#pragma HLS INTERFACE s_axilite port=b      bundle=control
#pragma HLS INTERFACE s_axilite port=n      bundle=control

これらのパラメータと同様の次の記述があります。少し不思議に思われるかもしれませんが、この記述はカーネルがレジスタマップを介して実行を制御されることを意味します。Vitis でのカーネル設計ではこの記述が必要と覚えておけば良いでしょう。

#pragma HLS INTERFACE s_axilite port=return bundle=control

最後に必要なのは、x をメモリから読み出して演算器の入力として供給し、演算器から出力される y をメモリへ書き出す回路です。この部分は次のプラグマでハードウェア化が指示されています。

#pragma HLS INTERFACE m_axi port=y offset=slave bundle=gmem
#pragma HLS INTERFACE m_axi port=x offset=slave bundle=gmem
#pragma HLS INTERFACE s_axilite port=y      bundle=control
#pragma HLS INTERFACE s_axilite port=x      bundle=control

x と y のインターフェースを m_axi にするよう指定することで、カーネルがメモリコントローラを介して配列にアクセスするための AXI マスタ回路が生成されます。これに加えて s_axilite を指定しているのは、実行時に決まるポインタの値 (メモリ上のアドレス) をアプリケーションからカーネルに渡すためです。

以上でカーネルができあがりました。このカーネルの全体は次の回路になります。

上記ではソフトウェアの記述をハードウェア化する流れで説明しましたが、そのようなやり方はたいていの場合うまくいきません。実際にはソフトウェアと等価な処理を行うハードウェアアーキテクチャを頭の中で思い描いた上で (きちんと設計図を書いた方が良いです)、そのハードウェアを C++ によって記述していく流れとなります。

まとめ

今回は FPGA アクセラレータ開発環境である Vitis をご紹介し、Vitis が解決する課題についてと Vitis によるカーネル設計の概要をお伝えしました。

次回はいよいよ AWS F1 インスタンスを立ち上げて、Vitis を起動するまでの手順を紹介する予定です。

ザイリンクス株式会社 安藤潤

タイトルとURLをコピーしました