仕事の都合でStanを使うことがあるんだけど、その環境を用意するのは結構面倒である。
特に困るのが並列処理。Stanの実装のひとつcmdstanは並列処理の方法をいくつか用意していて、そのひとつがOpenCLライブラリを経由したGPUの利用である。Windows上のRでcmdstanrパッケージを使う場合、その実行の遅さによってストレス死しないためにも、GPUをぜひ利用したい。
説明を読む分には簡単そうである。ところが、実際に試してみると、これがなかなかうまくいかない。
というわけで、cmdstanrパッケージでOpenCLを経由してGPUを使う方法について、試行錯誤の記録をメモしておく。すいません、細かい話で。大局的にみれば実にどうでもいい話である。どうでもいいじゃん! どうせ我々の仕事はいずれChatGPTに奪われちゃうんだろうし! 悲惨な老後が待っているんだろうし!
目次
- テスト用のStanコード
- Case 1: Win10, R4.2.1, Rtools4.2, cmdstan2.30.1, OpenCL(NVIDIA CUDA11.7.1), 成功
- Case 2: Win10, R4.3.1, Rtools4.3, cmdstan2.32.2, OpenCLなし, 成功
- Case 3: Win10, R4.3.1, Rtools4.3, cmdstan2.32.2, OpenCL(NVIDIA CUDA11.4.4), 失敗
- Case 4: Win10, R4.3.1, Rtools4.2, cmdstan2.32.2, OpenCL(NVIDIA CUDA11.4.4), 失敗
- Case 5: Win10, R4.3.1, Rtools4.2, cmdstan2.30.1, OpenCL(NVIDIA CUDA11.4.4), 失敗
- Case 6: Win10, R4.3.1, Rtools4.2, cmdstan2.30.1, OpenCL(NVIDIA CUDA11.7.1), 成功
- もしや、真の原因は
- Case 7: Win11, R4.3.1, Rtools4.3, cmdstan2.23.1, OpenCL (NVIDIA CUDA12.2), 成功
テスト用の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);
}
まずは、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)
ここからは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)
では、いよいよ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 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ごと再起動となることを覚悟し、固唾を飲んで見守っていましたが... もう少しで終わるという瞬間、ぷつんと画面が真っ暗に。ううう、心臓に悪い。
ということは、犯人は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と同じ。今度は無事に終了しそうだ、いやー長かった... と感慨に耽っているさなか、画面が真っ暗に。ああ無情。
毒を喰らわば皿まで。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度だしね。
がっくり...
[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度ほど下がった。計算中の温度上昇によりファンが強めに回っていたからだろうか。いずれにせよ、ある種の苦労はカネで解決できるということがわかった。