__aki_nikki__

分子分光学の研究をしている大学院生の日記です

SYCLの環境構築

SYCLの環境構築

去年の年内まではArch Linuxpacmanintel-one-api-dpcpp-cpp さえinstallしていればうまく動いていたのですが、年度末くらいに久しく触ろうとしたらOpenCLを認識できない等のトラブルが発生してビルドまではできるけど、実行ができないという状況になってしまいました。

色々とトラブルシューティングを試みたのでが、結局解決できなかったのでコンパイラ等のSDKをソースから自分でビルドする方法でやってみたところうまくできたのでblogに残しておこうと思います。

コンパイラSDKをソースからビルドする

intelリポジトリガイドを見るととりあえずGit,CMake,Python,Ninja,hwloc,C++のコンパイラが必要だそうなのでinstall されていなかったhwlocをinstallしました。

$ sudo pacman -S hwloc

まず適当なディレクトリを作成してそこにソースをクローンします。

$ mkdir sycl_workspace
$ cd sycl_workspace
$ git clone https://github.com/intel/llvm -b sycl # 結構時間がかかる!

続いてconfigureしてからビルドを実行します(お決まりのコース!)。ここで注意が必要なのは--native_cpuがないとCPUで実行できるコードをコンパイルできなくなります。

またLanguage Serverとしてclangdを使いたいので--ci-defaultも必要です。 というのも普通のC++用のclangdではSYCLのコードの解析ができないためです。

$ python llvm/buildbot/configure.py --ci-default --native_cpu
$ python llvm/buildbot/compile.py -j 11 # 40分くらいはかかる
$ python llvm/buildbot/compile.py -t clangd -j 11 # 15分くらいはかかる

ビルドが終了するとllvm/build/install内にbin,lib,includeが作られ必要なファイル類が置かれるのでinstallの中身を適当な場所に置いて使うときにPATH,LD_LIBRARY_PATHを設定すれば良いです。 clangdllvm/build/bin内にのみ置かれますが、適当なディレクトリに置いたbinにでもcopyしておけば良いです。

実際にビルド&実行できるかのチェック

実行にはlevel-zero-loaderが必要で、更にintel CPU内蔵のGPUでも実行したければintel-compute-runtimeも必要です。

$ sudo pacman -S intel-compute-runtime level-zero-loader

確認用にまずデバイスが認識されているかを見る以下のコードをビルド & 実行してみました。

check_devices.cpp

#include <sycl/sycl.hpp>

auto main() -> int {
  std::vector<sycl::device> devices = sycl::device::get_devices();

  for (const auto &device : devices) {
    std::cout << "Device: " << device.get_info<sycl::info::device::name>()
              << "\n";
    std::cout << "Vendor: " << device.get_info<sycl::info::device::vendor>()
              << "\n";
    std::cout << "Max Compute Units: "
              << device.get_info<sycl::info::device::max_compute_units>()
              << "\n";
    std::cout << "----------------------------------\n";
  }

  return 0;
}

今回は$HOME/sycl_localにツールチェーンとライブラリ、ヘッダを置いたので、まず以下のようにしたPATH,LD_LIBRARY_PATHの設定を行ないました。

$ export PATH="$HOME/sycl_local/bin:$PATH"
$ export LD_LIBRARY_PATH="$HOME/sycl_local/lib:${LD_LIBRARY_PATH:-}"

ビルドは以下のように行ないました。

$ clang -fsysl -fsysl-targets=native_cpu check_devices.cpp -o check_devices

これを実行してデバイス名が表示されればOKです。私の環境では以下のようになりました。 CPU,GPUが認識されていますね。

Device: Intel(R) UHD Graphics 770
Vendor: Intel(R) Corporation
Max Compute Units: 32
----------------------------------
Device: SYCL Native CPU
Vendor: Intel(R) Corporation
Max Compute Units: 24
----------------------------------
Device: Intel(R) UHD Graphics 770
Vendor: Intel(R) Corporation
Max Compute Units: 32
----------------------------------

最後にGPGPUの入門で良く見るベクトル加算c[i] = a[i] + b[i]の例の以下をCPU/GPUで実行して動作確認しました。

vector_add.cpp

#include <iostream>
#include <sycl/buffer.hpp>
#include <sycl/sycl.hpp>
#include <vector>

template <class T> auto show_vec_1d(const std::vector<T> &array) -> void {
  for (const auto &v : array) {
    std::cout << v << " ";
  }
  std::cout << "\n";
}

auto check_devices(const sycl::device &dev) -> void {
  std::cout << "vendor: " << dev.get_info<sycl::info::device::vendor>() << "\n";
  std::cout << "device: " << dev.get_info<sycl::info::device::name>() << "\n";
  std::cout << "device version: " << dev.get_info<sycl::info::device::version>()
            << "\n";
  std::cout << "driver version: "
            << dev.get_info<sycl::info::device::driver_version>() << "\n";
  std::cout << "mx compute units: "
            << dev.get_info<sycl::info::device::max_compute_units>() << "\n";
  std::cout << "local mem size: "
            << dev.get_info<sycl::info::device::local_mem_size>() << "\n";
  std::cout << "has fp64 :" << dev.has(sycl::aspect::fp64) << "\n";
  std::cout << "has fp16 :" << dev.has(sycl::aspect::fp16) << "\n";
}

auto main(void) -> int {
  std::vector<int> vec_a{1, 2, 3, 4, 5}, vec_b{10, 20, 30, 40, 50};
  auto vec_size = (std::size_t)vec_a.size();

  // impl naive
  std::vector<int> vec_c_naive(vec_size, 0);
  for (auto j = 0; j < vec_size; ++j) {
    vec_c_naive[j] = vec_a[j] + vec_b[j];
  }
  show_vec_1d(vec_c_naive);

  // impl SYCL
  std::vector<int> vec_c_sycl(vec_size, 0);

  // Queue
  // auto q = sycl::queue{sycl::cpu_selector_v, sycl::async_handler{}};
  // use gpu !
  auto q = sycl::queue{sycl::gpu_selector_v, sycl::async_handler{}};
  auto dev = q.get_device();
  std::cout << "information of device now running: \n";
  check_devices(dev);

  {
    sycl::buffer<int, 1> device_a(vec_a.data(), sycl::range<1>(vec_size));
    sycl::buffer<int, 1> device_b(vec_b.data(), sycl::range<1>(vec_size));
    sycl::buffer<int, 1> device_c(vec_c_sycl.data(), sycl::range<1>(vec_size));
    q.submit([&](sycl::handler &cgh) {
      sycl::accessor acc_a(device_a, cgh, sycl::read_only);
      sycl::accessor acc_b(device_b, cgh, sycl::read_only);
      sycl::accessor acc_c(device_c, cgh, sycl::write_only);

      cgh.parallel_for(sycl::range<1>(vec_size), [=](sycl::item<1> id) {
        acc_c[id] = acc_a[id] + acc_b[id];
      });
    });
  }
  show_vec_1d(vec_c_sycl);

  return 0;
}

ここではCPU/GPUの切り替えは簡易的にsycl::queueの構築の際にsycl::cpu_selector_vにするコード、sycl::gpu_selector_vにするコードとしてコメントアウトで切り変えてビルドしまいた。

ビルドはそれぞれ以下のように行いました。

$ clang -fsycl -fsycl-targets=native_cpu vector_add.cpp -o vector_add_cpu # CPU向けにビルド
$ clang -fsycl vector_add.cpp -o vector_add_gpu # GPU向けにビルド

それぞれを実行すると以下のような結果が得られました。 それぞれ正しい結果が得られていますね。

$ ./vector_add_cpu # CPUで実行
11 22 33 44 55
information of device now running:
vendor: Intel(R) Corporation
device: SYCL Native CPU
device version: 0.1
driver version: 0.0.0
mx compute units: 24
local mem size: 32768
has fp64 :1
has fp16 :1
11 22 33 44 55

$ ./vector_add_gpu # GPUで実行
11 22 33 44 55
information of device now running:
vendor: Intel(R) Corporation
device: Intel(R) UHD Graphics 770
device version: 12.2.0
driver version: 1.6.32961
mx compute units: 32
local mem size: 65536
has fp64 :0
has fp16 :1
11 22 33 44 55

感想

ソースからビルドする時に--native_cpuが必要だったりとハマったポイントはありましたが何とかSYCLの環境構築ができました。 syclacademyというチュートリアルを一応一周してはいるのでraytracingなんかをSYCLで書いてみたいと思います。