CUDAをRadeonで動かす(導入編)

みなさん、今日も元気にGPGPUしていますか?

去年(SC15)の話ですが、「RadeonでCUDAが使えるようにするよ!」とAMDが発表したニュースを覚えている方いらっしゃいますでしょうか。Boltzmann Initiativeの話です。

あれからしばらく時が流れ、機は熟しました。「CUDAはNVIDIA専用」そんな時期は今は昔。そう、CUDAをAMDのGPUであるRadeonで動かすことに成功しました!!

ので、ここではその方法と、その時に使ったコードを紹介したいと思います。

予めお断りしておくと、表題にもある通り、今回は導入編ということで「動くことを確認する」までになります。
そのうち時間を見つけて「性能測定編」もやってみたいと思いますが、ぜひこれを読んだ方、ぜひ性能測定してみてください&結果を教えて下さい!

また、この情報は、本記事執筆時点(つまり2016年7月21日現在)の話です。
Boltzmann Initiativeは現在も活発に進行中で、未来では手順が変わったりする可能性が十分にありますので、ご了承ください。

参考文献

説明に入る前に、最初に参考文献を挙げておきます。
本記事はこれらの文献のまとめになりますので、手順書さえあればできるという方・最新かつ正確な情報がほしい方は、以下を参照してください。

概要

使うのはROCmというプロジェクトです。
ROCmとは、Radeon Open Computeの略のようです。

ROCmはいくつかのプロジェクトのプロジェクトの集合体となっていますが、今回直接的に使うのは以下の3つです

  • Kernel Fusion DriverおよびRuntime群
  • HCC compiler
  • HIP

Kernel Fusion Driver

Kernel Fusion Driver(KFD)とは、ROCmの各種プロジェクトを動かすための「Linuxカーネルと一体となったドライバー」のことです。

なので、後述の手順で説明しますが、カーネルを入れ替える必要があります。
個人的にはkmodでどうにかしてほしいんですが・・・。

ともかく、このKFDを入れることで、AMD Radeonの上で様々なことができるようになります。

HCC compiler

HCC compilerは、KFDの上で動く、異種混合実行環境向けのC++コンパイラーです。
HCCとは、Heterogeneous Compute C++の略のようです。

現在のところ、バックエンドとしてはAMD独自のAMD GCN ISAを生で出力するモードと、HSA財団の策定したHSAILという中間言語を出力するモードがあります。
ただし、今回はRadeonを使うので前者で十分なことと、既定で前者が使われること、加えてそもそも後者はこの先開発はされないようなので、ここではAMD GCN ISAを使うモードを前提として話を進めます
(とは言っても、バックエンドが何であるかはアプリケーション層ではあまり気にする必要はなさそうですので、よく分からない方は飛ばしても大丈夫です)

また、フロントエンドにはC++17 Parallel STL、C++ AMP、OpenMP offload、HC C++ APIなどが用意されています。
今回は説明しませんが、C++ AMPをLinuxで動かしたい場合は、このHCC compilerを使うと動かせるようになります。

HIP

HIPは、CUDAをHCCでコンパイルできるようにするプラットフォームです。今回の話の主役になります。何の略称かは分かりません・・・。

HIP自体は、ほぼCUDAと同じ形式のRuntime APIやカーネル言語を既定するもので、基本的にcuXXXXをhipXXXXに置き換えたものです(例えばcuMemcpyhipMemcpyに対応します)。

HIPで書かれたものは、hipccというコンパイラによって解釈され、バックエンドであるnvccまたはhccに渡すことで、NVIDIAでもAMDでも動かせるようにできます。
このhipccは、CUDAあるいはHC言語そのものへの直接的な翻訳機に近い動きをするので、HIPで書くことにオーバーヘッドなどはなく、性能劣化することは全くないらしいです。

また、hipifyという、CUDAからHIPへ(逆)変換するツールが付属しており、これを用いることで既存のCUDAコードを簡単にHIPが解釈できるようになります。

ということで、このHIPを信じるなら、CUDAで書いたコードを、性能劣化させることなくRadeonでも動く形にすることができる、ということになります。

手順

ということで、実際に動かしてみましょう。

環境の準備

ROCmのREADMEROCm Hardware Requirementsに情報が散乱しているのですが、ROCmを動かすには、現在のところ以下の環境が必須です。

OS
Ubuntu 14.04.04またはFedora23
(おそらく動く:Ubuntu 16.04またはFedora 22)
CPU
Intel Haswell以降またはAMD Zen以降(PCIe Gen3 Atomicsのサポート必須)
GPU
Fiji以降のAMD GPU

今回は、Ubuntu 14.04、Intel Core i7-4790、AMD Radeon R9 FuryXの環境で試しました。

また、カーネルを入れ替えるという性質上、クリーンインストールすることにしました。
既存のシステムの上に入れても動かないわけではないと思いますが、何かあった時にサルベージが大変なので・・・。

あとこれ重要なのですが既存のAMDのプロプライエタリ・ドライバーとは衝突してまともに動かなくなるっぽいです。
公式ブログのコメント欄でしれっと書かれているのですが、
KFDでドライバーも提供されているので、ROCm導入時には既存のプロプライエタリ・ドライバーは除去する必要があります。
つまり、ROCmを入れるとOpenCLは使えなくなるということです(Issue参照)。
将来的にはサポートされるかもしれませんが・・・。

ということで、既存の環境をかなり破壊することになるので、試される方は新しいストレージを持ってきて、クリーンインストールして始めることをオススメします
(これで安全にはなりますが、カーネルの入れ替え等に限らず、ここに書いてあることは全て自己責任でお願いします)

ROCmのインストール

計算機を用意して、OSをクリーンインストールしたら、まずは普通に起動します。

起動したら、とりあえずまずインストールされているパッケージを最新に更新します。
これはセキュリティーリスクもそうですが、今回用意した環境だと後でバージョン違いでやり直しという羽目になったので、ここで一度更新しておくことをオススメします。
Ubuntuならapt-get upgrade、Fedoraならdnf updateですね。

その後、ビルド済みバイナリをAMDのレポジトリから持ってきます。
Ubuntuの場合
Fedoraの場合の両方が用意されています。
今回はUbuntuを使ったので、以下のコマンドを実行しました。

なお、AMDのサーバーがとても遅いのか、恐ろしく時間がかかります。
具体的には、一晩放置しても終わってませんでした。半日まるっとかかるぐらいは覚悟して放置しましょう。

インストールが無事に終わったら再起動します。
再起動後、uname -rとかでカーネル名が4.4.0-kfd-compute-rocm-rel-1.1.1-10などになっていれば新しいカーネルに切り替わっています。

その後、手順に従って、HSAILが動くことを確認しましょう。
ちゃんと動けばインストール成功です。
これで、ROCmの一式が全て入っているので、HCC compilerもHIPも全て/opt/rocmの下にインストールされました。

hipify-clangの導入

先のレポジトリから持ってくる手順で、CUDAからHIPに変換するツールであるhipifyは/opt/rocm/bin/hipifyにインストールされました。
が、実はこの標準のhipifyだけでは変換が不十分になります。
これは、このhipifyがただの文字列置換Perlスクリプトであり、C++の構文解析を一切していないからです。

なので、hipify-clangと言う、clangベースのツールを別に入れる必要があります。
別のツールといっても、clang-hipifyもHIPの中の1つです(↑の手順で入らないのはまだβ版だからのようですね)。

手順は、HIPのmasterレポジトリをダウンロードして、
clang-hipifyのREADMEに従ってビルド&インストールしましょう。
今回は以下のコマンドを実行しました。

これで、/opt/rocm/hip/bin/clang-hipifyがインストールされます。

元にするCUDAコードの用意

ということで準備が終わったので、実際にCUDAコードを動かす手順に移ります。

ということで手始めに元にするコードを用意します。今回は簡単なベクトルの加算を用意しました。

せっかくなので、これをまずは普通にCUDAの動く環境で動かしてみてください(vectorAddに移動してmakeで動くはずです)。

CUDAをHIPに変換する

CUDAコードを用意したら、いよいよ次はCUDAをHIPに変換する時です!

ということで、とりあえず普通についてくるhipifyを使ってみましょう。
hipify [yoursource.code]とやると、標準出力に変換結果が表示されます。
これをやると、例えば

は、

と変換されます。概ね一対一になっていて対応関係はそんなに難しいものではないと思います。

ただ、これは実は不完全です。これを後述のhipccに食わせてもコンパイルできません。
やってみると分かるのですが、カーネル関数の第一引数に必要な物がない、と言われます。
実はHIPの言語仕様として、カーネル関数の最初にはhipLaunchParm lpというものが必要ということになっています。

そのため、通常のhipifyを使っただけでは、変換後に手動でhipLaunchParm lpを挿入する必要があります。面倒ですね。

そこで、先にインストールしたhipify-clangの出番です。
これを使ってhipify-clang [yoursource.code] -o [yourhip.code]とすると、無事に全部変換してくれます。

しかしhipify-clangだけだとまだ不完全なようで、ひとつは、hip_runtime.hのincludeを追加してくれないんですね・・・。
ということで、現時点では正しく自動変換させるには、hipify-clangを書けた後に、通常のhipifyをする必要があるようです。

HIPのビルド

HIPに変換できたら、あとはhipcc [yourhip.code]とするだけでビルドできます。
あとはビルドされた実行ファイルを普通に実行できるはずです。

というわけで、HIPへの変換からビルドまでを一括でやってくれるMakefileを書きました。
ご覧のとおり、このMakefileでは以下のことをやってくれます。

  1. カレントディレクトリの*.cppをHIP Runtime APIを使うように変更し、obj/*.hip.cppに保存
  2. カレントディレクトリの*.cuをHIP Kernel Languageを使うように変更し、obj/*.hip.cuに保存
  3. 各obj/*.hip.c(pp|u)を、hipccでビルドしてobj/*.c(pp|u).oにコンパイル
  4. 全てのobj/*.c(pp|u).oをリンクして実行ファイルを出力

先述のベクトル加算の例にこのMakefileを置いたものも用意しました。
makeとやると、vectorAddという実行ファイルができます。

実行結果はあまりちゃんと書いてませんが、main.cppを読んでもらうと分かる通り、左側の数値がCPUの実行結果で、右側がGPUでの実行結果です。
ちゃんと実行できたことが分かりますね!

おわりに

ということで、CUDAのコードをRadeonで動かすことに成功しました!

ROCmあるいはHIPに関しての日本語での情報がほぼ皆無だったので、この記事が誰かの役に立てばとても嬉しく思います。

なお、最初に書いた通り、今回は「動かす」ことを目的にしたので、特に性能評価はしていません。
そのうち何か良い題材を見つけて性能評価をしてみたいと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です