覚え書き: cmdstanr+OpenCLでGPUをうまく使えたり使えなかったり (Windows-NVIDIA編)

 仕事の都合でStanを使うことがあるんだけど、その環境を用意するのは結構面倒である。
 特に困るのが並列処理。Stanの実装のひとつcmdstanは並列処理の方法をいくつか用意していて、そのひとつがOpenCLライブラリを経由したGPUの利用である。Windows上のRでcmdstanrパッケージを使う場合、その実行の遅さによってストレス死しないためにも、GPUをぜひ利用したい。
 説明を読む分には簡単そうである。ところが、実際に試してみると、これがなかなかうまくいかない。

 というわけで、cmdstanrパッケージでOpenCLを経由してGPUを使う方法について、試行錯誤の記録をメモしておく。すいません、細かい話で。大局的にみれば実にどうでもいい話である。どうでもいいじゃん! どうせ我々の仕事はいずれChatGPTに奪われちゃうんだろうし! 悲惨な老後が待っているんだろうし!

目次

テスト用のStanコード

 テスト用のStanコードは以下のとおり。こちらから頂きました。std_normal_lpdf関数もbernoulli_logit_glm_lpmf関数もOpenCLに対応しているので、サンプリングが速くなることが期待できる。カレントディレクトリにstantest.stanとして置かれている。

data {
  int<lower=1> k;
  int<lower=0> n;
  matrix[n, k] X;
  int y[n];
}
parameters {
  vector[k] beta;
  real alpha;
}
model {
  target += std_normal_lpdf(beta);
  target += std_normal_lpdf(alpha);
  target += bernoulli_logit_glm_lpmf(y | X, alpha, beta);
}

[2023/10/10追記] Stan 2.33.0 から、変数名の後ろにブラケットを置くことでarrayを宣言することができなくなった。これに伴い、Case 7では以下のコードを使用した。5行目を変更しています。カレントディレクトリにstantest2.stanとして置かれている。

data {
  int<lower=1> k;
  int<lower=0> n;
  matrix[n, k] X;
  array[n] int y;
}
parameters {
  vector[k] beta;
  real alpha;
}
model {
  target += std_normal_lpdf(beta);
  target += std_normal_lpdf(alpha);
  target += bernoulli_logit_glm_lpmf(y | X, alpha, beta);
}
Case 1: Win10, R4.2.1, Rtools4.2, cmdstan2.30.1, OpenCL (NVIDIA CUDA11.7.1), 成功

まずは、2022年12月に試した際の成功例を載せておく。Rのバージョンが少し古い。

  • Windows 10 Home (22H2)
  • NVIDIA GeForce GTX 660, ドライババージョン不明(47x.xx)
  • NVIDIA CUDA Toolkit 11.7.1
  • R 4.2.1
  • Rtools 4.2
  • cmdstanrパッケージ(0.5.3安定版)のインストール。
    install.packages("cmdstanr", repos = c("https://mc-stan.org/r-packages/", getOption ("repos")))
  • cmdstan(2.30.1)のインストール。cpp_optionsの設定はこちらの解説を参考にさせて頂きました。赤字のnotesやwarningがたくさん出たと記憶している。
    library(cmdstanr)
    path_to_opencl_lib <- "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.7/lib/x64"
    cpp_options <- list(
      "CXXFLAGS += fpermissive",
      "PRECOMPILED_HEADERS"=FALSE,
      paste0("LDFLAGS+= L\"", path_to_opencl_lib, "¥" -lOpenCL")
    )
    install_cmdstan(cores=4, overwrite=TRUE, cpp_options = cpp_options)
  • モデルのコンパイル。たしかここでも、赤字のnotesやwarningがたくさん出た。
    mod <- cmdstan_model(
        "./stantest.stan", 
        force_recompile = TRUE, 
        cpp_options = list(stan_opencl = TRUE)
    )
  • サンプリング。無事成功。サンプリングの所要時間は163秒。このとき、OpenCLを設定せずにインストールしたcmdstanも試したのだが、所要時間はたしかこの3倍程度だったと記憶している。
    n <- 250000
    k <- 20
    X <- matrix(rnorm(n * k), ncol = k)
    y <- rbinom(n, size = 1, prob = plogis(3 * X[,1] - 2 * X[,2] + 1))
    mdata <- list(k = k, n = n, y = y, X = X)
    fit <- mod$sample(data = mdata, chains = 4, parallel_chains = 4, opencl_ids = c(0, 0), refresh = 100)
Case 2: Win10, R4.3.1, Rtools 4.3, cmdstan2.32.2, OpenCLなし, 成功

ここからは2023年7月。ハードウェアは同じだが、Rのバージョンが少し上がっている。まずはベンチマークとして、OpenCLを使わない場合の時間を計ってみる。

  • Windows 10 Home (22H2)
  • R 4.3.1
  • Rtools 4.3.5550
  • cmdstanrパッケージ(0.5.3開発版)のインストール。
    • 安定版ではなく開発版をいれた理由は忘れたけど、あとになって、0.5.3安定版はRtools4.2, 開発版はRtools4.3を想定しているということに気が付いた。
    • 安定版も開発版もバージョン番号が同じなのって、わかりにくいっすね。
    install.packages("remotes")
    remotes::install_github("stan-dev/cmdstanr")
  • Rtoolsをインストールした直後は、cmdstanをインストールする前に、一度以下を実行しておく必要がありそうだ。
    check_cmdstan_toolchain(fix = TRUE)
  • cmdstan (2.32.2)のインストール:
    library(cmdstanr)
    install_cmdstan(cores=4, overwrite=TRUE)
  • モデルのコンパイル。
    mod <- cmdstan_model("./stantest.stan")
  • サンプリング。無事終了。所要時間は584秒であった。
    n <- 250000
    k <- 20
    X <- matrix(rnorm(n * k), ncol = k)
    y <- rbinom(n, size = 1, prob = plogis(3 * X[,1] - 2 * X[,2] + 1))
    mdata <- list(k = k, n = n, y = y, X = X)
    fit <- mod$sample(data = mdata, chains = 4, parallel_chains = 4, refresh = 100)
Case 3: Win10, R4.3.1, Rtools4.3, cmdstan2.32.2, OpenCL(NVIDIA CUDA11.4.4), 失敗

では、いよいよOpenCLを使ってみる。これがうまくいかなくてですね...

  • Windows 10 Home (22H2)
  • NVIDIA GeForce GTX 660, ドライババージョン474.44
  • NVIDIA CUDA Toolkit 11.4.4
    • この時点での最新版は12.2であったが、試行錯誤のうえ、11.4.4にまで引き下げた。Release Notesによれば、ドライバ474.44に対応しているのはCUDA 11.8.xまでらしいし、Toolkit同梱のドライバはCUDA 11.4.4で">=472.50"と書いてあるので、このへんがいいのかなと思った次第である。
  • R 4.3.1
  • Rtools 4.3.5550
  • cmdstanrパッケージ 0.5.3開発版
  • cmdstan (2.32.2)のインストール。無事終了したけど、途中で赤字のnotesやwarningが山ほど出た。怖い。
    library(cmdstanr)
    path_to_opencl_lib <- "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.4/lib/x64"
    cpp_options <- list(
      "CXXFLAGS += fpermissive",
      "PRECOMPILED_HEADERS"=FALSE,\
      paste0("LDFLAGS+= L\"", path_to_opencl_lib, "¥" -lOpenCL")
    )
    install_cmdstan(cores=4, overwrite=TRUE, cpp_options = cpp_options)
  • モデルのコンパイル。コードはCase 1と同じ。赤字はたくさん出るけど、とにかく終了した。
  • サンプリング。コードはCase 1と同じ。イテレーションがはじまり、GPUを使っていることをタスクマネージャで確認できた。ところが、驚くなかれ、途中でぶつん!と、Windowsごと再起動。モデルのコンパイルからやりなおしても再現された。呆然としました。
Case 4: Win10, R4.3.1, Rtools4.2, cmdstan2.32.2, OpenCL(NVIDIA CUDA11.4.4), 失敗

Case 3が失敗したのはRtools 4.3を使ったせいではないか、そしてcmdstanrパッケージは安定版ならRtools 4.2でも大丈夫なのではないか。というわけで、Rtools 4.3をアンインストールし、Rtools 4.2を入れなおして再挑戦。

  • Windows 10 Home (22H2)
  • NVIDIA GeForce GTX 660, ドライババージョン474.44
  • NVIDIA CUDA Toolkit 11.4.4
  • R 4.3.1
  • Rtools 4.2.0.1
  • cmdstanrパッケージ(0.5.3安定版)のインストール。
    remove.packages("cmdstanr", lib="~/AppData/Local/R/win-library/4.3")
    install.packages("cmdstanr", repos = c("https://mc-stan.org/r-packages/", getOption("repos")))
  • また文句を言われる前に、以下を実行しました。
    library(cmdstanr)
    check_cmdstan_toolchain(fix = TRUE)
  • cmdstan (2.32.2)のインストール。無事終了したけど、今回も途中で赤字のnotesやwarningが山ほど出ている。怖い。
    path_to_opencl_lib <- "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.4/lib/x64"
    cpp_options <- list(
      "CXXFLAGS += fpermissive",
      "PRECOMPILED_HEADERS"=FALSE,
      paste0("LDFLAGS+= L\"", path_to_opencl_lib, "¥" -lOpenCL")
    )
    install_cmdstan(cores=4, overwrite=TRUE, cpp_options = cpp_options)
  • モデルのコンパイル。コードはCase 1と同じ。やはり赤字の山。怖いよう...
  • サンプリング。コードはCase 1と同じ。今回もWindowsごと再起動となることを覚悟し、固唾を飲んで見守っていましたが... もう少しで終わるという瞬間、ぷつんと画面が真っ暗に。ううう、心臓に悪い。
Case 5: Win10, R4.3.1, Rtools4.2, cmdstan2.30.1, OpenCL(NVIDIA CUDA11.4.4), 失敗

ということは、犯人はcmdstanではないか? Case 1と同じバージョンに戻してみよう。

  • Windows 10 Home (22H2)
  • NVIDIA GeForce GTX 660, ドライババージョン474.44
  • NVIDIA CUDA Toolkit 11.4.4
  • R 4.3.1
  • Rtools 4.2.0.1
  • cmdstanrパッケージ 0.5.3安定版
  • cmdstan (2.30.1)のインストール。c:/users/(ユーザ名)/.cmdstan/の下にあるフォルダをすべて削除しておき、以下を実行。やはり赤字が出る...
    path_to_opencl_lib <- "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.4/lib/x64"
    cpp_options <- list(
      "CXXFLAGS += fpermissive",
      "PRECOMPILED_HEADERS"=FALSE,
      paste0("LDFLAGS+= L\"", path_to_opencl_lib, "¥" -lOpenCL")
    )
    install_cmdstan(cores=4, overwrite=TRUE, version = "2.30.1", cpp_options = cpp_options)
  • モデルのコンパイル。コードはCase 1と同じ。赤字の山。
  • サンプリング。コードはCase 1と同じ。今度は無事に終了しそうだ、いやー長かった... と感慨に耽っているさなか、画面が真っ暗に。ああ無情。
Case 6: Win10, R4.3.1, Rtools4.2, cmdstan2.30.1, OpenCL(NVIDIA CUDA11.7.1), 成功

 毒を喰らわば皿まで。CUDA ToolkitのバージョンをCase 1に揃えることにした。
 一言で言ってしまえばそれまでだけど、Windowsの「アプリと機能」からCUDAの項目をちくちく選んでは削除し、再起動し、ふたたびインストール... と、それだけで小一時間かかる面倒な作業である。なにをやっているんだろうか。

  • Windows 10 Home (22H2)
  • NVIDIA GeForce GTX 660, ドライババージョン474.44
  • NVIDIA CUDA Toolkit 11.7.1
  • R 4.3.1
  • Rtools 4.2.0.1
  • cmdstanrパッケージ 0.5.3安定版
  • cmdstan 2.30.1を再インストール。コードはCase 1と同じ。
  • モデルのコンパイル。コードはCase 1と同じ。赤字の山。
  • サンプリング。コードはCase 1と同じ。無事終了! 所要時間は奇しくもCase 1と同じ、163秒であった。
もしや、真の原因は

 やはり努力は報われる。原因はCUDA Toolkitのバージョンだったのだ。などと数十秒にわたって悦に入っていたが、ふと気が付いた。
 GPUを使ったサンプリング中、PCのファンの音がすごかった。わがPCは静音重視、CPUは水冷だがGPUはファンレスである。Case 3,4,5でWindowsごと吹っ飛んでしまったのは... もしかすると... 単なる熱暴走では?
 まさかね、と思って再びCase 6のサンプリングを試みたところ、今度はあっという間にWindowsごと再起動となった。

 いろいろ試行錯誤してまいりましたが、私の作業を妨げていた真の敵は、部屋が暑いことなのかもしれない。Case 1は冬だったしね。この部屋、エアコンないしね。現在の室温は33度だしね。
 がっくり...

Case 7: Win11, R4.3.1, Rtools4.3, cmdstan2.23.1, OpenCL (NVIDIA CUDA12.2), 成功

[2023/10/10追記] 歓迎、新しいPCくん。というわけで試してみました。

  • Windows 11 Pro (22H2)
  • NVIDIA GeForce RTX 4070, ドライババージョン537.42
  • NVIDIA CUDA Toolkit 12.2
  • R 4.3.1
  • Rtools 4.3
  • cmdstanrパッケージ(0.6.1)のインストール。
    install.packages(
      "cmdstanr", 
      repos = c("https://mc-stan.org/r-packages/", getOption ("repos"))
    )
  • cmdstan(2.33.1)のインストール。赤字のnotesやwarningが出た。
    library(cmdstanr)
    path_to_opencl_lib <- "C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v12.2/lib/x64"
    cpp_options = list(
      "CXXFLAGS += -fpermissive",
      "PRECOMPILED_HEADERS"=FALSE,
      paste0("LDFLAGS+= -L\"",path_to_opencl_lib,"\" -lOpenCL")
    )
    install_cmdstan(cores = 4, cpp_options = cpp_options, overwrite = TRUE)
  • モデルのコンパイル。赤字のnotesやwarningが出た。
    mod <- cmdstan_model(
        "./stantest2.stan", 
        force_recompile = TRUE, 
        cpp_options = list(stan_opencl = TRUE)
    )
  • サンプリング。コードはCase 1と同じ。無事成功。サンプリングの所要時間は56秒。拍手!
  • 立て続けに3回サンプリングしても無事に終了した。なお室温は29度。今度のビデオカードはGPU温度がわかるので眺めていたところ、通常は摂氏50度前後のところ、計算中もほぼ上昇せず、むしろ計算が終わると5度ほど下がった。計算中の温度上昇によりファンが強めに回っていたからだろうか。いずれにせよ、ある種の苦労はカネで解決できるということがわかった。