ocaml-torchをApple Siliconのマシンにインストールし,ひとまずCPU上で動かすことはできたので,手順をメモ書き的に残します.わりと乱雑なので,適宜修正するかもしれません.
目次
はじめに
深層学習関連のライブラリやツールは特定の計算機アーキテクチャやABIに依存した実装になっていることがしばしばあり,M1 Mac・M2 MacなどApple Siliconのマシンにインストールして動かすのは概して未だに厄介なようです.例えば,PyTorch 2.x自体をインストールして動かすのは比較的易しいようですが,.to(device)
などを各所で指定する必要のある,低級な部分をあまり覆い隠さないAPIになっているため,PyTorchに依存しているツールがx86_64前提であったりCUDAがサポートされている環境で動くことを前提した実装になっていてMPSでは使えない,といったこともよくあります.
PyTorchのOCamlバインディングであるocaml-torch(OPAM上のパッケージ名としては torch
)もこの点例外ではないようです.torch
は libtorch
というPyTorch C++ APIをラップしたOPAMパッケージに依存しており,この libtorch
のリリースが1.7.0以降はx86_64を前提していてApple Siliconはサポートされていません:
$ opam info libtorch
<><> libtorch: information on all versions ><><><><><><><><><><><><><><><><> 🐫
name libtorch
(中略)
all-versions 1.0.0 1.0.1 1.1.0 1.2.0 1.3.0 1.3.1 1.4.0 1.5.0 1.6.0 1.7.0+linux-x86_64
1.7.0+macos-x86_64 1.8.0+linux-x86_64 1.8.0+macos-x86_64 1.9.0+linux-x86_64
1.9.0+macos-x86_64 1.10.0+linux-x86_64 1.10.0+macos-x86_64 1.12.0+linux-x86_64
1.12.0+macos-x86_64 1.13.0+linux-x86_64 1.13.0+macos-x86_64 2.0.0+linux-x86_64
2.0.0+macos-x86_64 2.1.2+linux-x86_64 2.2.1+linux-x86_64
torch
も libtorch
もGitHubリポジトリ github.com/janestreet/torch
で開発されており1,これをcloneして opam pin
によりインストールを試みることもできますが,実際Apple Siliconのマシンでインストールしようとすると libtorch
の方のビルドで失敗するようで,Issueが立てられています:
で,これにはしっかりとした回答が寄せられています:
It looks like your problem is that the native libtorch binaries that you downloaded are for Intel rather than Apple Silicon. You can use these binaries by running an Intel build of OCaml through Rosetta. However, if you want to run natively, read on:
I added more instructions on this in LaurentMazare/ocaml-torch#76 (comment) but will copy here:
- Download
libtorch
binaries (or buildlibtorch
in your Mac). At the moment there are no official pre-build binaries. I downloaded my (unofficial) binaries from https://github.com/mlverse/libtorch-mac-m1/releases .- Install OCaml >= 4.14 (see here: https://opam.ocaml.org/packages/torch/)
- Double check what
libtorch
version is compatible with the current version of OCaml torch. Version 1.13.1 is the one you want withv0.16.0
version of OCamltorch
.- Set the
LIBTORCH
environment variable to the directory that includes the include and lib directories.To install with opam:
opam install torch.v0.16.0 --ignore-constraints-on libtorch
Note that I had to ignore-constraints to avoid failing because libtorch (an OCaml/OPAM library that contains pre-build libtorch binaries for some architectures that do not include M1/M2) is disabled for M1 Mac. Note also that I explicitly had to set the version of the package (JaneStreet version starts with v0 rather than 0) as I otherwise resolve to version 0.10 which is way too old.
要するに,単にバインドされるLibTorch(ややこしいですが,OPAMパッケージとしての libtorch
ではなくPyTorch C++ APIの実装のことです.上記回答中ではこれを指して「libtorch
binaries」とか単に「libtorch
」と表記されています)がx86_64をターゲットにしてビルドされていることが要因なようで,自前でApple Silicon向けにビルドしたLibTorchを用意して環境変数 LIBTORCH
にそのパスを設定してからOPAMパッケージをビルド・インストールするとよい,ということのようです.加えて,ありがたいことにLibTorchのそういったビルド結果をリリースしてくれている方がいるようです:
実際このリポジトリのReleasesにビルド済みのバイナリ群が公開されています:
というわけでひとまずこれを信頼して使わせてもらいましょう.どのバージョンにすべきかですが,以下のような考慮の結果上記回答と同じく1.13.1を使うことにしました:
torch
の最新版はv0.17.0
だが,これはOCaml 5.1.0以上を要請する.筆者の環境ではまだOCaml 4.14台を使い続けているので,手っ取り早く動かすために一旦これはやめておく(どうしても無理ならOCaml 5.1.0以上にアップグレードすることにする).torch
の1つ前のリリースv0.16.0
はOCaml 4.14で動くので,ひとまずこれを使うことを考える.これの依存パッケージlibtorch
に関するバージョン制約は"libtorch" {>= "1.13.0" & < "1.14.0"}
という記載になっている.- パッケージ
libtorch
のバージョン番号は,バインドされているLibTorchと同じものを使っているらしい. - というわけで,上記リリースのうち
libtorch-v1.13.1.zip
をダウンロードして使うとよさそう.
1 まずはインストールが一旦成功するまで
1-1 LibTorchの準備
まずは Releases · mlverse/libtorch-mac-m1 から libtorch-v1.13.1.zip
をダウンロードし,解凍して配置します.置く場所はどこでもよいですが,筆者はとりあえず $HOME/cloned/libtorch
に置きました.すなわち以下のような具合です:
$HOME/cloned/libtorch/
├── bin/
├── include/
├── lib/
└── share/
1-2 環境変数 LIBTORCH
の設定
1で配置したディレクトリを環境変数 LIBTORCH
に設定します(適切に読み替えてください):
$ export LIBTORCH="$HOME/cloned/libtorch"
1-3 janestreet/torch
の v0.16.0
をclone
これはやるだけ(1と同様に cloned/
を使っていますが自分の好みのディレクトリに読み替えてください):
$ cd $HOME/cloned
$ git clone https://github.com/janestreet/torch
$ cd torch
$ git checkout v0.16.0
1-4 libtorch
と torch
をビルド
さて,ここが少しハマりどころです.基本的には以下を実行すればインストールできるのですが,私の環境ではこれだけではダメでした:
$ opam pin add .
# 上記コマンドを叩くだけで libtorch と torch をインストールするか訊かれたりするが,`n`(=NO)をタイプしてインストールしない
$ opam install torch.v0.16.0 --ignore-constraints-on libtorch
具体的には,libtorch
のインストールには成功しますが,torch
のビルドでC言語の水準のエラーが出て失敗しました:
tsuwa@tsuwa-m2-MBP ~ % opam install torch.v0.16.0 --ignore-constraints-on libtorch
The following actions will be performed:
=== install 1 package
∗ torch v0.16.0
<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><> 🐫
⬇ retrieved torch.v0.16.0 (https://opam.ocaml.org/cache)
[ERROR] The compilation of torch.v0.16.0 failed at "dune build -p torch -j 7".
#=== ERROR while compiling torch.v0.16.0 ======================================#
# context 2.2.1 | macos/arm64 | ocaml-base-compiler.4.14.1 | https://opam.ocaml.org#56e31a3bc1fd0bfd87e5251972e806b8f78082a5
# path ~/.opam/4.14.1/.opam-switch/build/torch.v0.16.0
# command ~/.opam/opam-init/hooks/sandbox.sh build dune build -p torch -j 7
# exit-code 1
# env-file ~/.opam/log/torch-22992-7fcee8.env
# output-file ~/.opam/log/torch-22992-7fcee8.out
### output ###
# torch_stubs.c:38971:36: warning: passing 'const char *' to parameter of type 'char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
# [...]
# ^~~~~~
# ./torch_api_generated.h:2300:180: note: passing argument to parameter 'pad_mode' here
# void atg_stft_center(tensor *, tensor self, int64_t n_fft, int64_t hop_length_v, int hop_length_null, int64_t win_length_v, int win_length_null, tensor window, int center, char * pad_mode, int normalized, int onesided, int return_complex);
# ^
# 121 warnings and 1 error generated.
(中略)
<><> Error report <><><><><><><><><><><><><><><><><><><><><><><><><><><><><> 🐫
┌─ The following actions failed
│ λ build torch v0.16.0
└─
╶─ No changes have been performed
かなり多くのwarningが出ているのはさておき,ログファイル(上記の場合は ~/.opam/log/torch-22992-7fcee8.out
)をgrepしてみると,以下のようなerrorが /usr/bin/cc
によって出てビルドに失敗していました:
torch_stubs.c:295:27: error: incompatible function pointer types passing 'void (*)(const char *, void *)' to parameter of type 'void (*)(char *, tensor)' (aka 'void (*)(char *, void *)') [-Wincompatible-function-pointer-types]
at_load_callback(x280, x281);
^~~~
./torch_api.h:85:46: note: passing argument to parameter 'f' here
void at_load_callback(char *filename, void (*f)(char *, tensor));
筆者の環境の /usr/bin/cc
のバージョン等は以下です:
$ /usr/bin/cc --version
Apple clang version 15.0.0 (clang-1500.3.9.4)
Target: arm64-apple-darwin23.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
エラー報告にも出ているとおり,-Wincompatible-function-pointer-types
という設定に基づいたエラーであるはずなので,これを無効にするフラグを与えられれば通りそうです.というわけで,src/wrapper/dune
にそのような指定を追加します:
(library (name torch_core) (public_name torch.core) (c_names torch_stubs)
+ (c_flags :standard -Wno-incompatible-function-pointer-types)
(c_library_flags :standard -lstdc++ (:include c_library_flags.sexp))
(cxx_names torch_api) (cxx_flags -std=c++14 -fPIC (:include cxx_flags.sexp))
(libraries bigarray ctypes.foreign ctypes.stubs ctypes)
(preprocess (pps ppx_jane)))
(rule (targets cxx_flags.sexp c_library_flags.sexp)
(deps ../config/discover.exe) (action (bash %{deps})))
(rule (targets torch_bindings.ml) (deps ../stubs/torch_bindings.ml)
(action (bash "cp ../stubs/torch_bindings.ml torch_bindings.ml")))
(rule (targets torch_bindings_generated.ml)
(deps ../stubs/torch_bindings_generated.ml)
(action
(bash
"cp ../stubs/torch_bindings_generated.ml torch_bindings_generated.ml")))
(rule (targets torch_stubs.c torch_generated.ml)
(deps ../stubs/torch_gen.exe) (action (bash ./%{deps})))
branchを切ってこのような変更をcommitします(commitしないと,デフォルトではOPAMが無視してしまうためです.--working-dir
つきで opam pin
すればcommitしていない変更も加味されるのですが,いずれにしても作業履歴を残しておくのに便利なのでここではcommitします):
$ git checkout -b temp-patch
$ git add -A
$ git commit -m "patch \`dune\` as to \`c_flags\`"
この後にあらためて以下を叩くと torch
がインストールできました:
$ opam pin add .
# libtorchに関する依存制約が充足されていないので [ERROR] と出たりするが,無視してOK
$ opam install torch.v0.16.0 --ignore-constraints-on libtorch
というわけでここまででインストールはできましたが,実はこのままだと動かないので次章で修正を施していきます.
2 動かないところを直す
2-1 LibTorchのファイルを実行可能にする
まずは試しに utop
2で torch
が読み込めるか試してみましょう:
$ utop
(中略)
utop # #require "torch";;
Cannot load required shared library dlltorch_core_stubs.
Reason: $HOME/.opam/4.14.1/lib/stublibs/dlltorch_core_stubs.so: dlopen($HOME/.opam/4.14.1/lib/stublibs/dlltorch_core_stubs.so, 0x000A): Library not loaded: @rpath/libc10.dylib
Referenced from: <93108957-433A-3A67-8E42-77C8F6184B1D> $HOME/.opam/4.14.1/lib/stublibs/dlltorch_core_stubs.so
Reason: tried: '$HOME/cloned/libtorch/lib/libc10.dylib' (code signature in <(中略)> '$HOME/cloned/libtorch/lib/libc10.dylib' not valid for use in process: library load disallowed by system policy), '(中略)' (no such file), '$HOME/cloned/libtorch/lib/libc10.dylib' (code signature in <(中略)> '$HOME/cloned/libtorch/lib/libc10.dylib' not valid for use in process: library load disallowed by system policy), '(中略)' (no such file).
Error: Reference to undefined global `Torch_core__Wrapper'
筆者の環境だとOSによって $LIBTORCH/lib/libc10.dylib
の読み込みがブロックされました.permission上は実行可能でしたが,ネットワーク経由で落としてきたファイルなので,OSがpermissionとは別の仕組みによって実行できないものと扱っているようです.LibTorchを自己責任で信頼することにし,実行可能にしましょう.これは xattr
というコマンドで設定できるようです.まず,libc10.dylib
の実行がたしかにブロックされていることを確認しましょう:
$ cd $LIBTORCH
$ xattr lib/libc10.dylib
com.apple.quarantine
実行がブロックされるファイルには com.apple.quarantine
が付与されています.このファイルを信用してよいと判断したら,-d
オプションによって com.apple.quarantine
を削除します:
$ xattr -d com.apple.quarantine lib/libc10.dylib
これで実行できるようになりました.同様の処理をあと2つのファイルに対しても行ないます:
$ xattr -d com.apple.quarantine lib/libtorch_cpu.dylib
$ xattr -d com.apple.quarantine lib/libtorch.dylib
これでひとまず読み込み自体はできるようになります:
$ utop
(中略)
utop # #require "torch";;
2-2 実装例 examples/mnist/*.ml
を動かす準備
MNISTの学習の例 examples/mnist/{linear,nn,conv}.ml
がリポジトリにあるので,動かしてみましょう.examples/mnist/README.md
によると,リポジトリに data/
ディレクトリをつくってMNISTの訓練用データとテスト用データを配置する必要があるとのことなので,まずはそのように配置します.MNISTのデータセットは以下で配布されているようです:
……なのですが,少なくとも筆者が見たタイミングだとHTTPSの証明書が切れており,かつデータセットへのリンクにアクセスするとForbiddenでダウンロードできませんでした.Kaggleで配布されていたのでこちらを使わせてもらうことにします:
ここで配布されているものはファイル名が大元の配布と若干違っているので,直して data/
に以下のように配置します(それぞれ途中のピリオドをハイフンに変える必要がある.{t10k,train}-images-idx3-ubyte
はそれぞれzipファイルを解凍したもの):
torch/
└── data/
├── t10k-images-idx3-ubyte
├── t10k-labels-idx1-ubyte
├── train-images-idx3-ubyte
└── train-labels-idx1-ubyte
また,examples/mnist/dune
が v0.16.0
だと空ファイルになっているので,ビルドできるようにするために以下を書き込みます(v0.17.0
から取ってきたもの):
(executables
(modes byte exe)
(names conv linear nn)
(libraries stdio torch unix)
(preprocess
(pps ppx_jane)))
これで準備完了で,実際まず単純な線型回帰である linear.ml
をビルドしてみると成功はします:
$ dune build examples/mnist/linear.exe
しかし,できあがったバイナリを実行してみると以下のように落ちます3:
$ ./_build/default/examples/mnist/linear.exe
libc++abi: terminating due to uncaught exception of type c10::Error: The size of tensor a (10) must match the size of tensor b (10000) at non-singleton dimension 0
Exception raised from infer_size_impl at (中略)/libtorch-mac-m1/libtorch-mac-m1/pytorch/aten/src/ATen/ExpandUtils.cpp:35 (most recent call first):
frame #0: c10::detail::torchCheckFail(char const*, char const*, unsigned int, std::__1::basic_string, std::__1::allocator> const&) + 92 (0x100c1952c in libc10.dylib)
frame #1: at::infer_size_dimvector(c10::ArrayRef, c10::ArrayRef) + 376 (0x10ac6e35c in libtorch_cpu.dylib)
frame #2: at::TensorIteratorBase::compute_shape(at::TensorIteratorConfig const&) + 456 (0x10acbba90 in libtorch_cpu.dylib)
frame #3: at::TensorIteratorBase::build(at::TensorIteratorConfig&) + 520 (0x10acb6f8c in libtorch_cpu.dylib)
frame #4: at::TensorIteratorBase::build_borrowing_comparison_op(at::TensorBase const&, at::TensorBase const&, at::TensorBase const&) + 232 (0x10acb7960 in libtorch_cpu.dylib)
frame #5: at::(anonymous namespace)::wrapper_eq_Tensor(at::Tensor const&, at::Tensor const&) + 104 (0x10bf34c80 in libtorch_cpu.dylib)
frame #6: c10::impl::wrap_kernel_functor_unboxed_, at::Tensor, c10::guts::typelist::typelist>, at::Tensor (c10::DispatchKeySet, at::Tensor const&, at::Tensor const&)>::call(c10::OperatorKernel*, c10::DispatchKeySet, at::Tensor const&, at::Tensor const&) + 116 (0x10d456a08 in libtorch_cpu.dylib)
frame #7: at::_ops::eq_Tensor::call(at::Tensor const&, at::Tensor const&) + 284 (0x10b887ecc in libtorch_cpu.dylib)
frame #8: at::eq(at::Tensor const&, at::Tensor const&) + 40 (0x101457cb0 in dlltorch_core_stubs.so)
frame #9: atg_eq_tensor + 40 (0x101457b88 in dlltorch_core_stubs.so)
frame #10: caml__1017_atg_eq_tensor + 36 (0x1013ada70 in dlltorch_core_stubs.so)
frame #11: caml_interprete + 7760 (0x10093b370 in ocamlrun)
frame #12: caml_main + 1244 (0x10093d8e4 in ocamlrun)
frame #13: main + 16 (0x100960a14 in ocamlrun)
frame #14: start + 2476 (0x1816b3154 in dyld)
というわけで次節ではこれを直します.
2-3 不具合の修正
前節最後で出てきたエラーの原因の調査には数時間ほど費やされましたが,結論から言うと torch
の v0.16.0
に潜んでいる重大な不具合が原因でした.また,原因に気づいた後に確認したところ,v0.17.0
では修正されているようです.
具体的には,Torch.Tensor.argmax
の実装に間違いがあり,また具体例中での argmax
の使い方にも誤りがありました.以下のような修正が必要です(ついでに argmin
も修正しています):
src/wrapper/wrapper_generated.ml
:
let argmax self ~dim ~keepdim =
let out__ = CArray.make t 1 in
- stubs_argmax (CArray.start out__) self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 1 | None -> 0) (if keepdim then 1 else 0);
+ stubs_argmax (CArray.start out__) self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 0 | None -> 1) (if keepdim then 1 else 0);
let t0 = CArray.get out__ 0 in
Gc.finalise C.Tensor.free t0;
t0
let argmax_out ~out self ~dim ~keepdim =
let out__ = CArray.make t 1 in
- stubs_argmax_out (CArray.start out__) out self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 1 | None -> 0) (if keepdim then 1 else 0);
+ stubs_argmax_out (CArray.start out__) out self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 0 | None -> 1) (if keepdim then 1 else 0);
let t0 = CArray.get out__ 0 in
Gc.finalise C.Tensor.free t0;
t0
let argmin self ~dim ~keepdim =
let out__ = CArray.make t 1 in
- stubs_argmin (CArray.start out__) self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 1 | None -> 0) (if keepdim then 1 else 0);
+ stubs_argmin (CArray.start out__) self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 0 | None -> 1) (if keepdim then 1 else 0);
let t0 = CArray.get out__ 0 in
Gc.finalise C.Tensor.free t0;
t0
let argmin_out ~out self ~dim ~keepdim =
let out__ = CArray.make t 1 in
- stubs_argmin_out (CArray.start out__) out self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 1 | None -> 0) (if keepdim then 1 else 0);
+ stubs_argmin_out (CArray.start out__) out self (match dim with | None -> Int64.zero | Some v -> Int64.of_int v) (match dim with | Some _ -> 0 | None -> 1) (if keepdim then 1 else 0);
let t0 = CArray.get out__ 0 in
Gc.finalise C.Tensor.free t0;
t0
examples/mnist/linear.ml
:
(* Compute the validation error. *)
let test_accuracy =
- Tensor.(argmax (model test_images) = test_labels)
+ Tensor.(argmax ~dim:1 (model test_images) = test_labels)
|> Tensor.to_kind ~kind:(T Float)
|> Tensor.sum
|> Tensor.float_value
src/torch/dataset_helper.ml
:
let batch_accuracy =
- Tensor.(argmax predicted_labels = labels)
+ Tensor.(argmax ~dim:1 predicted_labels = labels)
|> Tensor.to_kind ~kind:(T Float)
|> Tensor.sum
|> Tensor.float_value
これでMNISTの学習がそれぞれ動くようになります:
$ ./_build/default/examples/mnist/linear.exe
1 2.302585 68.08%
2 1.508147 60.77%
3 1.387576 52.54%
4 1.578871 64.46%
5 1.706852 60.46%
6 1.193671 61.55%
7 1.395118 70.66%
8 1.290251 70.44%
9 0.891794 66.76%
10 0.934383 71.84%
11 1.107920 73.78%
(中略)
193 0.315388 91.65%
194 0.315182 91.67%
195 0.314978 91.66%
196 0.314774 91.66%
197 0.314573 91.67%
198 0.314373 91.68%
199 0.314174 91.68%
200 0.313977 91.68%
$ dune build examples/mnist/nn.exe
$ ./_build/default/examples/mnist/nn.exe
50 0.423307 89.46%
100 0.290042 92.06%
150 0.235908 93.36%
200 0.195982 94.31%
250 0.165280 94.96%
(中略)
800 0.037816 97.30%
850 0.033508 97.34%
900 0.029695 97.39%
950 0.026319 97.42%
1000 0.023346 97.44%
$ dune build examples/mnist/conv.exe
$ ./_build/default/examples/mnist/conv.exe
50 0.304574 93.58%
100 0.160071 96.40%
150 0.094311 97.60%
200 0.110899 97.92%
250 0.088540 97.92%
(中略)
4600 0.021709 99.12%
4650 0.030229 99.18%
4700 0.013466 99.01%
4750 0.005627 99.04%
4800 0.000020 99.11%
4850 0.002961 99.02%
4900 0.010990 99.09%
4950 0.013369 99.11%
5000 0.000920 99.20%
正解率から実際に学習もうまくいっていることがわかります.やったぜ.
まとめ
というわけでいろいろな落とし穴がありましたがひとまずCPUでocaml-torchを動かすことはできました.あとはMPSで動かせたら万々歳で,ocaml-torchのAPI上は可能そうなので,そのうち加筆するかもしれません.
もともと
github.com/LaurentMazare/ocaml-torch
で個人開発されていたものが2023年4月頃に現リポジトリへと移管されたようです. ↩︎OCamlの対話環境のひとつ.
opam install utop
でインストールできます. ↩︎ちなみに,ここのエラーで出ているテキストで「
(中略)
」とした部分にはおそらくLibTorchのApple Silicon向けビルドをつくった作者の方の作業ディレクトリであろうパスが表示されます.意図せずバイナリ中にハードコードされてしまったものと思われるので,念のため伏せました. ↩︎